読者です 読者をやめる 読者になる 読者になる

akiyoko blog

akiyoko の IT技術系ブログです

matplotlib のグラフに日本語を表示する方法(文字化け対応)

今回の内容は、Jupyter Notebook 上で matplotlib を利用したグラフを描画する際に日本語のラベル名が文字化けしてしまう事象への解消方法です。


ローカルの実行環境は以下の通り。

  • MacOS Sierra 10.12.3
  • Python 2.7.12 (Anaconda 4.2.0)
  • Jupyter Notebook 4.2.0
  • pandas 0.18.1


 

困ったこと

(ipynb ファイルの先頭部分はこのような感じで書いています。)

import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
% matplotlib inline

plt.style.use('ggplot')


例えば、次のようなデータがあるとして、

df = pd.DataFrame({u'データ1': [0, 2, 4],
                   u'データ2': [1, 2, 3]})
df

f:id:akiyoko:20170409131243p:plain:w150


matplotlib を利用して Jupyter Notebook 上にグラフを描くと、何もしないデフォルトの状態では、タイトルや判例、XY軸のラベル名などの日本語が文字化けして(いわゆる「豆腐」になって)しまいます。

df.plot(y=[u'データ1', u'データ2'])
plt.xlabel(u'X軸')
plt.ylabel(u'Y軸')
plt.title(u'日本語・テスト')

f:id:akiyoko:20170409131300p:plain



ここで、Mac にデフォルトでインストールされているゴシックフォント「AppleGothic」を指定すると、ほとんどの文字化けは解消されますが、まだ一部の文字(「ー」や「・」など)が文字化けしてしまいます。

font = {'family': 'AppleGothic'}
matplotlib.rc('font', **font)

df.plot(y=[u'データ1', u'データ2'])
plt.xlabel(u'X軸')
plt.ylabel(u'Y軸')
plt.title(u'日本語・テスト')

f:id:akiyoko:20170409131622p:plain



 

解決策

いろいろ調べてみると、フリーで使える TrueType フォントである「IPAex ゴシック」フォントを利用するのが一番手っ取り早そうでした。


以下の手順で解決できました。

  • 1)IPAex ゴシックフォントをインストール
  • 2)matplotlib のフォントキャッシュを削除


 

1)IPAex ゴシックフォントをインストール

IPAexフォントのダウンロードページから「IPAexフォント Ver.003.01」をクリック。
http://ipafont.ipa.go.jp/node17#jp

f:id:akiyoko:20170409130932p:plain

IPAexゴシック(Ver.003.01) のリンクから「ipaexg00301.zip(3.92MB)」をダウンロードします。

f:id:akiyoko:20170409131004p:plain


「~/Library/Fonts/」に .ttfファイルをコピーして、フォントをインストールします。

$ cp ~/Downloads/ipaexg00301/ipaexg.ttf ~/Library/Fonts/

なお公式ページは「/Library/Fonts」へのインストールを推奨していますが、私はいつもユーザ単位でインストールしています。


ちなみに、「/System/Library/Fonts/」は Mac のシステムが使用するフォントが格納されているので、ここには追加しないように注意。



2)matplotlib のフォントキャッシュを削除

次に、「~/.matplotlib/」直下のフォントのキャッシュを削除します。

$ rm ~/.matplotlib/fontList*.cache





最後に、確認。

font = {'family': 'IPAexGothic'}
matplotlib.rc('font', **font)

df.plot(y=[u'データ1', u'データ2'])
plt.xlabel(u'X軸')
plt.ylabel(u'Y軸')
plt.title(u'日本語・テスト')

f:id:akiyoko:20170409131800p:plain





以下のサイトを参考にしました。

(参考)


 

もっと便利に使う(matplotlibrc を作成)

「~/.matplotlib/matplotlibrc」を作成し、そこに利用する font.family などの指定をしておくと、matplotlib がその設定ファイルを優先的に読み込んでくれるようになります。


以下のファイルを作成します。

~/.matplotlib/matplotlibrc

font.family         : IPAexGothic



この設定ファイルを書いておくと、

font = {'family': 'IPAexGothic'}
matplotlib.rc('font', **font)

の設定は不要になります。


反映されない場合は、以下のコマンドを実行すれば解消するかもしれません。

$ rm ~/.matplotlib/fontList*.cache


 

参考

Jypyter Notebook の勉強をするならこの本を。



Pandas、matplotlib について本格的に勉強するならこちらの本を。

pandas.DataFrame で時系列データの手習い

前回・前々回と、pandas.DataFrame の基礎編についての記事を書きましたが、

その応用編として、時系列データを扱うための手習いとして pandas.DataFrame をいろいろ弄ってみようと思っています。


なおこれまで同様、ローカルの実行環境は以下の通りとなっています。

  • MacOS Sierra 10.12.3
  • Python 2.7.12 (Anaconda 4.2.0)
  • Jupyter Notebook 4.2.0
  • pandas 0.18.1



まずは、import などの諸設定。

import glob

import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
% matplotlib inline

pd.options.display.max_rows = 10

plt.style.use('ggplot')
font = {'family': 'IPAexGothic'}
matplotlib.rc('font', **font)

 

CSVファイルからデータを読み込む

例えば、1日分のデータとして以下のような CSVデータを想定。
一列目(date)が日付なのですが、「yymmdd」形式で出力されています。

date,expected_value,preliminary_value,name,confirmed_value
160301,,,,
160301,0,-100,銀行券要因,
160301,-7200,-10000,財政等要因,
160301,-7200,-10100,資金過不足,
160301,6000,6000, ,
160301,7500,7500, ,
160301,,, ,
160301,,, ,
160301,,, ,
160301,,, ,
160301,,, ,
160301,,, ,
160301,,, ,
160301,-300,-300, ,
160301,-100,-100, ,
160301,,, ,
160301,,, ,
160301,,, ,
160301,,, ,
160301,,-200, ,
160301,+13100,+12900, ,
160301,,, ,
160301,,, ,
160301,+0,+0, ,
160301,+13100,+12900,合計,
160301,+5900,+2800,当座預金増減,
160301,,2592700,当座預金残高,
160301,,2336500, ,
160301,,1896900, ,
160301,,1896900, ,
160301,,256200, ,
160301,,,マネタリーベース,
160301,,積み期間(2/16~3/15日)の所要準備額(積数),積み期間(2/16~3/15日)の所要準備額(積数),1917300
160301,,積み期間(2/16~3/15日)の所要準備額(1日平均),積み期間(2/16~3/15日)の所要準備額(1日平均),66100
160301,,3/2日以降の残り要積立額(積数),3/2日以降の残り要積立額(積数),3300
160301,,3/2日以降の残り要積立額(1日平均),3/2日以降の残り要積立額(1日平均),200


