170
159

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Python その2Advent Calendar 2015

Day 19

pandasのMultiIndexについて

Last updated at Posted at 2015-12-18

この記事はPython その2 Advent Calendar 2015の19日目の記事です.

Advent Calendar登録してみたものの、最近Pythonそんな書いていない&書いてても業務上特殊なことをやり過ぎてて書けないので、ちょっと今更感はありますがpandasの小ネタです。
データの前処理や集計等でおなじみのpandas、知ってはいるけどちょっと使いどころがわかりにくいMultiindexついて書きます。

執筆時の環境

  • Python 2.7.11 |Anaconda 2.0.1 (x86_64)
  • pandas 0.17.0
  • 実行環境: jupyter notebook

以下出てくるコードは

import pandas as pd

してある前提で書いてあります。また、貼り付けているテーブルはto_htmlしたものをコピペしているので、jupyter notebook上の表示とは若干異なります。

MultiIndexって何

MultiIndex / Advanced Indexing
pandasのDataFrame等で、複数の列をインデックスにする機能。複数の値をキーにしたらデータが一意に特定されるような場合に便利。

データの用意と普通のインデックスのおさらい

普通のインデックスをこの記事ではMultiIndexとの対比で便宜上SingleIndexと呼びます。(そういう型があるわけではない)

データの準備

説明用に次のようなcsvを用意する

sample.csv
name,year,product,expected,actual
Taro,2013,A,116,119
Taro,2013,B,131,149
Taro,2014,A,151,124
Taro,2014,B,125,107
Taro,2015,A,135,109
Taro,2015,B,142,148
Hanako,2013,A,170,130
Hanako,2013,B,113,190
Hanako,2014,A,102,142
Hanako,2014,B,183,125
Hanako,2015,A,169,120
Hanako,2015,B,134,199

これをpandasで読み込む。

df1 = pd.read_csv('sample.csv')
name year product expected actual
0 Taro 2013 A 116 119
1 Taro 2013 B 131 149
2 Taro 2014 A 151 124
3 Taro 2014 B 125 107
4 Taro 2015 A 135 109
5 Taro 2015 B 142 148
6 Hanako 2013 A 170 130
7 Hanako 2013 B 113 190
8 Hanako 2014 A 102 142
9 Hanako 2014 B 183 125
10 Hanako 2015 A 169 120
11 Hanako 2015 B 134 199

このテーブルの行ラベルになっている0,1,2...data.index、列ラベルの"name", "year",...data.columnsでどちらもpandas.Indexというデータ型になっている。

pandas.Indexnameというメンバを持っているので

df1.index.name = "id"
df1.columns.name = "header"

とすると

header name year product expected actual
id
0 Taro 2013 A 116 119
1 Taro 2013 B 131 149
2 Taro 2014 A 151 124
3 Taro 2014 B 125 107
4 Taro 2015 A 135 109
5 Taro 2015 B 142 148
6 Hanako 2013 A 170 130
7 Hanako 2013 B 113 190
8 Hanako 2014 A 102 142
9 Hanako 2014 B 183 125
10 Hanako 2015 A 169 120
11 Hanako 2015 B 134 199
こんなテーブルになる。

データの選択

  • 列で選択
df1["name"]
出力
id
0       Taro
1       Taro
2       Taro
3       Taro
4       Taro
5       Taro
6     Hanako
7     Hanako
8     Hanako
9     Hanako
10    Hanako
11    Hanako
Name: name, dtype: object
  • 行で選択
df1.loc[3]
出力
header
name        Taro
year        2014
product        B
expected     125
actual       107
Name: 3, dtype: object
  • 行インデックスで選択
df1.iloc[3]

今回はindexが行インデックスなので出力は同じ。

  • 行と列で選択
df1.ix[3:5,"product":"actual"]

出力

product expected actual
3 B 125 107
4 A 135 109
5 B 142 148
  • 条件で選択
df1[(df1["name"] == "Hanako")&(df1["product"] == "A")]

出力

header name year product expected actual
id
6 Hanako 2013 A 170 130
8 Hanako 2014 A 102 142
10 Hanako 2015 A 169 120

詳しくは他の記事や書籍参照。

MultiIndexの設定

ここからが本題。
先ほど読み込んだデータは、見ればわかるように

  • レコードidのような行でユニークな列がなく、
  • name, year, productの3列合わせるとレコードが1つに決まる。

