【Python】Matplotlibで2軸グラフ(折れ線グラフ+棒グラフ)

f:id:IsThisAPen:20180407124448j:plainf:id:IsThisAPen:20180407124518j:plain

POINT

  • Matplotlibで「折れ線グラフ」と「棒グラフ」の2軸グラフを作成した.横軸を日付にするには工夫が必要.
  • Excelで作成したグラフと同じものを「簡単に」作成することができた.
  • pandasでは階差や移動和などを求める関数が用意されている.

手っ取り早く2軸グラフ(折れ線グラフ+棒グラフ)の作り方だけを知りたい方は,以下を参照してください(クリックでジャンプ):

イントロダクション

以前の記事では,Pythonを用いて複数のExcelファイルから必要なデータを抜き出し時系列データを作成しました.その際,データの整形 (ソートや演算) やグラフの作成など,Excelでできる処理はコード化しませんでした.今回は,データ整形とグラフの作成までをPythonで実行してみます.


また,このプログラムを定期的に実行し以下の記事にアップしています.最近の外国人投資家動向を知りたい方は参考にしてください:

作成した図

作成した図は以下です.上段のグラフが前回作成したグラフ(1月単位のプロット)に相当します.下段は日経平均の週足データです.2つめのグラフは2017/03/31〜2018/03/31を抽出したものです.

  • 上のグラフ
    • 赤線:日経平均株価の「4週間前の終値」との差
    • 青棒:海外投資家の「過去4週間」の買越し額(マイナスなら売越し)
  • 下のグラフ
    • 日経平均株価の週足終値

f:id:IsThisAPen:20180407124448j:plain

f:id:IsThisAPen:20180407124518j:plain


前回Excelで作成した図と比較してみましょう.同じものが作成できていますね!

f:id:IsThisAPen:20180407124448j:plainf:id:IsThisAPen:20180303220055j:plain
Excelで作成したグラフとの比較.左図:今回作成.右図:前回作成 (Excel).

Pythonコード

作成したプログラムのコードを残しておきます.

コードにあるコメントを見れば,大体やっていることがわかると思います.そのうち各処理の解説をこの記事に追記できればなあ...と思っています.とりあえず,感想を書いておきます:

  • Pythonを使ってよかった点
    • データの結合,移動和や階差を求める関数が用意されている.Excelで処理するよりも遥かにラク.
    • 折れ線グラフだけで良ければ,DataFrame.plot()とするだけで良い感じのグラフが書ける.
    • コーディングの際はデータが必要ないこと.Excelで大量のデータを扱うとフィルタリング等の操作でフリーズするが,その心配がない.
  • 苦労した点

プログラムの解説

実行方法と,コードの解説です.

使い方

JPXから落としてきた「株式売買状況[金額]」のxlsファイルと日経平均株価のデータをpythonプログラムを同一ディレクトリに入れて実行すると,グラフがpdfで作成されます.データは以下から手動で取得しました:
  • 外国人投資家の売買額:株式売買状況[金額]のxlsファイル(stock_val_*.xlsという名称)

  • 日経平均株価の週足データ(^nkx_w.csvという名称)

Step.1:データの作成

前回の記事と同じ処理です.前回は,Step.2でExcelへの出力を行ったのに対し,今回はDataFrameを作成しています.

Step.2:DataFrameに変換

  1. {'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 ・・・ ・・・ ・・・ ・・・ ・・・
    ・・・ ・・・ ・・・ ・・・ ・・・ ・・・


  2. DataFrameを作成する.
    簡単にグラフを作成できるデータ形式である,DataFrameに変換します(同時に余分なデータを削除しています):

    frame = DataFrame(data_dict, columns=['market', 'difference'], index=data_dict['datetime_end'])
    
    datetime_end market difference
    期間の終了日 TSE 1st 金額(買い- 売り)
    ・・・ ・・・ ・・・
    ・・・ TSE 2nd ・・・
    ・・・ ・・・ ・・・


  3. データ整形し,新しい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:データの整形

  1. csvファイルをDataFrameにする.

    df_nikkei = pd.read_csv('./^nkx_w.csv', index_col=['Date'], parse_dates=['Date'])
    
    Date Open High Low Close Volume
    日付 始値 高値 安値 終値 出来高
    ・・・ ・・・ ・・・ ・・・ ・・・ ・・・


  2. 階差・移動和を計算する

    average_weeks = int(4) # 移動和・階差を取る単位.
    df_nikkei['Nikkei_diff'] = df_nikkei['Close'].diff(periods=average_weeks) #average_weeks前との差分
    df_ALL = df_ALL.rolling(window=average_weeks).sum() #average_weeks週間の移動和
    


  3. 海外投資家のデータと,日経平均株価のデータをマージする.

    df_ALL = pd.merge(df_ALL, df_nikkei, left_index=True, right_index=True, how='inner')
    df_ALL = df_ALL.dropna()# NaN値を含む行を削除
    
    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軸グラフを作成することはできた.しかし,この場合「データが存在しない日付」は表示しない(飛ばした)グラフになってしまう.

参考文献/記事