この間、Pandasの勉強をしていると、PandasのDataFrameクラスにはpivotとpivot_tableという2つのメソッドがあることに気付かず、違う方を使って自分で打ち込んだサンプルコードが動かなかったり、helpを読んで混乱することが度々あった。
pivotとpivot_tableが違うことに気付いた時にあまりにも悔しかったので、pivotとpivot_tableの違いに関してメモしておく。
以下、Jupyter Notebookで書いたコードと出力を使って記述する。
In [1]:
df = pd.DataFrame({'date': ['{}:{:02d}:{:02d}'.format(6, 30, s) for s in (2, 2, 2, 3, 3, 3, 4, 4, 4)],
'variable': ['A', 'B', 'C'] * 3,
'value': list('abcdefghi')})
df
Out [1]:
date | variable | value | |
---|---|---|---|
0 | 6:30:02 | A | a |
1 | 6:30:02 | B | b |
2 | 6:30:02 | C | c |
3 | 6:30:03 | A | d |
4 | 6:30:03 | B | e |
5 | 6:30:03 | C | f |
6 | 6:30:04 | A | g |
7 | 6:30:04 | B | h |
8 | 6:30:04 | C | i |
こういう、いわゆるlongフォーマットの時系列データを以下のwideフォーマットに変形することを考える。(筆者は最近実際にやることがあった)
In [2]:
df.pivot(index='date', columns='variable', values='value')
Out [2]:
variable | A | B | C |
---|---|---|---|
date | |||
6:30:02 | a | b | c |
6:30:03 | d | e | f |
6:30:04 | g | h | i |
上ではpivotを使ったが、pivot_tableを使っても同じことができる。
但し、pivot_tableはグループ毎(2次元の表にまとめられたセル毎)に集約関数が適用され、defaultの関数はnp.sumで、この例のようにセルの値が文字列だと適用できず、"DataError: No numeric types to aggregate"というエラーになる為、何か文字列を受け取れる関数を指定する必要がある。
(下記のようにaggfunc='first'とすると、DataFrameGroupBy.firstが用いられると思われる)
df.pivot_table(index='date', columns='variable', values='value', aggfunc='first')
Out [3]:
variable | A | B | C |
---|---|---|---|
date | |||
6:30:02 | a | b | c |
6:30:03 | d | e | f |
6:30:04 | g | h | i |
このように、pivot_tableはグループ毎に必ず集約関数が使われるのが、pivotとの主な違いのようである。
pivotでは集約ができないので、次のように結果が同じ行と同じ列に相当する行があると、それらが全く同じ値であってもエラーになる。(筆者は実際にそういうデータに遭遇して難儀した)
df = df.append(pd.DataFrame({'date': ['{}:{:02d}:{:02d}'.format(6, 30, s) for s in (6, 6, 6) * 2],
'variable': ['A', 'B', 'C'] * 2,
'value': list('jkl') * 2}))
df
Out [4]:
date | variable | value | |
---|---|---|---|
0 | 6:30:02 | A | a |
1 | 6:30:02 | B | b |
2 | 6:30:02 | C | c |
3 | 6:30:03 | A | d |
4 | 6:30:03 | B | e |
5 | 6:30:03 | C | f |
6 | 6:30:04 | A | g |
7 | 6:30:04 | B | h |
8 | 6:30:04 | C | i |
0 | 6:30:06 | A | j |
1 | 6:30:06 | B | k |
2 | 6:30:06 | C | l |
3 | 6:30:06 | A | j |
4 | 6:30:06 | B | k |
5 | 6:30:06 | C | l |
df.pivot(index='date', columns='variable', values='value')
Out [5]:
ValueError: Index contains duplicate entries, cannot reshape
pivot_tableであれば、この場合も成功する。
In [6]:
df.pivot_table(values='value', index='date', columns='variable', aggfunc='first')
Out [6]:
variable | A | B | C |
---|---|---|---|
date | |||
6:30:02 | a | b | c |
6:30:03 | d | e | f |
6:30:04 | g | h | i |
6:30:06 | j | k | l |
なお、PandasのAPI Referenceに次のように書かれていたのを、今日見つけた。
- DataFrame.pivot_table
- Generalization of pivot that can handle duplicate values for one index/column pair.
- DataFrame.pivot
- Pivot without aggregation that can handle non-numeric data.
軟派な印象があって何となく敬遠していたJupyter Notebookを今年に入ってから使い始めたが、短いコードを色々試行錯誤するのに予想外に便利で、すっかりはまってしまった。以前は短いコードを試行錯誤するのにEmacs+Jedi.elを使っていたが、それに比べ、入力補完が強力なのと、コード片を書き換えながら実行するのがやりやすいのと、実験中のコードの最後の出力が残るのが良い。
ただ、筆者の環境では1つのセルを実行するとウィンドウが次のセルの先頭まで飛んで実行結果がすぐに見えないことが時々あるのと、docstringを表示するのが面倒(オブジェクト名に?を付けて一度セルを実行しないと表示されない)なのと、最初から全て実行するのが少し面倒で遅いのと、マウス操作が必要になることが多いのが難点で、ついEmacsを併用してしまう。
また、デバッグがやりにくいので、長いコードを書く気には全くならない。筆者は長いコードを書く時は大体Spyderを使っているが、短いコードを試行錯誤する時の柔軟性が無い。当分は3つの環境を行き来しそうだ。
コメント