今日も窓辺でプログラム

外資系企業勤めのエンジニアが勉強した内容をまとめておくブログ

pandasで使われるデータ構造 ~1次元、2次元、3次元のデータの扱い方~

この記事について

今までなんとなくで使用していたpandasですが、なんとなくで実装していたコード(日経平均予想のやつです)が汚くメンテナンスが難しくなってきたので、一度ちゃんとpandasでのデータの扱い方をまとめておこうと思って書いたものです。

pandasの公式ドキュメントに Intro to Data Structures というものがあり、基本的にはそこで説明されていることの一部をピックアップして紹介している記事になります。
http://pandas.pydata.org/pandas-docs/stable/dsintro.html

Series: 1次元のデータ

Seriesの作り方

Seriesは1次元のラベル付き配列です。要素はどんな型のデータでも入れることができます。
例えば、日経平均(N225)、S&P500(GSPC)、DAX(GDAXI)という3つの株価指数の10月7日の終値は次のようなものでした。

指数 10/7の終値
N225 16860.089844
GSPC 2153.73999
GDAXI 10490.860352

このデータをSeriesで1次元の配列のように扱うには、次のようにSeriesを生成します。

# 10月7日の終値のSeriesを作る

# 配列から
s = pd.Series([16860.089844, 2153.73999, 10490.860352], index=['N225', 'GSPC', 'GDAXI'])

# 辞書から
s = pd.Series({'N225':16860.089844 , 'GSPC':2153.73999 , 'GDAXI':10490.860352 })

どちらもまったく同じSeriesが作られており、Pythonのprint()関数かJupyter Notebookで中身を確認するとこんな感じのデータになってます。

GDAXI    10490.860352
GSPC      2153.739990
N225     16860.089844
dtype: float64

Seriesはnumpyのndarrayに似ている

Seriesはnumpyのndarrayに似た扱いが可能です。
このように添え字でアクセスできます。Pythonの配列のようなスライスも使えます。

s[0]
10490.860352

こんなことをすると、中央値より大きな値を取り出す、なんてことも簡単にできます。

s[s > s.median()]
N225    16860.089844
dtype: float64

numpyの関数はそのまま使えます。例えば対数や指数を取りたいときも、こんな感じで一発です。

np.log(s)
GDAXI    9.258260
GSPC     7.674961
N225     9.732705
dtype: float64

SeriesはPythonの辞書に似ている

Seriesは辞書にも似ています。辞書のように、ラベルを使って各要素にアクセスできます。

s['N225']
16860.089844

辞書と同様に、in でキーの存在確認なんかもできます。

'FTSE' in s
False

ベクトル的な計算もできます

ベクトル同士の足し算や

s + s
GDAXI    20981.720704
GSPC      4307.479980
N225     33720.179688
dtype: float64

スカラー値との掛け算も。

s * 3
GDAXI    31472.581056
GSPC      6461.219970
N225     50580.269532
dtype: float64

Name属性

Serieseには名前を付けることもできます。このような形で name という引数を渡してあげます。

s = pd.Series({'N225':16860.089844 , 'GSPC':2153.73999 , 'GDAXI':10490.860352 }, name='close')

すると、次のようなSeriesが定義されます。

GDAXI    10490.860352
GSPC      2153.739990
N225     16860.089844
Name: close, dtype: float64

このように、最初に定義した名前にアクセスすることもできます。

s.name
'close'

名前を変えたいときはrenameメソッドを使います。

s.rename('closing_value')

DataFrame: 2次元のデータ

DataFrameの作り方

2次元のデータを扱うには、DataFrameを使用します。イメージとしてはエクセルのシートのようなものが扱えるイメージです。
これは非常に有名なデータ構造なので、pandasを使っているブログ記事などで頻繁に目にします。

DataFrameでは行のラベル(index)と列のラベル(columns)を設定することができます。
10月7日の終値に加えて始値の情報も持ったDataFrameの例が次の画像になります。

f:id:kanohk:20161013231126p:plain

左側の縦に並んでいるラベル(=行ラベル)がindex、上側の横に並んでいるラベル(=列ラベル)がcolumnsで設定できるラベルです。

このようなDataFrameを定義する方法は山ほどあるのですが、ここでは2つだけ紹介します。どちらの方法で定義しても上に示したDataFrameが作られます。

# Seriesの辞書から
df = pd.DataFrame({
        'open': pd.Series([16883.119141, 2164.189941, 10549.69043], index=['N225', 'GSPC', 'GDAXI']),
        'close': pd.Series([16860.089844, 2153.73999, 10490.860352], index=['N225', 'GSPC', 'GDAXI'])
    })

# 配列の辞書から
df = pd.DataFrame({
        'open': [16883.119141, 2164.189941, 10549.69043],
        'close': [16860.089844, 2153.73999, 10490.860352]
    }, index=['N225', 'GSPC', 'GDAXI'])

実際にはCSVなどのデータを読み込んでDataFrameとして扱うことが多いかもしれません。
その場合はread_csvという関数を使用します。
まずはopenclose.csvという名前の次のようなCSVがあるとします。

name,close,open
N225,16860.089844,16883.119141
GSPC,2153.739990,2164.189941
GDAXI,10490.860352,10549.690430

このとき、この1行でCSVを読み込んでDataFrameに変換できます。

df = pd.read_csv('./openclose.csv', index_col='name', header=0)

index_colで行ラベルとして使う列を、headerで列ラベルとして使う行番号をそれぞれ指定しています。headerはデフォルトで0となるので、省略しても構いません。

列の選択、追加、削除

特定の列だけを処理したい場合は、次のように抜き出します。

df['close']
name
N225     16860.089844
GSPC      2153.739990
GDAXI    10490.860352
Name: close, dtype: float64

列の名前がPythonの変数名として有効なものであれば、メンバ変数のようにアクセスすることもできます。

df.close


列の追加もできます。たとえば、10/7の値動きdelta(=終値 - 始値)の列を追加したい場合は、次のようにして追加できます。

df['delta'] = df['close'] - df['open']

f:id:kanohk:20161014005416p:plain

私個人はあまり使ったことはないですが、列の削除はpopでできるみたいです。

df.pop('delta')

assign関数で新しい列を追加する

先ほどの値動きの列deltaは、assignという関数を使っても追加することができます。

df.assign(delta=df['close']-df['open'])

df.assign()は戻り値に列の追加された新しいDataFrameを返しますが、元のdfには変更を加えません。

このassign関数の優れているところは、関数も渡せるところです。たとえば、

df.assign(updown=lambda x:  ["UP" if y > 0 else "DOWN" for y in x['close'] - x['open']])

としてやると、deltaがプラスならUP、マイナスならDOWNという文字列を持つupdownという列を新しく追加できます。

f:id:kanohk:20161014011023p:plain

要素の選択

こちらの表をそのまま引用します。

操作 文法 戻り値
列の選択 df[col] Series
行をラベルで選択 df.loc[label] Series
行を整数値で選択 df.iloc[loc] Series
行のスライス df[5:10] DataFrame
行をboolの配列で選択 df[bool_vec] DataFrame

下記の状態のDataFrameを例にして、一つずつ見ていきます。
f:id:kanohk:20161014005416p:plain

列の選択

始値(open)の列を取り出してみます。

df['open']
name
N225     16883.119141
GSPC      2164.189941
GDAXI    10549.690430
Name: open, dtype: float64

確かに戻り値はSeriesになっています。

行をラベルで選択

日経平均(=N225)の行だけを取り出してみます。行を選択するのに使うlocはlabel-locationに由来しているようです。

df.loc['N225']
close    16860.089844
open     16883.119141
delta      -23.029297
Name: N225, dtype: float64

こちらも結果がSeriesで得られました。

行を整数値で選択

次は0スタートで1番目の株価指数を取り出します。使用するilocは、integer-locationの略みたいです。

df.iloc[1]
close    2153.739990
open     2164.189941
delta     -10.449951
Name: GSPC, dtype: float64

こちらもS&P500(=GSPC)のデータがSeriesで取り出せました。

行のスライス

Pythonのリストと同様に、スライスを使って行を取り出すこともできます。例えば

df[1:]

とすると、1番目のGSPC以降の行がDataFrameとして取り出せます。

f:id:kanohk:20161014012701p:plain

行をbool配列で選択

0番目と2番目の行を取り出したい場合は、例えば[True, False, True]という配列を次のように渡してあげます。

df[[True, False, True]]

f:id:kanohk:20161014012915p:plain

コンソールに出力

今まではJupyter Notebookでの出力画面のスクショを紹介していましたが、printやto_string()などの関数を使ってきれいにコンソールに出力することもできます。

print(df)
df.to_string()

print関数での出力結果はこんな形。

              close          open      delta
name                                        
N225   16860.089844  16883.119141 -23.029297
GSPC    2153.739990   2164.189941 -10.449951
GDAXI  10490.860352  10549.690430 -58.830078

Panel: 3次元のデータ

pandasの名前の由来はpanel dataだった!

実はpandasでは3次元を扱うデータ構造も提供しています。それがPanelです。ちなみに、このPanelというデータ構造(= panel data)がpandasの名前の由来にもなっているようです*1

The term panel data is derived from econometrics and is partially responsible for the name pandas: pan(el)-da(ta)-s

http://pandas.pydata.org/pandas-docs/stable/dsintro.html#panel

Panelは3次元のデータを扱うので、軸も3つあり、それぞれitems, major_axis, minor_axisと呼ばれるようです。
itemsという軸がDataFrameを持っており、major_axisとminor_axisがそのDataFrameの行(index)と列(columns)に対応します。

Panelの作り方

今まで使用してきた例だと、10月7日の各株価指標の始値・終値をもったDataFrameを扱ってきました。他の日付の始値・終値も扱いたい、となった場合に、Panelが活躍します。
itemsに日付、major_axisに株式指標、minor_axisに始値/終値をとるPanelを使えばいいわけです*2

# 2日分のDataFrameを用意
df1006 = pd.DataFrame({
        'open': [16913.599609, 2158.219971, 10641.129883],
        'close': [16899.099609, 2160.77002, 10568.799805]
    }, index=['N225', 'GSPC', 'GDAXI'])
df1007 = pd.DataFrame({
        'open': [16883.119141, 2164.189941, 10549.69043],
        'close': [16860.089844, 2153.73999, 10490.860352]
    }, index=['N225', 'GSPC', 'GDAXI'])

# 辞書でDataFrameを渡してPanelを作成
panel = pd.Panel({
        '2016-10-06': df1006,
        '2016-10-07': df1007
    })

panelは3次元なので、printしても簡単に中を見れたりはしません。

<class 'pandas.core.panel.Panel'>
Dimensions: 2 (items) x 3 (major_axis) x 2 (minor_axis)
Items axis: 2016-10-06 to 2016-10-07
Major_axis axis: N225 to GDAXI
Minor_axis axis: close to open

他にも、3次元配列から初期化したりもできます。

要素の操作

Panelの操作はDataFrameによく似ています。まず、このようにitemsを指定してDataFrameを取り出せます。

panel['2016-10-06']

f:id:kanohk:20161014020835p:plain

DataFrameを行列のように見立てて、行列の計算なんかもできます。

panel['delta'] = panel['2016-10-07'] - panel['2016-10-06']

要素の選択

先ほど紹介した、panel[item]という形で取り出したDataFrameは、items軸に基づいて取り出したDataFrameでした。
major_axisやminor_axisに基づいてDataFrameを取り出したい場合は、それぞれ次のようにします。

major_axis

panel.major_xs('N225')

f:id:kanohk:20161014021416p:plain

minor_axis

panel.minor_xs('close')

f:id:kanohk:20161014021512p:plain

欲しいDataFrameが行と列が逆のものの場合は、.Tで転置を取って調節してください。

panel.major_xs('N225').T

f:id:kanohk:20161014021642p:plain

DataFrameに変換

to_frame()関数を使うと、PanelをDataFrameに変換することもできます。

panel.to_frame()

f:id:kanohk:20161014021820p:plain

参考サイト

http://pandas.pydata.org/pandas-docs/stable/dsintro.html


関連記事

pandasでデータを操作する際に便利な環境であるJupyter Notebookについて紹介しています。
www.madopro.net

*1:動物のパンダかと思ってた

*2:itemsに日付が来るのはあまりよくない例かもしれません。本当は日付はmajor_axis置くのがいいのかも。