こんな時はMultiIndexを使うとデータがすっきりとまとまる。

df2 = pd.read_csv("sample.csv",index_col=["name","year","product"])
expected actual
name year product
Taro 2013 A 116 119
B 131 149
2014 A 151 124
B 125 107
2015 A 135 109
B 142 148
Hanako 2013 A 170 130
B 113 190
2014 A 102 142
B 183 125
2015 A 169 120
B 134 199

データを読み込んだ後や、いろいろと処理をして作成したDataFrameにMultiIndexをはる時は
2016/3/18追記
set_index関数を使うと、指定した列をインデックスにしてくれる。(これ公式ドキュメントのMultiIndexのページに載っていなかったので知らなかった・・・。)

df2 = df1.set_index(["name","year","product"], drop=True)

drop=Trueとすると、インデックスに設定した列は削除される。
戻すときは

df1 = df2.reset_index()

とする
追記終わり

pd.MultiIndex.from_arrays()
pd.MultiIndex.from_product()
pd.MultiIndex.from_tuples()

あたりを使えばなんとかなる。ちなみにpd.MultiIndex.from_productは複数のコレクションを渡してやるとネストしたループのインデックスを作る(itertools.productと同じ)。例えばdf2と同じインデックスを作るには

pd.MultiIndex.from_product((["Taro","Hanako"],[2013,2014,2015],["A","B"]),\
    names=["name","year","product"])

とすればいい。

データの選択

行を選択する時はインデックスのタプルを渡せば良い。

df2.loc[("Taro",2013,"A")]
出力
expected    116
actual      119
Name: (Taro, 2013, A), dtype: int64

途中までのタプルを渡すと、テーブルが返ってくるが、途中のレベルまでの指定はPerformanceWarningがでるので、一時的に中を見たい時以外は使わないほうがいい。
第一レベル(今回で言うname)もしくは全レベルの指定なら問題ない。

df2.loc[("Taro",2013)]

出力 (PerformanceWarning)

expected actual
product
A 116 119
B 131 149

途中のレベルまでの指定とか、途中だけの指定とかはxsを使う

df2.xs(["Taro","A"],level=[0,2])

出力

expected actual
year
2013 116 119
2014 151 124
2015 135 109

これでWarnningは出なくなったが、上と同じ処理を書いてもこっちの方がちょっと遅い・・・

MultiIndexの使い所

pandas.Indexはpandas.Seriesではないので、普通の列のような感覚で使えないことが結構あり、地味にストレスがたまる。じゃあSingleIndexとSeriesのままでいいじゃん と言いたいところだが、せっかくあるんだから使いどころを考える

複数列をキーにして一意に決まるデータを探してくる

先ほども書きましたがこれが主な使い所でしょう。

df2.loc[("Taro",2013,"A")]

同じことをSingleIndexでやろうと思うと、

df1[(df1["name"] == "Taro")&(df1["year"] == 2013)&(df1["product"] == "A")].iloc[0]

という感じになって汚い(多分もっといい書き方があるが)。
当然速度にも差がある。と思って5万件くらいのデータ使って試したら1試行では2~3倍くらいしか差がつかなかった。%%timeitで調べようとしたが、df2の方がキャッシュされてしまうようで比較できなかった。

また、指定したキーに該当するデータがあるかどうかわからない時、MultiIndexなら

("Taro",2013,"A") in df2.index

でいいが、SingleIndexでは

df1[(df1["name"] == "Taro")&(df1["year"] == 2013)&(df1["product"] == "A")].shape[0]>0

とかやらないといけない(多分もっといい書き方があるが)

表示用

キーとなるインデックスが入れ子になったMultiIndexの出力は人間にとっては視認性が高いので、出力の為だけにMultiIndexを使うというのもあり。
DataFrameはhtml, excel, latex等々多数の形式で出力でき、エクセルであってもちゃんとMultiIndexに合わせてセル結合してくれるので地味に便利。
ちなみにdf.to_csvでは、MultiIndexであってもちゃんと全行フルに出力してくれる。

その他操作方法など

  • 1レベル分のインデックスを取り出したい時はdf.index.get_level_valuesを使う
  • MultiIndexをSingleIndexに戻すにはdf.reset_index()を使う。各levelの列が追加されて、通し番号がindexになったDataFrameができる。
  • その他、スライス等々あるがイマイチ使い方がわかっていないのでまたの機会に
170
159
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
170
159

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?