R ユーザーへの pandas 実践ガイド


# 概要
R で tidyverse (dplyr+tidyr) に使い慣れているが, Python に乗り換えると `pandas` がどうも使いにくい, と感じている人の視点で, Rの `dplyr` などとの比較を通して, `pandas` の効率的な使い方について書いています. そのため, 「R ユーザーへの」と書きましたが, R経験のない pandas ユーザーであってもなんらかの役に立つと思います. また, 自社インターン学生に対する教材も兼ねています. どちらかというと, 初歩を覚えたての初心者向けの記事となっています.


データ分析は一発で終わることはまずなく, 集計・前処理を探索的に行う必要があります. よって, プログラムを頻繁に書き直す必要があり, 普段以上に保守性のある書き方, 例えば参照透過性を考慮した書き方をしたほうが便利です. R の `tidyverse` の強みとして, 再帰代入をする必要がほとんどなく, 複雑な処理であってもかなりをワンライナーで書ける, というものがあります. 関数が, 集計, グループ化, 列の追加, というように, 機能の分け方が SQL 風になっているのもとっつきやすさにつながっていると思います. 一方で, Python の`pandas` は, メソッドチェーンで `dplyr` のパイプに近い機能を持たせるなど, `tidyverse` に影響された機能も多いですが, 普通に書こうとすると再帰代入を頻繁に要求されます. 

結果として, pandas を効率的に書くテクニックは存在しますが, 公式ドキュメントではあまり強調されていません. そこで, ここでは, `pandas` で実現可能な, 効率的なデータ分析作業をする方法について紹介します.

# 導入
この記事の記述の多くは, 以下を参考にしています.

1. 『[Pandas 公式チートシートを翻訳しました][1]』
2. 『[Python/pandasのデータ処理で再帰代入撲滅委員会][2]』
3. 『[dplyr のアレを Pandas でやる][3]』
4. 『[dplyr使いのためのpandas dfplyすごい編][4]』

最低限, [[1]] を読んでいて, **pandas の基本的な操作を知っている**ことを前提としています. そして今回は, 公式チートシートを踏まえて, より効率的なやり方をいろいろと紹介します. よって, `iloc` と `loc` の違いなど, 公式チートシートに書いてある話はあえて繰り返すことはありません. また, 教材という目的のため, 上記 [[2]]-[[4]] で紹介されている内容の繰り返しになっている箇所も多いですが, ご容赦ください.

なお, **動作確認は python 3.6,  pandas 0.25, dfply 0.3.3**でやっています.

今回紹介するものは, 以下のようにして作成したデータフレームを使っています.

```python3
import pandas as pd
import numpy as np
from dfply import *

np.random.seed(42)

df = pd.DataFrame(
    {
        'x': np.random.normal(size=50*3),
        'y': np.random.normal(size=50*3),
        'col': list('ABCDE') * 10 * 3,
    })
pd.options.display.max_rows = 10  # 記事上の見やすさのため表示行数を制限
pd.options.display.precision = 2  # 記事上の見やすさのため表示桁数を制限
```

# 参考記事のおさらい
## データフレームの再帰代入をやめる
このセクションは, 参考記事 [[2]] の内容と被る箇所がほとんどですが, いくつか異なる点もあります. 冗長ですが今一度確認します.


### 行の選択・列の作成

上記に挙げた記事では `lambda` 演算子を使う方法が紹介されていました.
これがなぜ必要なのか. 例えば, 以下の2つはどちらも同じです.


```python
df.assign(z=lambda d: d['x']**2)
df.assign(z=df['x']**2)
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>x</th>
      <th>y</th>
      <th>col</th>
 <th>z</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>0.25</td>
      <td>A</td>
      <td>0.25</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-0.14</td>
      <td>0.35</td>
      <td>B</td>
      <td>0.02</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.65</td>
      <td>-0.68</td>
      <td>C</td>
      <td>0.42</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1.52</td>
      <td>0.23</td>
      <td>D</td>
      <td>2.32</td>
    </tr>
    <tr>
      <th>4</th>
      <td>-0.23</td>
      <td>0.29</td>
      <td>E</td>
      <td>0.05</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>145</th>
      <td>0.78</td>
      <td>-0.69</td>
      <td>A</td>
      <td>0.61</td>
    </tr>
    <tr>
      <th>146</th>
      <td>-1.24</td>
      <td>0.90</td>
      <td>B</td>
      <td>1.53</td>
    </tr>
    <tr>
      <th>147</th>
      <td>-1.32</td>
      <td>0.31</td>
      <td>C</td>
      <td>1.74</td>
    </tr>
    <tr>
      <th>148</th>
      <td>0.52</td>
      <td>0.81</td>
      <td>D</td>
      <td>0.27</td>
    </tr>
    <tr>
      <th>149</th>
      <td>0.30</td>
      <td>0.63</td>
      <td>E</td>
      <td>0.09</td>
    </tr>
  </tbody>
</table>

</div>

これだけだと何がいいのかわかりませんが, メソッドチェーンを連結していくと `lambda` が便利であることがわかります. 例えば, 

1. `col` の値ごとにグループ別合計をとり
2. `x` の値が正の行だけを取り出し
3. `y` の数値を2乗した `z` を新たに作成する

という処理を, `lambda` を使う場合と使わない場合とで考えてみます. 以下の1行目が `lambda` を使った場合, 2行目が使わない場合です. いずれも同じ結果を返します.

```python3
df.groupby('col').sum().loc[lambda d: d['x'] > 0].assign(z=lambda d: d['y']**2)
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">      <th></th>      <th>x</th>      <th>y</th>      <th>z</th>    </tr>
    <tr>      <th>col</th>      <th></th>      <th></th>      <th></th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>A</th>
      <td>1.6</td>
      <td>-1.64</td>
      <td>2.68</td>
    </tr>
    <tr>
      <th>B</th>
      <td>1.9</td>
      <td>3.28</td>
      <td>10.77</td>
    </tr>
  </tbody>
</table>
</div>