まずは、CSVデータの読み込み。

CSV データの一列目の日付文字列を DatetimeIndex オブジェクトに変換して、index 列として読み込んでいます。

(参考)pandasで様々な日付フォーマットを取り扱う - Qiita

date_parser = lambda d: pd.datetime.strptime(d, '%y%m%d')
df = pd.read_csv(
    '/Users/akiyoko/PycharmProjects/marketstat/downloads/boj/2016/160301.csv',
    index_col='date', parse_dates=True, date_parser=date_parser,
    na_values=' ',  # たまに「name」列に ' ' が入っているので NaN に変換
)
df

f:id:akiyoko:20170409232420p:plain:w400


na_values オプションは、CSV 読み込み時に特定の値(今回の場合は半角スペース)を NaN に変換してくれるので便利です。



 

特定の列が NaN の行を除外

「name」列や「preliminary_value」列が NaN の行を除外します(不要なので)。

(参考)pandas.DataFrame.dropna — pandas 0.19.2 documentation

df = df.dropna(subset=['name', 'preliminary_value'])
df

f:id:akiyoko:20170409232651p:plain:w400


 

特定条件の行だけを抽出

「name」列が「当座預金残高」になっている行だけを抽出します。

df = df[df['name'] == '当座預金残高']
df

f:id:akiyoko:20170409232706p:plain:w400


 

特定の列だけを抽出

「expected_value」列と「confirmed_value」列を除外します(不要なので)。

(参考)pandas.DataFrame.drop — pandas 0.19.2 documentation

df = df.drop(['expected_value', 'confirmed_value', 'name'], axis=1)
df

# 以下のように残すカラムだけ指定しても、同じ結果が得られる
# df = df[['preliminary_value']]

f:id:akiyoko:20170409232719p:plain:w150



 

ラベル名を変更

column名を「preliminary_value」から「当座預金残高」に変更します。

(参考)pandas DataFrameの行名・列名の変更 | nkmk log

df = df.rename(columns={'preliminary_value': u'当座預金残高'})


次に、index名を「Date」に変更します。

df.index.names = ['Date']
df

f:id:akiyoko:20170409232732p:plain:w150


 

値を int型に変換

ここで、「当座預金残高」列のデータの値は object 型になっています。

df[u'当座預金残高']
Date
2016-03-01    2592700
Name: 当座預金残高, dtype: object


object型のままだとグラフにできないので、int型に変換します。

(参考)Python pandas strアクセサによる文字列処理 - StatsFragments

df[u'当座預金残高'] = df[u'当座預金残高'].astype(int)
df[u'当座預金残高']
Date
2016-03-01    2592700
Name: 当座預金残高, dtype: int64



 

実践編

次に、一定期間分(2016〜2017年分)の CSVデータを読み込んでみます。

# 時系列データ(日銀の当座預金残高)を読み込む
df_boj = pd.DataFrame()
date_parser = lambda d: pd.datetime.strptime(d, '%y%m%d')
paths = glob.glob('/Users/akiyoko/PycharmProjects/marketstat/downloads/boj/201[67]/*.csv')
for path in paths:
    df = pd.read_csv(path,
                     index_col='date', parse_dates=True, date_parser=date_parser,
                     na_values=' ')
    # 「name」列や「preliminary_value」列が NaN の行を除外
    df = df.dropna(subset=['name', 'preliminary_value'])
    # 「name」列が '当座預金残高' になっている行だけに絞る
    df = df[df['name'] == '当座預金残高']
    # 「expected_value」列と「confirmed_value」列を除外
    df = df.drop(['expected_value', 'confirmed_value', 'name'], axis=1)
    # column名を「preliminary_value」から「当座預金残高」に変更
    df = df.rename(columns={'preliminary_value': u'当座預金残高'})
    # index名を「Date」に変更する
    df.index.names = ['Date']
    # object型のままだとグラフにできないので int型に変換
    df[u'当座預金残高'] = df[u'当座預金残高'].astype(int)
    # 行を末尾に追加
    df_boj = df_boj.append(df)

# なお、column数が1つだけの場合はyオプションは不要
df_boj.plot()
df_boj

f:id:akiyoko:20170409232752p:plain:w150

f:id:akiyoko:20170409232810p:plain



ここで、2016年の時系列データ(日経平均株価)を読み込んでみます。

# 時系列データ(日経平均株価)を読み込む
df_n225 = pd.read_csv('n225.csv', index_col='Date', parse_dates=True, usecols=['Date', 'Adj Close'])
df_n225 = df_n225.sort_index()
df_n225 = df_n225.rename(columns={'Adj Close': u'日経平均株価(終値)'})
df_n225.plot()
df_n225

f:id:akiyoko:20170409232828p:plain:w180

f:id:akiyoko:20170409232857p:plain



単純に、二つのグラフを同じ図上に表示してみます。
日経平均株価の値が相対的に小さすぎて、地を這うようなグラフになってしまいました。

ax = df_boj.plot()
df_n225.plot(ax=ax)

f:id:akiyoko:20170409232940p:plain



見やすいように倍率を調整してみます。

adj = df_boj[u'当座預金残高'].mean() / df_n225[u'日経平均株価(終値)'].mean()
ax = df_boj.plot()
(df_n225 * adj).plot(ax=ax)

f:id:akiyoko:20170409232953p:plain



最後に、期間を合わせてみます。

