POINT
- Matplotlibで「折れ線グラフ」と「棒グラフ」の2軸グラフを作成した.横軸を日付にするには工夫が必要.
- Excelで作成したグラフと同じものを「簡単に」作成することができた.
- pandasでは階差や移動和などを求める関数が用意されている.
手っ取り早く2軸グラフ(折れ線グラフ+棒グラフ)の作り方だけを知りたい方は,以下を参照してください(クリックでジャンプ):
イントロダクション
以前の記事では,Pythonを用いて複数のExcelファイルから必要なデータを抜き出し時系列データを作成しました.その際,データの整形 (ソートや演算) やグラフの作成など,Excelでできる処理はコード化しませんでした.今回は,
データ整形とグラフの作成までをPythonで実行してみます.
また,このプログラムを定期的に実行し以下の記事にアップしています.最近の外国人投資家動向を知りたい方は参考にしてください:
作成した図
作成した図は以下です.上段のグラフが前回作成したグラフ(1月単位のプロット)に相当します.下段は日経平均の週足データです.2つめのグラフは2017/03/31〜2018/03/31を抽出したものです.
- 上のグラフ
- 赤線:日経平均株価の「4週間前の終値」との差
- 青棒:海外投資家の「過去4週間」の買越し額(マイナスなら売越し)
- 下のグラフ
前回Excelで作成した図と比較してみましょう.同じものが作成できていますね!
Pythonコード
作成したプログラムのコードを残しておきます.
コードにあるコメントを見れば,大体やっていることがわかると思います.そのうち各処理の解説をこの記事に追記できればなあ...と思っています.とりあえず,感想を書いておきます:
- Pythonを使ってよかった点
- データの結合,移動和や階差を求める関数が用意されている.Excelで処理するよりも遥かにラク.
- 折れ線グラフだけで良ければ,DataFrame.plot()とするだけで良い感じのグラフが書ける.
- コーディングの際はデータが必要ないこと.Excelで大量のデータを扱うとフィルタリング等の操作でフリーズするが,その心配がない.
- 苦労した点
import os, re, xlrd
import seaborn as sns
import numpy as np
from datetime import datetime
from pandas import Series, DataFrame
import pandas as pd
from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
data = {}
target_filename_pattern = re.compile(r'stock_val.*')
counter = 0
for filename in os.listdir('.'):
counter += 1
period = ''
mo = target_filename_pattern.search(filename)
if mo == None:
continue
if filename.endswith(".xls"):
print('opening '+str(counter)+'. '+filename)
wb = xlrd.open_workbook(filename)
for i in range(wb.nsheets):
sheet = wb.sheet_by_index(i)
sheetname = sheet.name
period = sheet.cell(3,0).value
classification = sheet.cell(29,0).value
sales = sheet.cell(29,8).value
purchases = sheet.cell(30,8).value
difference = int(purchases.replace(',','')) - int(sales.replace(',',''))
if period != '':
data.setdefault(filename,{})
data[filename].setdefault(sheetname,{})
data[filename][sheetname].setdefault(period,{})
data[filename][sheetname][period].setdefault(classification,{})
data[filename][sheetname][period][classification]['sales'] = int(sales.replace(',',''))
data[filename][sheetname][period][classification]['purchases'] = int(purchases.replace(',',''))
data[filename][sheetname][period][classification]['difference'] = int(difference)
data_dict = {}
data_dict['market'] = []
data_dict['datetime_start'] = []
data_dict['datetime_end'] = []
data_dict['sales'] = []
data_dict['purchases'] = []
data_dict['difference'] = []
date_pattern = re.compile(r'(.*)\s+(.*)/(.*)\s+week(.*)\s+\((.*)/(.*)\s+-\s+(.*)/(.*)\)')
for k_filename in data.keys():
for k_sheetname in data[k_filename].keys():
for k_period in data[k_filename][k_sheetname].keys():
mo = date_pattern.search(k_period)
year = int(mo.group(2))
start_month = int(mo.group(5))
start_day = int(mo.group(6))
end_month = int(mo.group(7))
end_day = int(mo.group(8))
for k_classification in data[k_filename][k_sheetname][k_period].keys():
data_dict['datetime_start'].append(datetime(year, start_month, start_day))
data_dict['datetime_end'].append(datetime(year, end_month, end_day))
data_dict['market'].append(k_sheetname)
data_dict['sales'].append(data[k_filename][k_sheetname][k_period][k_classification]['sales'])
data_dict['purchases'].append(data[k_filename][k_sheetname][k_period][k_classification]['purchases'])
data_dict['difference'].append(data[k_filename][k_sheetname][k_period][k_classification]['difference'])
frame = DataFrame(data_dict, columns=['market', 'difference'], index=data_dict['datetime_end'])
frame = frame.sort_index()
df_TSE1 = frame[frame['market'].isin(['TSE 1st'])]
df_TSE2 = frame[frame['market'].isin(['TSE 2nd'])]
df_TSEM = frame[frame['market'].isin(['TSE Mothers'])]
df_TSEJ = frame[frame['market'].isin(['TSE JASDAQ'])]
df_TSETN = frame[frame['market'].isin(['Tokyo & Nagoya'])]
df_TSE1 = df_TSE1.drop(['market'], axis=1)
df_TSE2 = df_TSE2.drop(['market'], axis=1)
df_TSEM = df_TSEM.drop(['market'], axis=1)
df_TSEJ = df_TSEJ.drop(['market'], axis=1)
df_TSETN = df_TSETN.drop(['market'], axis=1)
df_TSE1 = df_TSE1.rename(columns={'difference':'TSE 1st'})
df_TSE2 = df_TSE2.rename(columns={'difference':'TSE 2nd'})
df_TSEM = df_TSEM.rename(columns={'difference':'TSE Mothers'})
df_TSEJ = df_TSEJ.rename(columns={'difference':'TSE JASDAQ'})
df_TSETN = df_TSETN.rename(columns={'difference':'Tokyo & Nagoya'})
df_ALL = pd.merge(df_TSE1, df_TSE2, left_index=True, right_index=True, how='inner')
df_ALL = pd.merge(df_ALL, df_TSEM, left_index=True, right_index=True, how='inner')
df_ALL = pd.merge(df_ALL, df_TSEJ, left_index=True, right_index=True, how='inner')
df_ALL = pd.merge(df_ALL, df_TSETN, left_index=True, right_index=True, how='inner')
df_ALL = df_ALL.div(1e6, axis='columns')
df_nikkei = pd.read_csv('./^nkx_w.csv', index_col=['Date'], parse_dates=['Date'])
average_weeks = int(4)
df_nikkei['Nikkei_diff'] = df_nikkei['Close'].diff(periods=average_weeks)
df_ALL = df_ALL.rolling(window=average_weeks).sum()
df_ALL = pd.merge(df_ALL, df_nikkei, left_index=True, right_index=True, how='inner')
df_ALL = df_ALL.dropna()
market = 'TSE 1st'
sns.set()
fig = plt.figure()
plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=0.1)
ax1 = fig.add_subplot(2,1,1)
df_ALL[['Nikkei_diff']].plot(ax=ax1, color='r', rot=90, grid=True)
ax1.legend(loc='upper right')
ax2 = ax1.twinx()
ax2.bar(df_ALL.index, df_ALL[market], width=6.5, label='Foreigner\'s purchases', color='b', alpha=0.6)
ax2.legend(loc='lower right')
ax1.xaxis.set_major_locator(mdates.MonthLocator(bymonth=[1,4,7,10]))
ax1.set_xlim([df_ALL.index[0],df_ALL.index[len(df_ALL.index)-1]])
ax1.set_title(u'Foreigners and Nikkei225')
ax1.set_xlabel('Date')
ax1.set_ylabel('Nikkei225_diff(Yen)')
ax2.set_ylabel('Foreigner\'s purchases \n at '+market+' (billion Yen)')
if average_weeks == 1:
ax1.set_ylim([-2000,2000])
ax2.set_ylim([-2000,2000])
if average_weeks == 4:
ax1.set_ylim([-3000,3000])
ax2.set_ylim([-3000,3000])
ax3 = fig.add_subplot(2,1,2)
df_ALL[['Close']].plot(ax=ax3, rot=90, grid=True)
ax3.set_xlim([df_ALL.index[0],df_ALL.index[len(df_ALL.index)-1]])
ax3.xaxis.set_major_locator(mdates.MonthLocator(bymonth=[1,4,7,10]))
ax3.set_ylabel('Nikkei225_Close(Yen)')
plt.savefig('Foreigner_and_Nikkei225_'+str(average_weeks)+'weeks.pdf', bbox_inches='tight')
x_min = '2017/03/31'
x_max = '2018/03/31'
y3_min = df_ALL['Close'].ix[x_min:x_max].min() - 500
y3_max = df_ALL['Close'].ix[x_min:x_max].max() + 500
ax1.set_xlim([x_min, x_max])
ax2.set_xlim([x_min, x_max])
ax3.set_xlim([x_min, x_max])
ax3.set_ylim([y3_min, y3_max])
ax1.xaxis.set_major_locator(mdates.MonthLocator())
ax3.xaxis.set_major_locator(mdates.MonthLocator())
plt.savefig('Foreigner_and_Nikkei225_'+str(average_weeks)+'weeks_ThisYear.pdf', bbox_inches='tight')
x_min = '2017/12/01'
x_max = '2018/04/01'
y3_min = df_ALL['Close'].ix[x_min:x_max].min() - 500
y3_max = df_ALL['Close'].ix[x_min:x_max].max() + 500
ax1.set_xlim([x_min, x_max])
ax2.set_xlim([x_min, x_max])
ax3.set_xlim([x_min, x_max])
ax3.set_ylim([y3_min, y3_max])
datefmt = mdates.DateFormatter('%Y-%m-%d')
ax1.xaxis.set_major_formatter(datefmt)
ax2.xaxis.set_major_formatter(datefmt)
ax3.xaxis.set_major_formatter(datefmt)
ax1.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=FR))
ax2.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=FR))
ax3.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=FR))
plt.savefig('Foreigner_and_Nikkei225_'+str(average_weeks)+'weeks_4month.pdf', bbox_inches='tight')
print('complete')
プログラムの解説
実行方法と,コードの解説です.
使い方
JPXから落としてきた「株式売買状況[金額]」のxlsファイルと日経平均株価のデータをpythonプログラムを同一ディレクトリに入れて実行すると,グラフがpdfで作成されます.データは以下から手動で取得しました:
- 外国人投資家の売買額:株式売買状況[金額]のxlsファイル(stock_val_*.xlsという名称)
- 日経平均株価の週足データ(^nkx_w.csvという名称)
Step.1:データの作成
前回の記事と同じ処理です.前回は,Step.2でExcelへの出力を行ったのに対し,今回はDataFrameを作成しています.
Step.2:DataFrameに変換
- {'title':[list]}という辞書(data_dict)を作成する.
これは,次のような表をつくることに対応しています(実際はmarketごとには並んでいません).
titleは下表のmarket, datetime_start, datetime_end, sales, purchases, differenceを表しており,その下に並ぶ一列が対応するlistです.
market |
datetime_start |
datetime_end |
sales |
purchases |
difference |
TSE 1st |
期間の開始日 |
期間の終了日 |
金額(売り) |
金額(買い) |
金額(買い- 売り) |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
TSE 2nd |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
TSE Mothers |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
TSE JASDAQ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
Tokyo & Nagoya |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
- DataFrameを作成する.
簡単にグラフを作成できるデータ形式である,DataFrameに変換します(同時に余分なデータを削除しています):
frame = DataFrame(data_dict, columns=['market', 'difference'], index=data_dict['datetime_end'])
datetime_end |
market |
difference |
期間の終了日 |
TSE 1st |
金額(買い- 売り) |
・・・ |
・・・ |
・・・ |
・・・ |
TSE 2nd |
・・・ |
・・・ |
・・・ |
・・・ |
- データ整形し,新しいDataFrameを作成する.
- 日付順にソートする
frame = frame.sort_index()
- 市場ごとのデータを取り出したDataFrameを作成する.
df_TSE1 = frame[frame['market'].isin(['TSE 1st'])]
datetime_end |
market |
difference |
期間の終了日 |
TSE 1st |
金額(買い- 売り) |
期間の終了日 |
TSE 1st |
・・・ |
期間の終了日 |
・・・ |
・・・ |
期間の終了日 |
TSE 1st |
・・・ |
- 不要なmarket列を削除する
df_TSE1 = df_TSE1.drop(['market'], axis=1)
datetime_end |
difference |
期間の終了日 |
金額(買い- 売り) |
・・・ |
・・・ |
- differenceというタイトルを,市場名称に変更する
df_TSE1 = df_TSE1.rename(columns={'difference':'TSE 1st'})
datetime_end |
TSE 1st |
期間の終了日 |
金額(買い- 売り) |
・・・ |
・・・ |
- 市場ごとのデータをindex(datetime_end)をもとにマージする
df_ALL = pd.merge(df_TSE1, df_TSE2, left_index=True, right_index=True, how='inner')
df_ALL = pd.merge(df_ALL, df_TSEM, left_index=True, right_index=True, how='inner')
df_ALL = pd.merge(df_ALL, df_TSEJ, left_index=True, right_index=True, how='inner')
df_ALL = pd.merge(df_ALL, df_TSETN, left_index=True, right_index=True, how='inner')
datetime_end |
TSE 1st |
TSE 2nd |
・・・ |
Tokyo & Nagoya |
期間の終了日 |
金額(買い- 売り) |
金額(買い- 売り) |
・・・ |
金額(買い- 売り) |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
Step.3:データの整形
- csvファイルをDataFrameにする.
df_nikkei = pd.read_csv('./^nkx_w.csv', index_col=['Date'], parse_dates=['Date'])
Date |
Open |
High |
Low |
Close |
Volume |
日付 |
始値 |
高値 |
安値 |
終値 |
出来高 |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
- 階差・移動和を計算する
average_weeks = int(4)
df_nikkei['Nikkei_diff'] = df_nikkei['Close'].diff(periods=average_weeks)
df_ALL = df_ALL.rolling(window=average_weeks).sum()
- 海外投資家のデータと,日経平均株価のデータをマージする.
df_ALL = pd.merge(df_ALL, df_nikkei, left_index=True, right_index=True, how='inner')
df_ALL = df_ALL.dropna()
Date |
TSE 1st |
TSE 2nd |
・・・ |
Tokyo & Nagoya |
Nikkei_diff |
日付 |
4週間の合計金額(買い- 売り) |
週間の合計金額金額(買い- 売り) |
・・・ |
週間の合計金額金額(買い- 売り) |
4週間前との差分(日経平均終値) |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
・・・ |
Step.4:plot (2軸グラフ)
折れ線グラフ+棒グラフの2軸グラフを作成するには以下の様にすれば良いことがポイントです(あとは,ラベルや軸の調整などをしているに過ぎません).
DataFrame.plot(ax=ax1)
ax2 = ax1.twinx()
ax2.bar(DataFrame.index, DataFrame['title'])
棒グラフでx軸として日付データ(datetime型)を用いる方法(Pandas & Matplotlib: personalize the date format in a bar chart - Simone Centellegher, PhD - Data scientist and Researcher)を見つけるのが大変だった.他の方法(DataFrame.plot()など)ではエラーが出てしまう.
- DataFrame.plot()でエラーが出る理由:恐らくDataFrame.plot(kind='bar')ではx軸が非負整数の連番となるため(print(Axes.get_xticks())で確認した).
- 他の方法:ダミーのx軸データをnp.arange(len(DataFrame.index))で作成し,line plot とbar plotで2軸グラフを作成することはできた.しかし,この場合「データが存在しない日付」は表示しない(飛ばした)グラフになってしまう.
参考文献/記事
- まずは,この本をペラペラめくって,使えそうなところを継ぎ接ぎしました.古い部分はGoogle検索して書き直せばOKです: