pandas で 3 次元のデータ構造を扱う

# 3 次元のデータを扱う

[pandas](http://pandas.pydata.org/) におけるデータ構造として主要なものに 1 次元つまり線の [Series](http://pandas.pydata.org/pandas-docs/dev/generated/pandas.Series.html) と 2 次元つまり表形式の [DataFrame](http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.html) があります。これは pandas における主要なオブジェクトであり [Python for Data Analysis](http://shop.oreilly.com/product/0636920023784.do) でも詳しく解説されています。

しかし実はもう一つ主要なオブジェクトがあります。それが [Intro to Data Structures](http://pandas.pydata.org/pandas-docs/stable/dsintro.html) でも 3 つ目に登場する 3 次元の [Panel](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Panel.html) です。

この 3 次元のデータ構造は、たとえば毎日の表データから任意の数値を取り出して時系列のログに関する統計分析をおこないたいといった用途において役立ちます。

## Panel オブジェクトを作る

Panel は辞書形式にした DataFrame または 3 次元の ndarray を引数にとることで生成することができます。具体的にやってみましょう。

```py3
import pandas as pd
rng = pd.date_range('1/1/2014',periods=100,freq='D')

# 乱数でデータフレームを作っていく、インデックスは ABCD とする
df1 = pd.DataFrame(np.random.randn(100, 4), index = rng, columns = ['A','B','C','D'])
df2 = pd.DataFrame(np.random.randn(100, 4), index = rng, columns = ['A','B','C','D'])
df3 = pd.DataFrame(np.random.randn(100, 4), index = rng, columns = ['A','B','C','D'])

# これらデータフレームをまとめて Panel オブジェクトをつくる
pf = pd.Panel({'df1':df1,'df2':df2,'df3':df3})

pf
#=>
# <class 'pandas.core.panel.Panel'>
# Dimensions: 3 (items) x 100 (major_axis) x 4 (minor_axis)
# Items axis: df1 to df3
# Major_axis axis: 2014-01-01 00:00:00 to 2014-04-10 00:00:00
# Minor_axis axis: A to D
```

このように Panel オブジェクトが生成されました。
それぞれの次元は Items axis, Major_axis, Minor_axis と言います。

このオブジェクトにどのようなメソッドがあるかは[ドキュメント](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Panel.html)を参照すると良いでしょう。

### Panel オブジェクトの主要な操作

まずはインデックスでアクセスするのがポピュラーでしょう。

```py3
pf.ix[0] # df1 へのアクセス
pf.ix[1] # df2 へのアクセス
pf['df1'] # これも df1 へのアクセス
```

このように Panel が持つ各表へアクセスすることができます。

```py3
# 表へ新しいカラムを追加
pf['df1']['E'] = pd.DataFrame(np.random.randn(100, 1), index = rng)
pf['df2']['E'] = pd.DataFrame(np.random.randn(100, 1), index = rng)

# データ構造を確認する
pf.shape
#=> (3, 100, 4)

# df1 表の E 列のカラムを 10 件アクセスする
pf.ix['df1',-10:,'E']
#=>
# 2014-04-01   -1.623615
# 2014-04-02    1.878481
# 2014-04-03   -0.890555
# 2014-04-04    0.736037
# 2014-04-05   -1.451665
# 2014-04-06    0.126473
# 2014-04-07    0.997485
# 2014-04-08   -1.252981
# 2014-04-09   -1.136791
# 2014-04-10   -1.873199
```

また Panel は to_flame() で積み重ねたデータフレームに変換することができます。この積み重ねたデータフレームに対し統計関数を利用することができます。さらにこのオブジェクトは to_panel() で元の Panel に再変換できます。

```py3
pf.to_frame().to_panel()
#=>
# <class 'pandas.core.panel.Panel'>
# Dimensions: 3 (items) x 100 (major_axis) x 4 (minor_axis)
# Items axis: df1 to df3
# Major_axis axis: 2014-01-01 00:00:00 to 2014-04-10 00:00:00
# Minor_axis axis: A to D
```

## ログデータの分析に Panel を使う

あるディレクトリにアプリケーションのログファイルが毎日たとえば [Fluentd](http://www.fluentd.org/) などで生成されているとします。このログファイルを日付をまたいで分析する場合、一日分のデータを表にして 3 次元のデータ構造とすれば時系列の分析ができますから大変便利です。

[先日の記事](http://qiita.com/ynakayama/items/8616f4c0c6e372de9a42)でサンプルとしてちらっと出てきたディレクトリのファイル一覧を取得するプログラムを少し書き換えて応用します。

```py3
import sys
import os
import pandas as pd

def list_files(path):
    dic = {}
    for root, dirs, files in os.walk(path):
        for filename in files:
            fullname = os.path.join(root, filename)
            if filename.startswith("fluent") \
               and filename.endswith(".log"):
                try:
                    print("Reading: %(filename)s" % locals())
                    df = pd.read_table(
                        os.path.join(path, filename), header=None)
                    dic[filename] = df
                except pd.parser.CParserError:
                    print("Skip: %(filename)s" % locals())
    return pd.Panel(dic)
```

このメソッドで戻ってくる Panel オブジェクトは複数のログファイルをまとめた 3 次元のデータ構造ですから、統計関数を利用して時系列のデータ分析などをすることができます。

## まとめ

pandas では Panel を利用して 3 次元のデータ構造を扱うことができます。行と列のデータ構造に加え、もう 1 つ次元を加えることで、時系列のデータ分析などにおいて威力を発揮します。