start_date = '2016-01-01'
end_date = '2016-12-31'
span = pd.date_range(start_date, end_date)
df_2016 = pd.DataFrame(index=span)
df_2016 = df_2016.join(df_boj[start_date:end_date])
df_2016 = df_2016.join(df_n225[start_date:end_date] * adj)
df_2016.plot()
df_2016

f:id:akiyoko:20170409233012p:plain:w250

f:id:akiyoko:20170409233028p:plain




 

参考

Pandas について本格的に勉強するならこちらの本を。


Jypyter Notebook の勉強をするならこの本を。

pandas.DataFrame の列の抽出(射影)および行の抽出(選択)方法まとめ

前回、「Pandas の DataFrame の基本的な使い方 - akiyoko blog」と題して pandas.DataFrame の基本的な使い方のまとめをしましたが、今回は、pandas.DataFrame の操作の中でも一番よく使うであろう「列の抽出(射影)方法」および「行の抽出(選択)方法」についてのまとめをしたいと思います。


先に結論を書くと、
列の抽出(射影)方法には以下の5パターンがあります。

行の抽出(選択)方法には以下の4パターンがあります。

行と列を同時に指定して抽出する方法としては以下の3パターンがあります。


 

射影と選択

まず、列の抽出は「射影」、行の抽出は「選択」と呼ばれます。
概念図は以下のようになります。

f:id:akiyoko:20141107171430p:plain

前提条件

ローカルの実行環境は以下の通りです。

  • MacOS Sierra 10.12.3
  • Python 2.7.12 (Anaconda 4.2.0)
  • Jupyter Notebook 4.2.0
  • pandas 0.18.1

 

事前準備

まずは事前準備です。
前回 同様、日経平均のデータ(n225.csv)のサンプルデータを読み込んでおきます。

import pandas as pd

# 10行だけ表示するおまじない
pd.options.display.max_rows = 10

# サンプルデータを読み込む
df = pd.read_csv('n225.csv', index_col='Date', parse_dates=True, usecols=['Date', 'Open', 'Volume', 'Adj Close'])
df = df.sort_index()
df

f:id:akiyoko:20170403070357p:plain:w250



 

1.列の抽出(射影)

1−1)['カラム名']

列へアクセスする場合の基本形。
戻り値として pandas.Series が取得できます。

# 列へアクセスするための基本形
df['Open']

# この形であればスペースを含むカラム名もOK
# df['Adj Close']
Date
2016-01-04    18818.580078
2016-01-05    18398.759766
2016-01-06    18410.570312
2016-01-07    18139.769531
2016-01-08    17562.230469
                  ...     
2016-12-26    19394.410156
2016-12-27    19353.429688
2016-12-28    19392.109375
2016-12-29    19301.039062
2016-12-30    18997.679688
Name: Open, dtype: float64


 

1−2).カラム名

列にアクセスする場合に「df.column_A」とシンプルに書くこともできますが、このパターンではスペースを含むカラム名が扱えないのであまり使われることはないと思います。

# 最もシンプルなのは .を使うパターンだが、スペースを含むカラム名は扱えない
df.Adj Close

# 以下のように '' や "" で囲んでも SyntaxError になるのでNG
df.'Adj Close'
SyntaxError: invalid syntax


 

1−3)[['カラム名', 'カラム名']]

複数カラムを指定する場合はこのように書くことができます。
この場合は pandas.DataFrame が得られます。

df[['Open', 'Adj Close']]

f:id:akiyoko:20170403070458p:plain:w200


 

1−4)[[カラム番号]]

カラム番号で指定する場合はこう書きます。
この場合も pandas.DataFrame が得られます。

df[[0]]

f:id:akiyoko:20170403071159p:plain:w150


 

1−5)[[カラム番号, カラム番号]]

カラム番号を複数指定する場合は、カンマで列挙します。

df[[0, 2]]

f:id:akiyoko:20170403071314p:plain:w200


 

2.行の抽出(選択)

2−1)head() / tail()

一番シンプルなのは、head() や tail() を使うパターンでしょうか。
head() は先頭から、tail() は行末から任意の数だけ行を取得できます。

# 先頭から10行目までを抽出
df.head(10)

f:id:akiyoko:20170403071552p:plain:w250


引数を指定しないと 5行分取得できます。

# 行末から5行を抽出
df.tail()

f:id:akiyoko:20170403071608p:plain:w250


 

2−2) [indexの始点:indexの終点]

index の始点と終点で行をスライスするには、このパターンを使います。

まずは、行番号で指定する場合。

# 2行目から3行目までを抽出
df[1:3]

f:id:akiyoko:20170403071840p:plain:w250


次に、index のラベル名で指定する場合。

# DatetimeIndex の場合は '20160107' のように文字列を指定してもいい感じに変換してくれるっぽい
df['20160107':'20160114']

# なお、以下のように指定すると列の抽出になるので KeyError となる
# df['20160107']

f:id:akiyoko:20170403071932p:plain:w250


ちなみに、DatetimeIndex の場合は、存在しない日付を指定しても KeyError になりません。

df['20160101':'20160110']

f:id:akiyoko:20170403071932p:plain:w250


起点と終点を index のラベル名で指定するパターンは、この例の方が分かりやすいかもしれません。

df2 = pd.DataFrame([[1, 11, 111], [2, 22, 222], [3, 33, 333], [4, 44, 444]],
                   index=['one', 'two', 'three', 'four'],
                   columns=['A', 'B', 'C'])
df2['two':'four']

f:id:akiyoko:20170403215756p:plain:w100


 

2−3)[条件]

行の抽出の基本形はこのパターンです。
条件の部分には boolean vector で指定します。

df[df['Adj Close'] > 19400]

f:id:akiyoko:20170403072648p:plain:w250


index の条件を指定する場合はこのように書けます。

df[df.index < '20160110']

f:id:akiyoko:20170403072723p:plain:w250


panads.date_range() で作成した DatetimeIndex の範囲内の行を絞り込む場合は、DatetimeIndex.isin() を使ってこう書くことができます。

# 以下は df['20160101':'20160110'] と同じ結果になる
span = pd.date_range('2016-01-01', periods=10)
df[df.index.isin(span)]

f:id:akiyoko:20170403072723p:plain:w250

(参考)pandas.DatetimeIndex.isin — pandas 0.19.2 documentation




複数AND条件の場合はこう書くことができます。

(参考)Indexing and Selecting Data — pandas 0.19.2 documentation

df[(df.index.month == 3) & (df.index.day < 10)]

f:id:akiyoko:20170403072829p:plain:w250


複数OR条件の場合はこう書くことができます。

df[(df.index.is_month_start) | (df.index.is_month_end)]

f:id:akiyoko:20170403072918p:plain:w250

ちなみに、DatetimeIndex の月初・月末判定については以下を参照。
(参考)pandas.DatetimeIndex — pandas 0.19.2 documentation


 

2−4)query('条件')

query() を使っても 2−3)と同様に特定条件の行を抽出できます。

df.query('Volume == 173000')

f:id:akiyoko:20170403072953p:plain:w250


しかしながら、ラベル名にスペースが含まれる場合に SyntaxError が発生してしまいます。今のところこの回避索が分からないので、汎用的に使うことができません。

df.query('Adj Close == 173000')
SyntaxError: invalid syntax


 

3.行・列を同時指定して抽出

loc()、iloc()、および ix() は、行と列を同時に指定することができます。

3−1)loc()

loc() はラベル名で指定します。
index名、column名の順で指定します。


まず、単行・単列を抽出する場合。

df.loc['20160107', 'Volume']
163000.0

複数行・複数列を抽出する場合は、「:」を使って範囲指定します。

df.loc['20160104':'20160107', 'Volume':'Adj Close']

f:id:akiyoko:20170403073138p:plain:w200


列の指定を省略することも可能です。その場合は、単に行の抽出ということになります。

df.loc['20160107':'20160114']

# 以下と等価
# df.loc['20160107':'20160114', :]

f:id:akiyoko:20170403073253p:plain:w250


行の抽出条件を指定したくない場合は、行の指定は省略できないので「:」を指定します。その場合には、単に列の抽出ということになります。

df.loc[:, 'Volume']
Date
2016-01-04    136000
2016-01-05    128300
2016-01-06    142200
2016-01-07    163000
2016-01-08    178800
               ...  
2016-12-26         0
2016-12-27    110200
2016-12-28     77700
2016-12-29         0
2016-12-30    117800
Name: Volume, dtype: int64


飛び飛びの index, column を指定したい場合は、それぞれ [] を使います。

df2.loc[['one', 'three'], ['A', 'C']]

# なお、DatetimeIndex の場合は pd.to_datetime で変換する必要があるっぽいので面倒・・
# df.loc[pd.to_datetime(['20160107', '20160114']), ['Open', 'Adj Close']]

f:id:akiyoko:20170403073558p:plain:w100


 

3−2)iloc()

iloc() は数値で指定します。
index番号、column番号の順で指定します。


まず、単行・単列を抽出する場合。

df.iloc[0, 0]
18818.580077999999

複数行・複数列を抽出する場合は、「:」を使って範囲指定します。

df.iloc[1:3, 1:2]

f:id:akiyoko:20170403073723p:plain:w120


列の指定を省略することも可能です。その場合は、単に行の抽出ということになります。

df.iloc[1:3]

# 以下と等価
# df.iloc[1:3, :]

f:id:akiyoko:20170403073859p:plain:w200


行の抽出条件を指定したくない場合は、行の指定は省略できないので「:」を指定します。その場合には、単に列の抽出ということになります。

df.iloc[:, 1:3]

f:id:akiyoko:20170403073924p:plain:w200


飛び飛びの index, column を指定したい場合は、それぞれ [] を使います。

df.iloc[[0, 2], [0, 2]]

f:id:akiyoko:20170403074002p:plain:w200


 

3−3)ix()

ix() なら、ラベル名と数値のいずれでも利用することができます。

df.ix['20160107', 'Volume']
df.ix['20160104':'20160107', 'Volume':'Adj Close']
df.ix['20160107':'20160114']
df.ix['20160107':'20160114', :]
df.ix[:, 'Volume']
df.ix[0, 0]
df.ix[1:3, 1:2]
df.ix[1:3]
df.ix[1:3, :]
df.ix[:, 1:3]

 

Pandas の DataFrame の基本的な使い方

Python でデータ分析をするためのライブラリといえば「Pandas」がデファクトですが、今回は、Pandas の DataFrame の基本的な使い方をまとめてみようと思い立ちました。

特に、DataFrame で時系列データを扱うことを想定しています。具体的には、「金融データのPythonでの扱い方 - 今日も窓辺でプログラム」のように株価データを扱ってみたいと考えています。

 

前提条件

ローカルの実行環境は以下の通りです。

  • MacOS Sierra 10.12.3
  • Python 2.7.12 (Anaconda 4.2.0)
  • Jupyter Notebook 4.2.0
  • pandas 0.18.1


参考までに、MacOS Sierra に(Pandas や Jupyter Notebook などのデータ分析関連の Python パッケージが全部入った)Anaconda を Pyenv を使ってインストールする手順はこちらになります。

<過去記事>
akiyoko.hatenablog.jp



 

pandas.DataFrame の作成方法

pandas.DataFrame の作成方法は、大きく分けて3つあります。


 

1)二次元配列を引数にする

二次元配列の一次元目は縦方向、二次元目は横方向に並びます。

import pandas as pd

df = pd.DataFrame([[1, 11, 111], [2, 22, 222], [3, 33, 333], [4, 44, 444]])
df

横方向のデータを縦に繋いだイメージです。伝わりますかね??

f:id:akiyoko:20170325004558p:plain:w100


DataFrame を縦方向に連結する

DataFrame を縦方向に連結するには、pandas.concat() あるいは pandas.DataFrame.append() を使います。

df = pd.DataFrame([[1, 11, 111]])
df2 = pd.DataFrame([[2, 22, 222]])
df3 = pd.DataFrame([[3, 33, 333]])
df4 = pd.DataFrame([[4, 44, 444]])
df = pd.concat([df, df2, df3, df4])

# append でもOK
# df = df.append(df2).append(df3).append(df4)
df