```python3
df.groupby('col').sum().loc[df.groupby('col').sum()['x'] > 0].assign(
    z=df.groupby('col').sum().loc[df.groupby('col').sum()['x'] > 0]['y'] ** 2)
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">      <th></th>      <th>x</th>      <th>y</th>      <th>z</th>    </tr>
    <tr>      <th>col</th>      <th></th>      <th></th>      <th></th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>A</th>
      <td>1.6</td>
      <td>-1.64</td>
      <td>2.68</td>
    </tr>
    <tr>
      <th>B</th>
      <td>1.9</td>
      <td>3.28</td>
      <td>10.77</td>
    </tr>
  </tbody>

</table>
</div>

メソッドチェーンはデータフレームを書き換えるわけではないので, 集計後の値を参照する場合は, このように同一の集計処理を何度も書く必要があり, 非常に冗長となります. 何度もやり直すことが普通なデータ分析の作業で, 同じ処理を何度も書き直すのは効率が悪く, 変更時に書き間違える可能性が増します.
このように, 処理が複雑になり, メソッドチェーンが深くなるほど `lambda` を使った参照透過性は重要になっていきます.

参考記事 [[2]] では, `df.pipe` を使って SQL の select-where 構文の処理を実行していますが, 参考記事 [[3]] の[ここ](https://qiita.com/mwmsnn/items/6a464865759231aa888d#%E5%88%97%E3%81%AE%E9%81%B8%E6%8A%9E-dplyrselect) にあるように, **このような処理は実際には `df.loc` で済むことが多いです**. `pipe` メソッドは, 以下にあるようにデータフレーム全体に関数を適用したい際に使う (`lambda` も関数ですが) ものです.

 『[Python pandas データのイテレーションと関数適用、pipe - StatsFragrments](http://sinhrks.hatenablog.com/entry/2015/06/18/221747)』


すべての列 or 行に適用する `mutate_all` 相当の処理は, apply を使います. こちらでも `lambda` を使えます.


```python3
# mutate_all()
# s は DataFrame ではなく Series であることに注意
df.select_dtypes(exclude='object').apply(lambda s: s - [10] * s.shape[0], axis=0)
# row-wise に適用したい場合
df.select_dtypes(exclude='object').apply(lambda s: s - [10] * s.shape[0], axis=1)
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">      <th></th>      <th>x</th>      <th>y</th>  </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>-9.50</td>
      <td>-9.75</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-10.14</td>
      <td>-9.65</td>
    </tr>
    <tr>
      <th>2</th>
      <td>-9.35</td>
      <td>-10.68</td>
    </tr>
    <tr>
      <th>3</th>
      <td>-8.48</td>
      <td>-9.77</td>
    </tr>
    <tr>
      <th>4</th>
      <td>-10.23</td>
      <td>-9.71</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>145</th>
      <td>-9.22</td>
      <td>-10.69</td>
    </tr>
    <tr>
      <th>146</th>
      <td>-11.24</td>
      <td>-9.10</td>
    </tr>
    <tr>
      <th>147</th>
      <td>-11.32</td>
      <td>-9.69</td>
    </tr>
    <tr>
      <th>148</th>
      <td>-9.48</td>
      <td>-9.19</td>
    </tr>
    <tr>
      <th>149</th>
      <td>-9.70</td>
      <td>-9.37</td>
    </tr>
  </tbody>

</table>
</div>


処理が多い場合は, dict で与える手もあります. リスト内包表記と組み合わせれば複雑な処理も比較的シンプルに書けるかもしれません.

```python3
df.assign(**{'a': lambda d: d['x']**2, 'col2': lambda d: d['col'] + '_2'})
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">      <th></th>      <th>x</th>      <th>y</th>      <th>col</th>      <th>a</th>      <th>col2</th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>0.25</td>
      <td>A</td>
      <td>0.25</td>
      <td>A_2</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-0.14</td>
      <td>0.35</td>
      <td>B</td>
      <td>0.02</td>
      <td>B_2</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.65</td>
      <td>-0.68</td>
      <td>C</td>
      <td>0.42</td>
      <td>C_2</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1.52</td>
      <td>0.23</td>
      <td>D</td>
      <td>2.32</td>
      <td>D_2</td>
    </tr>
    <tr>
      <th>4</th>
      <td>-0.23</td>
      <td>0.29</td>
      <td>E</td>
      <td>0.05</td>
      <td>E_2</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>145</th>
      <td>0.78</td>
      <td>-0.69</td>
      <td>A</td>
      <td>0.61</td>
      <td>A_2</td>
    </tr>
    <tr>
      <th>146</th>
      <td>-1.24</td>
      <td>0.90</td>
      <td>B</td>
      <td>1.53</td>
      <td>B_2</td>
    </tr>
    <tr>
      <th>147</th>
      <td>-1.32</td>
      <td>0.31</td>
      <td>C</td>
      <td>1.74</td>
      <td>C_2</td>
    </tr>
    <tr>
      <th>148</th>
      <td>0.52</td>
      <td>0.81</td>
      <td>D</td>
      <td>0.27</td>
      <td>D_2</td>
    </tr>
    <tr>
      <th>149</th>
      <td>0.30</td>
      <td>0.63</td>
      <td>E</td>
      <td>0.09</td>
      <td>E_2</td>
    </tr>
  </tbody>
</table>
</div>


#### 補足: assign と eval

[dplyr使いのためのpandas 基本編](https://qiita.com/T_Shinomiya/items/d445ce2f3d95160ec944#mutate--eval-assign)  にあるように, `eval` メソッドは文字列を評価して, `assign` と同様のことができます. 参照透過性も担保されているため, 再帰代入も必要ありません. 最初の例で `eval` を使うなら, 以下のようになります.

```python3
df.groupby('col').sum().loc[lambda d: d['x'] > 0].eval('z = y**2')
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">      <th></th>      <th>x</th>      <th>y</th>      <th>z</th>    </tr>
    <tr>      <th>col</th>      <th></th>      <th></th>      <th></th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>A</th>
      <td>1.6</td>
      <td>-1.64</td>
      <td>2.68</td>
    </tr>
    <tr>
      <th>B</th>
      <td>1.9</td>
      <td>3.28</td>
      <td>10.77</td>
    </tr>
  </tbody>
</table>
</div>