f:id:akiyoko:20170325005953p:plain:w100

しかしながら、append は内部的に全体のコピーが毎回走るのでループで一行ずつ追加するような処理は避けた方がよいそうです。

(参考)Python pandas 図でみる データ連結 / 結合処理 - StatsFragments


 

index や columns を指定する

index(縦方向のラベル) や columns(横方向のラベル)を指定することが可能です。

df = pd.DataFrame([[1, 11, 111], [2, 22, 222], [3, 33, 333], [4, 44, 444]], index=['one', 'two', 'three', 'four'], columns=['A', 'B', 'C'])
df

f:id:akiyoko:20170325010057p:plain:w120


時系列データを扱う

時系列っぽいデータを作成してみます。

df = pd.DataFrame([[18818.580078, 136000, 18450.980469],
                   [18398.759766, 128300, 18374.00],
                   [18410.570312, 142200, 18191.320312],
                   [18139.769531, 163000, 17767.339844]],
                  index=pd.to_datetime(['2016-01-04', '2016-01-05', '2016-01-06', '2016-01-07']),
                  columns=['Open', 'Volume', 'Adj Close'])
df

f:id:akiyoko:20170325010155p:plain:w300


2)辞書を引数にする

辞書を引数にして pandas.DataFrame を作成することもできます。
なお辞書の値は、リストまたは pandas.Series を指定します。

df = pd.DataFrame({'A': [1, 11, 111],
                   'B': pd.Series([2, 22, 222]),
                   'C': pd.Series({0: 3, 1: 33, 2: 333})})
df

1)とは逆に、縦方向のデータを横に繋いだイメージになります。

f:id:akiyoko:20170325010246p:plain:w120

columns の値が辞書ごとに指定できるので便利ですが、時系列データを扱うことを考えると、カラムごとの値のリストをそれぞれ用意する必要があるのでちょっと使いづらいですかね。。



ちなみに、リストの要素数が他と合わないと ValueError が発生してしまいます。

df = pd.DataFrame({'A': [1, 11],
                   'B': pd.Series([2, 22, 222]),
                   'C': pd.Series({0: 3, 1: 33, 2: 333})})
ValueError: array length 2 does not match index length 3


なお Series の場合は、足りない要素が「NaN」で補完されるので便利です。

df = pd.DataFrame({'A': [1, 11, 111],
                   'B': pd.Series([2, 22]),
                   'C': pd.Series({0: 3, 1: 33, 2: 333})})
df

f:id:akiyoko:20170325110441p:plain:w120

 

DataFrame を横方向に連結する

なお、DataFrameを横方向に結合するには、pandas.concat() あるいは pandas.DataFrame.join() を使います。

df = pd.DataFrame({'A': [1, 11, 111]})
df2 = pd.DataFrame({'B': [2, 22, 222]})
# 横方向に結合するときは axis=1 を指定
df = pd.concat([df, df2], axis=1)
# あるいは、index が同じであれば、index をキーにして結合する DataFrame.join を使ってもよい
# df = df.join(df2)
df

f:id:akiyoko:20170325010345p:plain:w80


 

時系列データを扱う

2)の作成方法では時系列データを扱いづらいのですが、もしやるなら一旦以下のように DataFrame を作成して、作成した後に DataFrame.T で縦横を反転させる方がいいのかな。

df = pd.DataFrame({'2016-01-04': [18818.580078, 136000, 18450.980469],
                   '2016-01-05': [18398.759766, 128300, 18374.00],
                   '2016-01-06': [18410.570312, 142200, 18191.320312],
                   '2016-01-07': [18139.769531, 163000, 17767.339844]}, index=['Open', 'Volume', 'Adj Close'])
df = df.T
df

f:id:akiyoko:20170325010408p:plain:w300


うーん。やっぱり使いづらいですね。。



 

3)pandas.read_csv を使う

ここからが本題。

pandas.read_csv() を使えば、CSVファイルから簡単に DataFrame を作成することができます。

日経平均のデータ(n225.csv)を読み込んでみます。データは Nikkei 225 Stock - Yahoo Finance からダウンロードしました。

df = pd.read_csv('n225.csv', index_col='Date', parse_dates=True, usecols=['Date', 'Open', 'Volume', 'Adj Close'])
df = df.sort_index()
df.head(10)

f:id:akiyoko:20170326012255p:plain:w300


金融データのPythonでの扱い方 - 今日も窓辺でプログラム」では、以下のように DataFrame を作成していました。

# 2016年1年分の DataFrame を用意
df = pd.DataFrame(index=pd.date_range('2016-01-01', '2016-12-31'))

# 日経平均のデータを読み込んで join
df = df.join(pd.read_csv('n225.csv', index_col='Date', parse_dates=True, usecols=['Date', 'Open', 'Volume', 'Adj Close']))

# 市場が休みの日のデータを取り除く
df = df.dropna()
df.head(10)

f:id:akiyoko:20170326012353p:plain:w300


最初のやり方と結果が変わらないような気がするのですが、まあそれは追々考えることにしましょう。



 

余談:Iris のデータを Pandas で使う方法

統計分析の練習用サンプルデータと言ったらやっぱり Iris でしょ、ということでデータをロードしようと思ったのですが、pandas には標準で入っていないようです。

Anaconda には標準で scikit-learn が入っているので、そちらを使えば簡単にロードできるようです。
(参考)irisのデータセットをpandasで使う - DISTRICT 37

import pandas as pd
from sklearn import datasets

iris = datasets.load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['Species'] = iris.target_names[iris.target]
df.rename(columns={'sepal length (cm)': 'Sepal.Length',
                   'sepal width (cm)': 'Sepal.Width',
                   'petal length (cm)': 'Petal.Length',
                   'petal width (cm)': 'Petal.Width'},
          inplace=True)
df.head()

f:id:akiyoko:20170331230004p:plain:w450




なお、rpy2(R interface)パッケージを使ってもいいようですが、rpy2 は Anaconda 4.2.0 にはデフォルトで入っていなかったので見送りました。

(参考)Sample Datasets in Pandas - Stack Overflow

import pandas.rpy.common as rcom
iris = rcom.load_data('iris')
ImportError: No module named rpy2.robjects.packages

 

まとめ

pandas.DataFrame で時系列データを作成するには、pandas.read_csv を使うのが圧倒的に便利です。もちろん、対象データが CSV形式で保存されているなら、という条件付きですが。

pandas.read_csv には把握するのが大変なほど多種多様なオプション引数が用意されており、使いこなせれば強力な武器になりそうです。

f:id:akiyoko:20170325121734p:plain

pandas.read_csv — pandas 0.19.2 documentation より)


なお今回のソースコードは、GitHub のリポジトリにアップしておきました。

github.com




 

「実践!機械学習 - Web系企業 CTO が実例を公開」に参加してきました

主催

Forkwell Jobs

会場

株式会社フロムスクラッチ
東京都新宿区西新宿7丁目20番1号(住友不動産西新宿ビル17階)


 

全体の感想など

最近よくある機械学習の基礎知識系の勉強会ではなく、機械学習をすでにビジネスに導入・応用して実際に運用している企業のCTOが集結して、その知見を披露してもらえるというイベントでした。

とは言え、私も機械学習に詳しいわけではなく、最近ようやく、

www.udemy.com

を修了したばかりの初心者なので全く付いていけないところも何度かありましたが、とりあえずキーワードをメモしておいて、少し勉強してからまた見直してみたいと思います。




マーケティングプラットフォームにおけるレコメンドサービス

井戸端 洋彰 氏(株式会社フロムスクラッチ)


広告データや広告ページのアクセスログおよびビジネスデータを収集・統合して分析・活用するためのプラットフォーム「B→Dash」の運用実績が二年半ほどあるとのことです。

中でもデータの統合が一番大変で、機械学習については、どのアルゴリズムを使うかよりも、マーケティングの設計の方が大事。結果を分析して得られた「示唆」を抽出し、次の施策へ活かすプロセスが重要とのことでした。


<関連サイト>
seleck.cc



 

トラッキングデータを使った機械学習活用

柴山 直樹 氏(株式会社プレイド)


サイト訪問者行動解析プラットフォーム「KARTE(カルテ)」に、「強化学習による配信最適化」や「回帰/2クラス分類によるユーザモデリング」という形で機械学習を導入しているとのこと。

導入ポリシーとしては、「人間の発想を活かす」「機械学習はそれを補助するように使う」ということで、機械学習が導き出す答えが定型化して退屈なものになってしまわないように気を付けているとのことでした。アルゴリズムよりもサービスの本質に目を向けないとダメで、事業のコアにするのは危険?(マーケティングの手段としてはアリ)という話も。また、世間の AIへの期待が高すぎるということを危惧されているようでした。


<キーワード>



<関連?スライド>

 

パネルディスカッション

  • 機械学習導入の難易度は、それぞれのドメインにおけるデータの集めやすさやデータの質に依る
    • 前処理が大変なので、データが汚いとそれだけ大変になる
  • GV(旧 Google Ventures)や Google が提唱している「デザインスプリント」の本を元に、組織でビジネスアイデアを検討している

  • 今機械学習ができるのは結局、分類と回帰のみ。近い将来、汎用AIを作るフェーズになっていく

ゼロからはじめる Amazon QuickSight(AWS でお手軽データ分析 その3/3)

前々回の記事 および 前回の記事 で、Scrapy で Webスクレイピングしたデータを CSVファイルとして S3 に格納し、Amazon Athena のテーブルを作成して CSV のデータを流し込むところまでを実施しました。

今回は、作成した Amazon Athena のテーブルをデータソースとして Amazon QuickSight のデータ分析用のストアに取り込み、ダッシュボードからグラフを作成してみたいと思います。


<過去記事>
akiyoko.hatenablog.jp

akiyoko.hatenablog.jp


 

Amazon QuickSight とは

Amazon QuickSight については、昨年末の AWS Black Belt Online Seminar の資料に詳しく説明がされています。


要約すると、Amazon QuickSight は

  • すぐに利用可能(セットアップ不要)
  • フルマネージド
  • 多様なデータソースが使える
  • 低コスト
  • 結果のグラフはスマホにも最適化

という特徴を持つクラウドベースの BI ツールです。Redshift、RDS、Aurora、Athena、S3 などを始めとする AWS 上のデータセットをデータソースとして取り込んで、ダッシュボード上で様々なグラフ(*1)でデータを可視化することができます。CSVファイルや Excelファイルを QuickSight に直接アップロードしたり、オンプレミスサーバ上のデータベースをデータソースとして利用することもできるようですが、AWS のプロダクトと連携して利用するのがメインのユースケースとなるでしょう。

なお現時点では、N. Virginia、Oregon、Ireland の 3リージョンでしか利用できないようです。

「SPICE」というキーワードがちょくちょく出てきますが、独自に開発したインメモリデータストア用の超高速エンジンで、「Super-fast, Parallel, In-Memory Calculation Engine」の略だそうです。使用料は、その SPICEの容量(最初の1GBは無料)とユーザ数(最初の1ユーザは無料)に対して課金されます。お試しで使うのであれば、無料の範囲で十分使えそうです。

(参考)Amazon QuickSight | Editions


概要レベルの資料では、
Amazon QuickSight:【概要】Amazon QuickSightとは何か? | Developers.IO
も有用です。

あと、全然読み切れてませんが、クラスメソッドの しんや 氏の「Amazon QuickSight (全部俺) Advent Calendar 2016 - Qiita」なんていう資料もありました。猛者です。




 

目的

「AWS でお手軽データ分析」の全体像は、下図のように Scrapy → Amazon S3 → Amazon Athena → Amazon QuickSight という流れで AWS のいろいろなサービスを使ってデータ分析をすることを想定していますが、最終回である今回は、作成した Amazon Athena のテーブルを Amazon QuickSight のデータ分析用のストアに読み込んで可視化するところを試してみます。

f:id:akiyoko:20170311224916p:plain



 

QuickSight を使ってみる

QuickSight アカウントの作成

AWS Management Consle のアカウントとは別に、QuickSight アカウントを作成する必要があります。