『[pandas でメモリに乗らない 大容量ファイルを上手に扱う - Stats Fragments](http://sinhrks.hatenablog.com/entry/2014/11/21/231534)』 の「eval による処理の最適化」のセクションに書かれているように, `eval` は `assign` と違いメモリを消費しません. よって, 大容量のデータを扱うときは `eval` のほうが高速になります. しかし, これも引用記事に書かれていることですが, **評価できる演算子が限定されているため, 汎用性はありません**(引用した記事は少し古いですが, 最新の 0.25 でもこの状況は変わっていません). 書きやすさの観点から, 私も引用記事の著者と同じ用に, 普段は `assign` を使い, 大容量なデータを扱う際にどうしてもネックになる処理だけ `eval` を使う, というやり方にしています.

参考: 公式リファレンス "_Enhancing performance_" 中の 『[Expression evaluation via eval()](https://pandas.pydata.org/pandas-docs/stable/user_guide/enhancingperf.html#expression-evaluation-via-eval)』

#### 補足: ドットか括弧か
参考記事ではいずれも, 列を参照するとき, `d.x` という書き方をしていますが, この記事では `d['x']` という書き方をしています. `.` で列にアクセスする場合は, 列名とデータフレームのメソッドと名前が重複していると, 列ではなくメソッドが呼び出されてしまいます[^dotbrace].
そこで, 名前の衝突回避のため `lambda d: d['col']` のような書き方をすると安全です. ただし, 括弧の多用により, 少し見づらくなります. どちらを使うかは好みの問題でしょう.

#### 補足: inplace を使うべきか

これも必ずこうしたほうがいい, というより好みの問題です. pandas のメソッドのほとんどはデータフレームを返すので, 結果を自身に代入するオプションとして, `inplace` 引数が多くのメソッドに用意されています. しかし, メソッドチェーンの中で `inplace=True` するとエラーになりますが, 末尾に `inplace=True` しても何もエラーが発生しません. また,   `assign` など `inplace` がないメソッドもあります. そのため, 何度も処理の組み合わせを変更しているうちにおかしなコードになってしまう恐れがあるので, 私は `inplace=True`  を使わず, なるべく `=` 代入の形で統一するようにしています.



### 列選択

上記の例では, `assign` と `.loc` での行選択で `lambda` を使っていますが, 列選択にも使うことができます.

`dplyr::select()` に対応する操作は, `df[['A', 'B']]` や `df.loc[]` です. 前者は列名のリストによる指定ですが, 後者は `.loc` を使うことでインデックスまたはブーリアンによる指定になっています.

しかし, もう少し複雑な条件で取り出したいときはもあります. そのような場合, 以下のようなメソッドが使えます.
1. 正規表現で列名にマッチする `df.filter` 
2. 列の型で条件付けて取り出す `df.select_dtypes`
3. `dplyr` の `starts_with`, `ends_with`, `contains` に対応する `df.columns.str.startswith`/`endswith`/`contains` 

などがあります.

```python3
df.loc[lambda d: d['x'] > 0, lambda d: d.filter(regex='x').columns]
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">      <th></th>      <th>x</th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.65</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1.52</td>
    </tr>
    <tr>
      <th>6</th>
      <td>1.58</td>
    </tr>
    <tr>
      <th>7</th>
      <td>0.77</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
    </tr>
    <tr>
      <th>143</th>
      <td>0.18</td>
    </tr>
    <tr>
      <th>144</th>
      <td>0.26</td>
    </tr>
    <tr>
      <th>145</th>
      <td>0.78</td>
    </tr>
    <tr>
      <th>148</th>
      <td>0.52</td>
    </tr>
    <tr>
      <th>149</th>
      <td>0.30</td>
    </tr>
  </tbody>
</table>
</div>

```python3
df.filter(regex='x').loc[lambda d: d['x'] > 0]
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">      <th></th>      <th>x</th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.65</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1.52</td>
    </tr>
    <tr>
      <th>6</th>
      <td>1.58</td>
    </tr>
    <tr>
      <th>7</th>
      <td>0.77</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
    </tr>
    <tr>
      <th>143</th>
      <td>0.18</td>
    </tr>
    <tr>
      <th>144</th>
      <td>0.26</td>
    </tr>
    <tr>
      <th>145</th>
      <td>0.78</td>
    </tr>
    <tr>
      <th>148</th>
      <td>0.52</td>
    </tr>
    <tr>
      <th>149</th>
      <td>0.30</td>
    </tr>
  </tbody>
</table>
</div>

この例では, メソッドチェーンが浅いため, `filter` を先に持ってきて, `lambda` を使わないほうがむしろシンプルになります. しかし, もっと複雑な処理でも, このように `lambda` 内で `.filter()` など, `pandas.DataFrame` の持つ便利なメソッドを呼び出せることを覚えておいてください.

## long to wide

wide (いわゆる横持ち) から long (いわゆる縦持ち) への変換は, `pd.DataFrame.melt`[^melt]が `tidyr::gather` とほぼ同じなので省略します. 問題は long から wide への変換の場合です. 公式チートシートでは `pivot` を使えばできることになっていますが...


```python3
df.pivot(columns='col', values=['x', 'y'])
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr>      <th></th>      <th colspan="5" halign="left">x</th>      <th colspan="5" halign="left">y</th>    </tr>
    <tr>      <th>col</th>      <th>A</th>      <th>B</th>      <th>C</th>      <th>D</th>      <th>E</th>      <th>A</th>      <th>B</th>      <th>C</th>      <th>D</th>      <th>E</th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.25</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>1</th>
      <td>NaN</td>
      <td>-0.14</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.35</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.65</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>-0.68</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>3</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1.52</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.23</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>4</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>-0.23</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.29</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>145</th>
      <td>0.78</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>-0.69</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>146</th>
      <td>NaN</td>
      <td>-1.24</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.90</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>147</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>-1.32</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.31</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>148</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.52</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.81</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>149</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.30</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>0.63</td>
    </tr>
  </tbody>

</table>
</div>



この通り**大失敗**です.

そもそもこれは, `pivot` の名前の通り, 本来ピボットテーブルを作るのが目的のメソッドです. 行に対応する軸を指定しなければ, 意図したとおりに変形してくれません. これは `unstack` でも同様です.

今回は, A-E の5要素で1グループとしたいので, 変換後の行に対応する行インデックスを新たに作る必要があります. 以下のように

```python3
df.assign(
    row=lambda x: sorted(list(range(x.shape[0] // x.col.nunique())) * x.col.nunique())
).head(10)
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>x</th>
      <th>y</th>
      <th>col</th>
      <th>row</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>0.25</td>
      <td>A</td>
      <td>0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-0.14</td>
      <td>0.35</td>
      <td>B</td>
      <td>0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.65</td>
      <td>-0.68</td>
      <td>C</td>
      <td>0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1.52</td>
      <td>0.23</td>
      <td>D</td>
      <td>0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>-0.23</td>
      <td>0.29</td>
      <td>E</td>
      <td>0</td>
    </tr>
    <tr>
      <th>5</th>
      <td>-0.23</td>
      <td>-0.71</td>
      <td>A</td>
      <td>1</td>
    </tr>
    <tr>
      <th>6</th>
      <td>1.58</td>
      <td>1.87</td>
      <td>B</td>
      <td>1</td>
    </tr>
    <tr>
      <th>7</th>
      <td>0.77</td>
      <td>0.47</td>
      <td>C</td>
      <td>1</td>
    </tr>
    <tr>
      <th>8</th>
      <td>-0.47</td>
      <td>-1.19</td>
      <td>D</td>
      <td>1</td>
    </tr>
    <tr>
      <th>9</th>
      <td>0.54</td>
      <td>0.66</td>
      <td>E</td>
      <td>1</td>
    </tr>
  </tbody>

</table>
</div>

列を作ってから `pivot` を呼び出します.

```python3
df.assign(
    row=lambda x: sorted(list(range(x.shape[0] // x.col.nunique())) * x.col.nunique())
).pivot(index='row', columns='col', values=['x', 'y'])
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr>
      <th></th>
      <th colspan="5" halign="left">x</th>
      <th colspan="5" halign="left">y</th>
    </tr>
    <tr>
      <th>col</th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
      <th>E</th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
      <th>E</th>
    </tr>
    <tr>
      <th>row</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>-0.14</td>
      <td>0.65</td>
      <td>1.52</td>
      <td>-0.23</td>
      <td>0.25</td>
      <td>0.35</td>
      <td>-0.68</td>
      <td>0.23</td>
      <td>0.29</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-0.23</td>
      <td>1.58</td>
      <td>0.77</td>
      <td>-0.47</td>
      <td>0.54</td>
      <td>-0.71</td>
      <td>1.87</td>
      <td>0.47</td>
      <td>-1.19</td>
      <td>0.66</td>
    </tr>
    <tr>
      <th>2</th>
      <td>-0.46</td>
      <td>-0.47</td>
      <td>0.24</td>
      <td>-1.91</td>
      <td>-1.72</td>
      <td>-0.97</td>
      <td>0.79</td>
      <td>1.16</td>
      <td>-0.82</td>
      <td>0.96</td>
    </tr>
    <tr>
      <th>3</th>
      <td>-0.56</td>
      <td>-1.01</td>
      <td>0.31</td>
      <td>-0.91</td>
      <td>-1.41</td>
      <td>0.41</td>
      <td>0.82</td>
      <td>1.90</td>
      <td>-0.25</td>
      <td>-0.75</td>
    </tr>
    <tr>
      <th>4</th>
      <td>1.47</td>
      <td>-0.23</td>
      <td>0.07</td>
      <td>-1.42</td>
      <td>-0.54</td>
      <td>-0.89</td>
      <td>-0.82</td>
      <td>-0.08</td>
      <td>0.34</td>
      <td>0.28</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>25</th>
      <td>2.19</td>
      <td>-0.99</td>
      <td>-0.57</td>
      <td>0.10</td>
      <td>-0.50</td>
      <td>0.46</td>
      <td>0.20</td>
      <td>-0.60</td>
      <td>0.07</td>
      <td>-0.39</td>
    </tr>
    <tr>
      <th>26</th>
      <td>-1.55</td>
      <td>0.07</td>
      <td>-1.06</td>
      <td>0.47</td>
      <td>-0.92</td>
      <td>0.11</td>
      <td>0.66</td>
      <td>1.59</td>
      <td>-1.24</td>
      <td>2.13</td>
    </tr>
    <tr>
      <th>27</th>
      <td>1.55</td>
      <td>-0.78</td>
      <td>-0.32</td>
      <td>0.81</td>
      <td>-1.23</td>
      <td>-1.95</td>
      <td>-0.15</td>
      <td>0.59</td>
      <td>0.28</td>
      <td>-0.62</td>
    </tr>
    <tr>
      <th>28</th>
      <td>0.23</td>
      <td>1.31</td>
      <td>-1.61</td>
      <td>0.18</td>
      <td>0.26</td>
      <td>-0.21</td>
      <td>-0.49</td>
      <td>-0.59</td>
      <td>0.85</td>
      <td>0.36</td>
    </tr>
    <tr>
      <th>29</th>
      <td>0.78</td>
      <td>-1.24</td>
      <td>-1.32</td>
      <td>0.52</td>
      <td>0.30</td>
      <td>-0.69</td>
      <td>0.90</td>
      <td>0.31</td>
      <td>0.81</td>
      <td>0.63</td>
    </tr>
  </tbody>

</table>
</div>

`unstack` でもほぼ同じです


```python3
df.assign(
    row=lambda x: sorted(list(range(x.shape[0] // x.col.nunique())) * x.col.nunique())
).set_index(['row', 'col']).unstack(1)
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr>
      <th></th>
      <th colspan="5" halign="left">x</th>
      <th colspan="5" halign="left">y</th>
    </tr>
    <tr>
      <th>col</th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
      <th>E</th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
      <th>E</th>
    </tr>
    <tr>
      <th>row</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>-0.14</td>
      <td>0.65</td>
      <td>1.52</td>
      <td>-0.23</td>
      <td>0.25</td>
      <td>0.35</td>
      <td>-0.68</td>
      <td>0.23</td>
      <td>0.29</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-0.23</td>
      <td>1.58</td>
      <td>0.77</td>
      <td>-0.47</td>
      <td>0.54</td>
      <td>-0.71</td>
      <td>1.87</td>
      <td>0.47</td>
      <td>-1.19</td>
      <td>0.66</td>
    </tr>
    <tr>
      <th>2</th>
      <td>-0.46</td>
      <td>-0.47</td>
      <td>0.24</td>
      <td>-1.91</td>
      <td>-1.72</td>
      <td>-0.97</td>
      <td>0.79</td>
      <td>1.16</td>
      <td>-0.82</td>
      <td>0.96</td>
    </tr>
    <tr>
      <th>3</th>
      <td>-0.56</td>
      <td>-1.01</td>
      <td>0.31</td>
      <td>-0.91</td>
      <td>-1.41</td>
      <td>0.41</td>
      <td>0.82</td>
      <td>1.90</td>
      <td>-0.25</td>
      <td>-0.75</td>
    </tr>
    <tr>
      <th>4</th>
      <td>1.47</td>
      <td>-0.23</td>
      <td>0.07</td>
      <td>-1.42</td>
      <td>-0.54</td>
      <td>-0.89</td>
      <td>-0.82</td>
      <td>-0.08</td>
      <td>0.34</td>
      <td>0.28</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>25</th>
      <td>2.19</td>
      <td>-0.99</td>
      <td>-0.57</td>
      <td>0.10</td>
      <td>-0.50</td>
      <td>0.46</td>
      <td>0.20</td>
      <td>-0.60</td>
      <td>0.07</td>
      <td>-0.39</td>
    </tr>
    <tr>
      <th>26</th>
      <td>-1.55</td>
      <td>0.07</td>
      <td>-1.06</td>
      <td>0.47</td>
      <td>-0.92</td>
      <td>0.11</td>
      <td>0.66</td>
      <td>1.59</td>
      <td>-1.24</td>
      <td>2.13</td>
    </tr>
    <tr>
      <th>27</th>
      <td>1.55</td>
      <td>-0.78</td>
      <td>-0.32</td>
      <td>0.81</td>
      <td>-1.23</td>
      <td>-1.95</td>
      <td>-0.15</td>
      <td>0.59</td>
      <td>0.28</td>
      <td>-0.62</td>
    </tr>
    <tr>
      <th>28</th>
      <td>0.23</td>
      <td>1.31</td>
      <td>-1.61</td>
      <td>0.18</td>
      <td>0.26</td>
      <td>-0.21</td>
      <td>-0.49</td>
      <td>-0.59</td>
      <td>0.85</td>
      <td>0.36</td>
    </tr>
    <tr>
      <th>29</th>
      <td>0.78</td>
      <td>-1.24</td>
      <td>-1.32</td>
      <td>0.52</td>
      <td>0.30</td>
      <td>-0.69</td>
      <td>0.90</td>
      <td>0.31</td>
      <td>0.81</td>
      <td>0.63</td>
    </tr>
  </tbody>

</table>
</div>

`pandas.DataFrame.pivot` が `tidyr::spread` と同じという説明はかなりミスリードです. `pivot` は複数列を同時に処理できますが, 行インデックスも指定しなければうまくいきません. 一方で `spread` は1列づつしか処理できませんが, 当然行インデックスは存在しないので不要です. 

なお, tidyr 1.0 ではまた新たな構文が追加される予定らしい[^1] [^2] ですが, これに対応する機能は当然まだありません. 個人的には1関数1機能のほうが直感的でわかりやすいです.

`unstack` や `pivot` の詳しい挙動は以下も参考にしてください.

* [[3]] の『[縦持ち横持ち変換 tidyr::gather tidyr::spread](https://qiita.com/mwmsnn/items/6a464865759231aa888d#%E7%B8%A6%E6%8C%81%E3%81%A1%E6%A8%AA%E6%8C%81%E3%81%A1%E5%A4%89%E6%8F%9B-tidyrgather-tidyrspread)』のセクション
* 公式ドキュメントの "[Reshaping and pivot tables](https://pandas.pydata.org/pandas-docs/stable/user_guide/reshaping.html)"

# データの要約

[[3]] の 『[グループ化 dplyr::group_by と集約 dplyr::summarise](https://qiita.com/mwmsnn/items/6a464865759231aa888d#%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E5%8C%96-dplyrgroup_by-%E3%81%A8%E9%9B%86%E7%B4%84-dplyrsummarise)』セクションにあるように, pandas で各列を要約する `agg` メソッドは,  任意の列に任意の要約関数 (`sum` や `count` など, 列の全要素から計算する関数群のこと) を適用する `dplyr::summarise` とは機能が異なります. すべての列に要約関数を適用する `summarise_at` か `summarise_all` に近いです.

基本的な要約関数ならば, `agg` を使わなくとも呼び出せます. 使える関数は, [[1]] などを参考にしてください.

```python3
df.groupby('col').mean()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>x</th>
      <th>y</th>
    </tr>
    <tr>
      <th>col</th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>A</th>
      <td>0.05</td>
      <td>-0.05</td>
    </tr>
    <tr>
      <th>B</th>
      <td>0.06</td>
      <td>0.11</td>
    </tr>
    <tr>
      <th>C</th>
      <td>-0.11</td>
      <td>0.16</td>
    </tr>
    <tr>
      <th>D</th>
      <td>-0.07</td>
      <td>-0.07</td>
    </tr>
    <tr>
      <th>E</th>
      <td>-0.35</td>
      <td>0.21</td>
    </tr>
  </tbody>

</table>
</div>

```python3
df.groupby('col').agg({'x': [np.min, np.max]})
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead tr th {
        text-align: left;
    }

    .dataframe thead tr:last-of-type th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr><th></th><th colspan="2" halign="left">x</th></tr>
    <tr><th></th><th>amin</th><th>amax</th></tr>
    <tr><th>col</th><th></th><th></th></tr>
  </thead>
  <tbody>
    <tr>
      <th>A</th>
      <td>-1.92</td>
      <td>2.19</td>
    </tr>
    <tr>
      <th>B</th>
      <td>-1.24</td>
      <td>1.89</td>
    </tr>
    <tr>
      <th>C</th>
      <td>-1.96</td>
      <td>1.48</td>
    </tr>
    <tr>
      <th>D</th>
      <td>-1.91</td>
      <td>2.46</td>
    </tr>
    <tr>
      <th>E</th>
      <td>-2.62</td>
      <td>1.03</td>
    </tr>
  </tbody>

</table>
</div>

# 0.25 からの新機能
以下では, 最近追加された機能で特に便利なものをピックアップします

## 要約処理の改善

pandas 0.25 からは, pd.NamedAgg という新しい構文が追加され, `NEW_COLUMN=pd.NamedAgg(column='COL', aggfunc=FUNCTION)` という構文で, `dplyr::summarise` に近いことができるようになりました. 詳細は以下の公式ドキュメントを確認してください.

* "[Groupby Aggregation with Relabeling](https://pandas-docs.github.io/pandas-docs-travis/whatsnew/v0.25.0.html#groupby-aggregation-with-relabeling)"
* "[Named Aggregation](https://pandas-docs.github.io/pandas-docs-travis/user_guide/groupby.html#groupby-aggregate-named)"

例えば, `x` 列に対して最大, 最小, 中央値をグループ別に集計し, それぞれ `x_min`, `x_max`, `x_med` という列名にしたい場合は, 以下のように,

```python3
df.groupby('col').agg(
    x_min=pd.NamedAgg('x', 'min'),
    x_max=pd.NamedAgg('x', 'max'),
    x_med=pd.NamedAgg('x', 'median')
)
```

<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th><th>x_min</th><th>x_max</th><th>x_med</th></tr>
      <tr><th>col</th><th></th><th></th><th></th></tr>
  </thead>
  <tbody>
    <tr>
      <th>A</th>
      <td>-1.92</td>
      <td>2.19</td>
      <td>0.17</td>
    </tr>
    <tr>
      <th>B</th>
      <td>-1.24</td>
      <td>1.89</td>
      <td>-0.05</td>
    </tr>
    <tr>
      <th>C</th>
      <td>-1.96</td>
      <td>1.48</td>
      <td>-0.02</td>
    </tr>
    <tr>
      <th>D</th>
      <td>-1.91</td>
      <td>2.46</td>
      <td>0.05</td>
    </tr>
    <tr>
      <th>E</th>
      <td>-2.62</td>
      <td>1.03</td>
      <td>-0.23</td>
    </tr>
  </tbody>

</table>
</div>



と書けます. さらに省略して, (列名, 関数) のタプルだけで表現できる構文も用意されています. これはかなり便利になりました.

```python3
df.groupby('col').agg(
    x_min=('x', 'min'),
    x_max=('x', 'max'),
    x_med=('x', 'median')
)
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>      <th>x_min</th>      <th>x_max</th>      <th>x_med</th>    </tr>
    <tr>      <th>col</th>      <th></th>      <th></th>      <th></th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>A</th>
      <td>-1.92</td>
      <td>2.19</td>
      <td>0.17</td>
    </tr>
    <tr>
      <th>B</th>
      <td>-1.24</td>
      <td>1.89</td>
      <td>-0.05</td>
    </tr>
    <tr>
      <th>C</th>
      <td>-1.96</td>
      <td>1.48</td>
      <td>-0.02</td>
    </tr>
    <tr>
      <th>D</th>
      <td>-1.91</td>
      <td>2.46</td>
      <td>0.05</td>
    </tr>
    <tr>
      <th>E</th>
      <td>-2.62</td>
      <td>1.03</td>
      <td>-0.23</td>
    </tr>
  </tbody>

</table>
</div>

## ネスト/アンネスト

python で使う機会はあまりなさそうな気もしますが, R の `tibble` のように, データフレームの要素にさらにテーブル構造のデータが入れ子になっている場合を考えます.
`pandas` は `tibble` のようにデータフレームの要素にデータフレームを入れることもできますが, `nest`/`unnest` はサポートされていません.
ただし, 要素がリストやタプルであるなら, 0.25 以降は行に展開する `explode` メソッドが使えます.

以下では, それ以外の方法も紹介されています.
* [How to unnest (explode) a column in a pandas DataFrame? - Stack Overflow](https://stackoverflow.com/questions/53218931/how-to-unnest-explode-a-column-in-a-pandas-dataframe)


```python3
df_nested = pd.DataFrame({'x': [1, 2], 'y': [df, df]})
```

`iloc` で要素 (=データフレーム) を取り出せます.


```python3
df_nested.iloc[0, 1]
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>      <th>x</th>      <th>y</th>      <th>col</th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>0.25</td>
      <td>A</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-0.14</td>
      <td>0.35</td>
      <td>B</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.65</td>
      <td>-0.68</td>
      <td>C</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1.52</td>
      <td>0.23</td>
      <td>D</td>
    </tr>
    <tr>
      <th>4</th>
      <td>-0.23</td>
      <td>0.29</td>
      <td>E</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>145</th>
      <td>0.78</td>
      <td>-0.69</td>
      <td>A</td>
    </tr>
    <tr>
      <th>146</th>
      <td>-1.24</td>
      <td>0.90</td>
      <td>B</td>
    </tr>
    <tr>
      <th>147</th>
      <td>-1.32</td>
      <td>0.31</td>
      <td>C</td>
    </tr>
    <tr>
      <th>148</th>
      <td>0.52</td>
      <td>0.81</td>
      <td>D</td>
    </tr>
    <tr>
      <th>149</th>
      <td>0.30</td>
      <td>0.63</td>
      <td>E</td>
    </tr>
  </tbody>

</table>
</div>

`explode` は正確には `unnest` と対応する処理ではありませんが, 場合によっては代用になります.


```python3
df_nested = pd.DataFrame({'a': [1, 2], 'y': [['A', 1], ['B', 2]]})
df_nested
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>      <th>a</th>      <th>y</th>    </tr>
  </thead>
  <tbody>
    <tr>      <th>0</th>      <td>1</td>      <td>[A, 1]</td>    </tr>
    <tr>      <th>1</th>      <td>2</td>      <td>[B, 2]</td>    </tr>
  </tbody>
</table>
</div>

```python3
df_nested.explode('y')
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>      <th>a</th>      <th>y</th>    </tr>
  </thead>
  <tbody>
    <tr>      <th>0</th>      <td>1</td>      <td>A</td>    </tr>
    <tr>      <th>0</th>      <td>1</td>      <td>1</td>    </tr>
    <tr>      <th>1</th>      <td>2</td>      <td>B</td>    </tr>
    <tr>      <th>1</th>      <td>2</td>      <td>2</td>    </tr>
  </tbody>
</table>
</div>

# メソッドチェーンでできないことをできるように

しかし, 頻繁に必要になるもののメソッドチェーンで完結できない処理というものが pandas ではまだいくつかあります. ここでは, いくつか解消方法を紹介します.

## 列に MultiIndex がある場合

インデックスは `tidyverse` にはない機能でした. pandas に乗り換えたユーザーにとって一番戸惑うのが `index` の扱いだと思います. `grouoby` で集計したら `index`, データフレーム同士を結合したら `index`, `pivot` したら `index`, というように何かにつけて `index` が出てきます. とはいえ, 基本的には `reset_index` メソッドを使えば列に戻すことができるので, さほど問題にはなりません.

厄介なのは, 列名が `MultiIndex` になっている場合です. これは `reset_insex` で解消できません. これはまさに, 先ほど紹介した `pivot` や `unstack` をしたとき, あるいは `agg` で複数の要約関数を適用した場合にも発生します.


```python3
df_test = df.assign(
    row=lambda x: sorted(list(range(x.shape[0] // x.col.nunique())) * x.col.nunique())
).set_index(['row', 'col']).unstack(1).reset_index()
```

列名が `MultiIndex` で入れ子になっている場合, タプルを使って列にアクセスします.

```python3
df_test.columns
```



    MultiIndex([('row',  ''),
                (  'x', 'A'),
                (  'x', 'B'),
                (  'x', 'C'),
                (  'x', 'D'),
                (  'x', 'E'),
                (  'y', 'A'),
                (  'y', 'B'),
                (  'y', 'C'),
                (  'y', 'D'),
                (  'y', 'E')],
               names=[None, 'col'])




```python3
df_test[[('row', ''), ('x', 'A')]]
```




<div>
<table border="1" class="dataframe">
  <thead>
    <tr>      <th></th>      <th>row</th>      <th>x</th>    </tr>
    <tr>      <th>col</th>      <th></th>      <th>A</th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>0.50</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1</td>
      <td>-0.23</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>-0.46</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3</td>
      <td>-0.56</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4</td>
      <td>1.47</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>25</th>
      <td>25</td>
      <td>2.19</td>
    </tr>
    <tr>
      <th>26</th>
      <td>26</td>
      <td>-1.55</td>
    </tr>
    <tr>
      <th>27</th>
      <td>27</td>
      <td>1.55</td>
    </tr>
    <tr>
      <th>28</th>
      <td>28</td>
      <td>0.23</td>
    </tr>
    <tr>
      <th>29</th>
      <td>29</td>
      <td>0.78</td>
    </tr>
  </tbody>

</table>
</div>


すると, 3点の問題が発生します:
1. `df.x` のようにドットで列にアクセスできなくなる
2. `df.filter` での列名マッチがしづらくなる
3. 列を `str` で指定できなくなる

(1) はともかくとして, (2, 3) が問題です. `seaborn` や `plotnine` は便利ですが, タプルでは列を指定できません. これ以外にも文字列だけで列名を指定できたほうが便利な場面は多いです. そこで, タプルを文字列に置き換える必要がでてきますが, この機能をもつビルトインメソッドは存在しません.
この問題に対して, 以下では新しくメソッドを定義する方法が提案されています.

"[Method chaining solution to drop column level in pandas DataFrame](https://stackoverflow.com/questions/40655854/method-chaining-solution-to-drop-column-level-in-pandas-dataframe)"

ここで提案されている方法を少し修正して, メソッドチェーン内で列インデックスの変換をする方法を紹介します.

```python3
def reset_column_index(self, inplace=False,  sep='_'):
    if inplace:
        self.columns = [sep.join(filter(None, tup)) for tup in self.columns]
    else:
        c = self.copy()
        c.columns = [sep.join(filter(None, tup)) for tup in c.columns]
        return c

# 横持ちに変換して multiindex 列を作成
df_wide = df.assign(
    row=lambda x: sorted(list(range(x.shape[0] // x.col.nunique())) * x.col.nunique())
).pivot(index='row', columns='col', values=['x', 'y'])

df_wide.pipe(reset_column_index)
```

<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>x_A</th>
      <th>x_B</th>
      <th>x_C</th>
      <th>x_D</th>
      <th>x_E</th>
      <th>y_A</th>
      <th>y_B</th>
      <th>y_C</th>
      <th>y_D</th>
      <th>y_E</th>
    </tr>
    <tr>
      <th>row</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>-0.14</td>
      <td>0.65</td>
      <td>1.52</td>
      <td>-0.23</td>
      <td>0.25</td>
      <td>0.35</td>
      <td>-0.68</td>
      <td>0.23</td>
      <td>0.29</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-0.23</td>
      <td>1.58</td>
      <td>0.77</td>
      <td>-0.47</td>
      <td>0.54</td>
      <td>-0.71</td>
      <td>1.87</td>
      <td>0.47</td>
      <td>-1.19</td>
      <td>0.66</td>
    </tr>
    <tr>
      <th>2</th>
      <td>-0.46</td>
      <td>-0.47</td>
      <td>0.24</td>
      <td>-1.91</td>
      <td>-1.72</td>
      <td>-0.97</td>
      <td>0.79</td>
      <td>1.16</td>
      <td>-0.82</td>
      <td>0.96</td>
    </tr>
    <tr>
      <th>3</th>
      <td>-0.56</td>
      <td>-1.01</td>
      <td>0.31</td>
      <td>-0.91</td>
      <td>-1.41</td>
      <td>0.41</td>
      <td>0.82</td>
      <td>1.90</td>
      <td>-0.25</td>
      <td>-0.75</td>
    </tr>
    <tr>
      <th>4</th>
      <td>1.47</td>
      <td>-0.23</td>
      <td>0.07</td>
      <td>-1.42</td>
      <td>-0.54</td>
      <td>-0.89</td>
      <td>-0.82</td>
      <td>-0.08</td>
      <td>0.34</td>
      <td>0.28</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>25</th>
      <td>2.19</td>
      <td>-0.99</td>
      <td>-0.57</td>
      <td>0.10</td>
      <td>-0.50</td>
      <td>0.46</td>
      <td>0.20</td>
      <td>-0.60</td>
      <td>0.07</td>
      <td>-0.39</td>
    </tr>
    <tr>
      <th>26</th>
      <td>-1.55</td>
      <td>0.07</td>
      <td>-1.06</td>
      <td>0.47</td>
      <td>-0.92</td>
      <td>0.11</td>
      <td>0.66</td>
      <td>1.59</td>
      <td>-1.24</td>
      <td>2.13</td>
    </tr>
    <tr>
      <th>27</th>
      <td>1.55</td>
      <td>-0.78</td>
      <td>-0.32</td>
      <td>0.81</td>
      <td>-1.23</td>
      <td>-1.95</td>
      <td>-0.15</td>
      <td>0.59</td>
      <td>0.28</td>
      <td>-0.62</td>
    </tr>
    <tr>
      <th>28</th>
      <td>0.23</td>
      <td>1.31</td>
      <td>-1.61</td>
      <td>0.18</td>
      <td>0.26</td>
      <td>-0.21</td>
      <td>-0.49</td>
      <td>-0.59</td>
      <td>0.85</td>
      <td>0.36</td>
    </tr>
    <tr>
      <th>29</th>
      <td>0.78</td>
      <td>-1.24</td>
      <td>-1.32</td>
      <td>0.52</td>
      <td>0.30</td>
      <td>-0.69</td>
      <td>0.90</td>
      <td>0.31</td>
      <td>0.81</td>
      <td>0.63</td>
    </tr>
  </tbody>
</table>
</div>

修正点は2つです. 1つは, 今回では `row` 列のように, 列インデックスが1層しかないものはセパレータ "`_`" がつかないようになっています. もう1つは, メソッドとして与えるのではなく, `.pipe` で与えているという点です. この修正もほとんど個人的な好みの問題なので, 上記のリンクそのままの使い方でもかまいません.

## 再帰的な計算処理
assign, apply でできるのは, element/row/column-wise な処理です. また, `groupby` メソッドならばグループ別集計ができます. 1つ前の要素も参照して処理するようなことはできません. つまり, ラグ演算や累積的な集計処理です. いちおう, pandas にはラグ計算専用のメソッドがいくつか用意されています

* `cumsum` (累積和)
* `cumprod` (累積積)
* `cummax` (累積最大値)
* `cummin` (累積最小値)

なぜか累積平均がありませんが, チートシート[[1]] の `expanding` メソッドを使えば実現できます.

```python3
df.expanding().mean()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>x</th>
      <th>y</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>0.25</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.18</td>
      <td>0.30</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.34</td>
      <td>-0.03</td>
    </tr>
    <tr>
      <th>3</th>
      <td>0.63</td>
      <td>0.04</td>
    </tr>
    <tr>
      <th>4</th>
      <td>0.46</td>
      <td>0.09</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>145</th>
      <td>-0.07</td>
      <td>0.06</td>
    </tr>
    <tr>
      <th>146</th>
      <td>-0.08</td>
      <td>0.06</td>
    </tr>
    <tr>
      <th>147</th>
      <td>-0.09</td>
      <td>0.06</td>
    </tr>
    <tr>
      <th>148</th>
      <td>-0.08</td>
      <td>0.07</td>
    </tr>
    <tr>
      <th>149</th>
      <td>-0.08</td>
      <td>0.07</td>
    </tr>
  </tbody>

</table>
</div>

グループ化にも適用できます.


```python3
df.groupby('col').expanding().mean()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th></th>
      <th>x</th>
      <th>y</th>
    </tr>
    <tr>
      <th>col</th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
 <tbody>
    <tr>
      <th rowspan="5" valign="top">A</th>
      <th>0</th>
      <td>0.50</td>
      <td>0.25</td>
    </tr>
    <tr>
      <th>5</th>
      <td>0.13</td>
      <td>-0.23</td>
    </tr>
    <tr>
      <th>10</th>
      <td>-0.07</td>
      <td>-0.48</td>
    </tr>
    <tr>
      <th>15</th>
      <td>-0.19</td>
      <td>-0.26</td>
    </tr>
    <tr>
      <th>20</th>
      <td>0.14</td>
      <td>-0.38</td>
    </tr>
    <tr>
      <th>...</th>
      <th>...</th>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th rowspan="5" valign="top">E</th>
      <th>129</th>
      <td>-0.34</td>
      <td>0.15</td>
    </tr>
    <tr>
      <th>134</th>
      <td>-0.36</td>
      <td>0.22</td>
    </tr>
    <tr>
      <th>139</th>
      <td>-0.39</td>
      <td>0.19</td>
    </tr>
    <tr>
      <th>144</th>
      <td>-0.37</td>
      <td>0.20</td>
    </tr>
    <tr>
      <th>149</th>
      <td>-0.35</td>
      <td>0.21</td>
    </tr>
  </tbody>
</table>

</div>



移動平均を取りたいならば, `rolling` や, 指数加重移動平均を取る `ewma` メソッドがあります.

"[Exponentially-weighted moving window functions](https://pandas.pydata.org/pandas-docs/version/0.17.0/api.html#exponentially-weighted-moving-window-functions)"

しかし, より一般的な再帰式で表現されるような処理はこれらでは処理できません. つまり, 
$$
x_{t+1} = f(x_t)\\
t=0, 1, 2, ...
$$

のような, 行ごとの再帰的な計算をしたい場合です. これは R の `dplyr` でもできない処理ですが, tidyverse に属する `purrr::accumulate` を組み合わせるとできます.

pandas の場合, $f$ に対応する関数を与えるだけで計算してくれるメソッドは存在しません[^3]. そこで, 簡易的な代替方法として, 以下のような関数を用意します. 以下ならば, 1階差分方程式で表現できる計算ができるはずです.


```python3
def recurrence(x, func):
    x = x.copy()
    for i in range(1, x.shape[0]):
        x[i] = func(x[i-1])
    return x
```

例として, $f(x) = 2(x+1)$ を適用します.

```python3
df.assign(x2=lambda d: recurrence(d['x'], lambda x: (x+1)*2))
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">      <th></th>      <th>x</th>      <th>y</th>      <th>col</th>      <th>x2</th>    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.50</td>
      <td>0.25</td>
      <td>A</td>
      <td>4.97e-01</td>
    </tr>
    <tr>
      <th>1</th>
      <td>-0.14</td>
      <td>0.35</td>
      <td>B</td>
      <td>2.99e+00</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.65</td>
      <td>-0.68</td>
      <td>C</td>
      <td>7.99e+00</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1.52</td>
      <td>0.23</td>
      <td>D</td>
      <td>1.80e+01</td>
    </tr>
    <tr>
      <th>4</th>
      <td>-0.23</td>
      <td>0.29</td>
      <td>E</td>
      <td>3.79e+01</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>145</th>
      <td>0.78</td>
      <td>-0.69</td>
      <td>A</td>
      <td>1.11e+44</td>
    </tr>
    <tr>
      <th>146</th>
      <td>-1.24</td>
      <td>0.90</td>
      <td>B</td>
      <td>2.23e+44</td>
    </tr>
    <tr>
      <th>147</th>
      <td>-1.32</td>
      <td>0.31</td>
      <td>C</td>
      <td>4.45e+44</td>
    </tr>
    <tr>
      <th>148</th>
      <td>0.52</td>
      <td>0.81</td>
      <td>D</td>
      <td>8.91e+44</td>
    </tr>
    <tr>
      <th>149</th>
      <td>0.30</td>
      <td>0.63</td>
      <td>E</td>
      <td>1.78e+45</td>
    </tr>
  </tbody>
</table>
</div>



# ラムダが多すぎる
ここまで, 参照透過性のため `lambda` を多用してきましたが, そもそも `dplyr` では `lambda` すら必要ないため, pandas ではコードが冗長になりがちです. そこで, `dplyr` 風の構文を python 上で再現することを目指した `dfply` を紹介します.

https://github.com/kieferk/dfply

日本語での使用法の解説は, 最初に挙げた参考記事 [[4]] があります.

見てわかるように, メソッド名が `dplyr` とそっくりで, パイプ演算子も `>>` として再現しています. 一方で, 現時点での `dfply` の問題点として, 

1. `pandas.DataFrame` ではない独自のクラスを返すため, `dfply` のメソッドで処理を完結しなければならない
2. すべて python で書かれているため, (`pandas` に比べ) さほど早くない[^4].
3. Python の構文上, `>>` の直後に改行することができない (コードが見づらくなる)

という点が挙げられます.

具体的な操作方法は, `dplyr` をすでに使っているのなら [[4]] を読めばあとは直感でできるので, ここでは詳しい説明を省略します.
以前 `dfply` を見つけたときは使いやすそうだと思いましたが, `pandas` のほうも `lambda` の利用や 0.25 以降の新機能によりだいぶ使いやすくなったので, 個人的にはそこまでして使う必要はないかな, という考えに移りつつあります. 特に大きなデータを扱う場合は.



[1]:https://qiita.com/s_katagiri/items/4cd7dee37aae7a1e1fc0
[2]:https://qiita.com/piroyoung/items/dd209801ca60a0b00c11
[3]:https://qiita.com/mwmsnn/items/6a464865759231aa888d
[4]:https://qiita.com/T_Shinomiya/items/c039451869382e0790f4

[^1]: https://speakerdeck.com/yutannihilation/tidyr-pivot
[^2]: https://blog.atusy.net/2019/06/29/pivoting-tidyr-1-0-0/
[^dotbrace]: 典型例は `.reset_index()` で `index` という列が作られたときです. `df.index` では通常列名ではなくメンバの `index` が呼び出されてしまいます.
[^melt]: インターネット上のスニペットでは `pd.melt` を使っている例も多いですが, 0.20 以降はデータフレームのメソッドとしても呼び出せます. 
[^3]: Issues では「そのうち追加したい」機能という程度の優先順位に位置づけられています. https://github.com/pandas-dev/pandas/issues/4567
[^4]: すべて確認したわけではないですが, たとえば pandas の `sort_values` と dfply の `arrange` とでは, 後者のほうが顕著に時間がかかります. 余計なコピーが発生しているため?