QuickSight のアカウントについては「Amazon QuickSight: ユーザー管理やパーミッション設定について | Developers.IO」が詳しいです。


AWS Management Console にログインし、サービス一覧から「QuickSight」を選択します。
f:id:akiyoko:20170312115142p:plain

https://quicksight.aws.amazon.com/
にリダイレクトされますが、まだサインアップしていないので入れません。
ここでログアウトせずに続けて「Sign up」することで、現在 AWS Management Console にログインしているアカウントを QuickSight アカウントとして紐付けることができるようです。
f:id:akiyoko:20170312115205p:plain

「Standard edition」を選択します。最初の1ユーザーは無料です。
f:id:akiyoko:20170312115223p:plain

以下の内容を設定します。

QuickSight account name 任意(全体でユニークになるように)
Notification email address 任意
QuickSight capacity region US East (N.Virginia)


またここで、QuickSight からアクセスするリソースに対してパーミッションを与える必要があります。 *2

今回の例では、

  • Amazon Athena
  • Amazon S3(Athena のテーブルが参照している S3 バケット)

を許可する必要があります。

ということで、「Choose S3 buckets」をクリックします。
f:id:akiyoko:20170312115242p:plain

Athena のテーブルが参照している S3 バケット(今回の例では「marketstat」)を指定します。
f:id:akiyoko:20170312115306p:plain

アカウント作成を完了します。
f:id:akiyoko:20170312115324p:plain

f:id:akiyoko:20170312115347p:plain

グラフ(Visual)の作成

QuickSight のトップ画面はこんな感じです。

「New Analysis」をクリック。
f:id:akiyoko:20170312115409p:plain

「New data set」をクリックします。
f:id:akiyoko:20170312115429p:plain

取り込むデータセットとして、「Athena」を選択します。
f:id:akiyoko:20170312115505p:plain

データソース名を任意に設定します。
f:id:akiyoko:20170312115540p:plain

データを取り込む対象となる Athena のデータベースとテーブルを選択します。
f:id:akiyoko:20170312115611p:plain

インポート先に「SPICE」を選択して、「Visualize」をクリックします。
f:id:akiyoko:20170312115647p:plain

データの取り込みが完了すると、「Import complete」と表示されます。
f:id:akiyoko:20170312115748p:plain

ここから、グラフを作成していきます。
まず、グラフのタイプを選んで、X軸とY軸を選択します。
f:id:akiyoko:20170312115822p:plain

X軸をソートするには、以下のように操作します。
f:id:akiyoko:20170312115859p:plain

次に、フィルタ(データの抽出条件)を設定します。
f:id:akiyoko:20170312115934p:plain

左上の「Add」をクリックすると、グラフを「Visual」として保存することができます。
f:id:akiyoko:20170312120048p:plain


また、複数のグラフをダッシュボードとして保存して他のユーザと共有することができるほか、複数のグラフをプレゼン資料のようにまとめたものを「Story」として保存することもできます。



  

まとめ

今回、Scrapy → Amazon S3 → Amazon Athena → Amazon QuickSight という流れで AWS のいろいろなサービスを使って Webスクレイピングしたデータを可視化して分析するまでを、「AWS でお手軽データ分析」と称して三回に分けて記事にまとめてみました。

f:id:akiyoko:20170311224916p:plain


途中何度か軽く躓きかけたものの、全体的にはすんなりとクラウドベースでデータ分析する入り口までたどり着くことができました。
今後は、スクレイピングしたデータやダウンロードした公式のデータを使って、いろいろな分析を実際にしていきたいと思います。

*1:利用できるグラフの種類については、「利用可能な表示形式一覧 #quicksight #01 | Amazon QuickSight Advent Calendar 2016 | Developers.IO」を参照

*2:ここでパーミッションを設定しなくても、後で[Manage QuickSight] > [Account settings]から設定を変更することもできます。

ゼロからはじめる Amazon Athena(AWS でお手軽データ分析 その2/3)

前回の記事 で、Scrapy で Webスクレイピングしたデータを CSV形式で S3 に格納しました。

今回は、S3 に格納した CSVファイルに対して、Amazon Athena を使ってデータ分析用のテーブルに取り込みたいと思います。


<過去記事>
akiyoko.hatenablog.jp



Amazon Athena とは

Amazon Athena については、先日開催された AWS Black Belt Online Seminar の「Amazon Athena」回の資料が詳しいです。


Athena は OLAP(OnLine Analytical Processing)ツールとして簡単な分析向けには使えるが、ETL(Extract, Transform, Load)用途としては向かないとのこと。ただし、大規模でないデータに対して、低頻度で ETL 処理をするユースケースには使えるとのことです。

  • 高速な分散クエリエンジン「Presto」を使用
    • インメモリで処理するので、バッチ処理ではなくてインタラクティブ向け
  • データをフルスキャン&変換する用途で使うと高コストになるので、バッチ処理には向かない
    • 大規模データに対してフルスキャンを定期的に行いたいなら、EMR を使う
    • サブクエリや JOIN を駆使した複雑な主計や高頻度なレポーティングをしたいなら、Redshift を使う

アクセス手段は Amazon Management Console、JDBLドライバ経由のみ。API や CLI は未提供とのことです。JDBC 経由で直接クエリを投げられるので、自前の BIツールとかも使えるようですね。

料金はクエリ単位の従量課金で、S3 のデータスキャンに対して「$5/1TB」で課金されます。注意点としては、

  • リージョンをまたぐ場合は、データ転送時間と転送料金が掛かる
  • パーティションをうまく設定するとスキャンするデータ量を効果的に減らすことができる

などが挙げられていました。

なお、Athena はディレクトリを指定して検索するので、バケット直下に直接ファイルを置くのは間違いとのことです。

 

目的

「AWS でお手軽データ分析」の全体像は、下図のように Scrapy → Amazon S3 → Amazon Athena → Amazon QuickSight という流れで AWS のいろいろなサービスを使ってデータ分析をすることを想定していますが、今回は、S3 に格納された CSVファイルを Amazon Athena のテーブルに流し込むところまでを試してみます。

f:id:akiyoko:20170311224916p:plain



取り込むデータの考慮点

Amazon Athena は CSV、JSON、ORC、Avro、Parquet などの形式のデータを取り込むことができます。

(参考)よくある質問 - Amazon Athena | AWS


今回は、S3上の CSVデータを取り込むことを想定していますが、その際に考慮しなければいけない点がいくつかありました。

CSVデータの内容

1)カンマを取り除く

CSVデータの値として、例えば「18,900」のようにデータの途中に半角カンマ(,)が存在する場合は、最初に出現したカンマの位置までで切られてしまい、値が「18」と認識されてしまうので、データの値から半角カンマ(,)を取り除いておく必要があります。

(参考)Remove comma from value · akiyoko/marketstat@82abc4b · GitHub


2)ヘッダ行

回避策があるかもしれませんが、Athena で を取り込んだ際に、CSV のヘッダ行も1データとして取り込まれてしまうという問題があります。
今のところ解決策が無いので、次の(QuickSight での)分析フェーズでデータをフィルタリングして除去するようにしています。

パーティション

Athena を利用する際は、「パーティション」という概念が非常に重要になります。

(参考)Amazon Athenaのパーティションを理解する #reinvent | Developers.IO


例えば、S3 に保存する際のディレクトリを以下のような形式にすることで、Athena でデータを取り込む際に、「year」「month」というパーティションが使えるようになります。

marketstat(バケット)
└── boj
    ├── year=2014
    ├── year=2015
    ├── year=2016
    └── year=2017
        ├── month=01
        ├── month=02
        └── month=03
            ├── 170301.csv
            ├── 170302.csv
            ├── 170303.csv
            ・
            ・


Amazon Athena はスキャンしたデータ量に対して課金されるため、頻繁に絞り込むカラムをパーティションのキーに指定することで、読み込むデータ量を効果的に減らすことができます。パーティションに設定した項目は、SQL の WHERE句の条件に指定して絞り込むことができます。





 

テーブルを作成

いよいよ Amazon Athena の実践です。

AWS Management Console のサービス一覧から「Amazon Athena」を選択します。
f:id:akiyoko:20170311165842p:plain

現在のところ、Amaozn Athena は「N. Virginia」「Ohio」「Oregon」の3つのリージョンしか対応していません。 *1

今回は「N. Virginia」を選択します。
f:id:akiyoko:20170311165926p:plain


f:id:akiyoko:20170311170013p:plain


「Query Editor」で「Add table」をクリックして、ウィザードを使ってテーブルを作成していきます。

f:id:akiyoko:20170311170030p:plain

項目 設定値
Database: marketstat(任意)
Table Name: boj(任意)
Location of Input Data Set: s3://marketstat/boj/

なお、S3 のロケーションは、スラッシュ(/)で終了する文字列でなければいけません。

f:id:akiyoko:20170311170127p:plain


データフォーマットに「CSV」を選択します。
f:id:akiyoko:20170311170203p:plain


CSV のカラム名とタイプを一つ一つ入力していくのは面倒なので、「Bulk add columns」で一括設定します。
f:id:akiyoko:20170311170321p:plain

今回は、

date int, expected_value int, preliminary_value int, name string, confirmed_value int

と設定します。

注意点としては、CSVファイルに出力された順番にカラム名を書かないといけません。なおパーティションに含めるフィールドは、ここには含めなくてよいです。
f:id:akiyoko:20170311195617p:plain

f:id:akiyoko:20170311170441p:plain


最後にパーティションを設定します。
f:id:akiyoko:20170311170617p:plain

パーティションは、

カラム名 タイプ
year int
month int

としました。タイプを「int」形式にしないと WHERE 句で「 > 」などの演算子が使えないため、上記のように設定しました。
f:id:akiyoko:20170311170657p:plain


エラーが出なければ、テーブル作成は完了です。

ちなみに、今回作成したテーブルの DDL は以下のようになっています。

CREATE EXTERNAL TABLE IF NOT EXISTS marketstat.boj (
  `date` int,
  `expected_value` int,
  `preliminary_value` int,
  `name` string,
  `confirmed_value` int 
) PARTITIONED BY (
  year int,
  month int 
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = ',',
  'field.delim' = ','
) LOCATION 's3://marketstat/boj/';



パーティションを使用した場合は、最後に

MSCK REPAIR TABLE marketstat.boj;

を忘れずに実行しなければいけません。 非常に重要です!!!

f:id:akiyoko:20170311170758p:plain



 

テスト

Amazon Athena は内部で分散処理基盤「Presto」を使用していて、標準の ANSI SQL が利用できます。

以下のSQLを実行してみます。

SELECT * FROM boj WHERE year = 2016 AND month BETWEEN 1 AND 3 limit 100;

f:id:akiyoko:20170311200800p:plain

WHERE句でパーティションを指定しているのがポイントです。これにより、スキャンする容量を節約することができます。


例えば、全検索するとスキャン量が「1MB」だったのが・・

f:id:akiyoko:20170314204257p:plain

WHERE句の条件として「year = 2016」を指定すると、「360 KB」ほどにスキャン量が削減されているのが分かります。

f:id:akiyoko:20170314204625p:plain



結果一覧の右上のファイルアイコンをクリックすることで、結果のCSVをダウンロードすることもできます。

f:id:akiyoko:20170311200817p:plain

"date","expected_value","preliminary_value","name","confirmed_value","year","month"
,,,"name",,"2016","2"
"160210",,,"",,"2016","2"
"160210","-400","-400","銀行券要因",,"2016","2"
"160210","-28800","-29900","財政等要因",,"2016","2"
"160210","-29200","-30300","資金過不足",,"2016","2"
"160210",,," ",,"2016","2"
"160210",,," ",,"2016","2"
    ・
    ・


 

まとめ

知識ゼロから Amazon Athena を使って、S3 に格納した CSVファイルを元にデータ分析用のテーブルを作成してみました。

少し難解だったのはパーティションの概念でしたが、それが理解できればあとは直感で何とかなりそうな感じでした。

次は、QuickSight を使って実際にデータ分析していきたいと考えています。