[pandas]よくわかんないけど便利なapply, groupbyサンプル

# はじめに
挙動が分かりづらいが、なんだかんだ便利な時があるpandasのgroupbyのコード例をいくつか記載しておきます（備忘兼ねて）
※19/10/27 修正しました

# サンプルデータ作成
結果

```python
# check
df
"""
   x1  x2  gender young_old
0   3   5  female     young
1   8  10    male       old
2   2   7    male       old
3   9   0  female       old
4  11   6  female     young
5   1   4    male     young
"""

```

コード

```python
import pandas as pd
import numpy as np

# 値
arr = np.arange(12)

# セグメント用
gender = ['male','female'] * 3
young_old = ['young','old'] * 3

# シャッフル
np.random.shuffle(arr)
np.random.shuffle(gender)
np.random.shuffle(young_old)

# 値のデータフレーム
df = pd.DataFrame(data=arr.reshape(6,2),
                  columns=['x1','x2']
                  )
# セグメントデータ追加
df['gender'] = gender
df['young_old'] = young_old
```

# よくわかんないけど便利なgroupbyコード例
3種類記載

## その3 複数カラム使って条件分岐（新バージョン）
※@konandoiruasaさんよりアドバイスコメント頂き修正しました

結果

```python
# age_genderカラムを、genderとyoung_oldをもとに生成
tmp[['gender', 'young_old', 'age_gender']]
   gender young_old age_gender
0    male       old     オールドマン
1  female     young       女性全般
2    male     young      ヤングマン
3    male     young      ヤングマン
4  female       old       女性全般
5  female       old       女性全般
```

コード

```python
def my_func_switch3(srs):
    # 1行がSeriesになったものを受け取る
    if srs['gender']=='male':
        if srs['young_old']=='young':
            return 'ヤングマン'
        elif srs['young_old']=='old':
            return 'オールドマン'
    else:
        return '女性全般'

tmp = df.copy()  # なくてもいい。ここではdfを書き換えたくないのでやってる
tmp['age_gender'] = tmp.apply(my_func_switch3, axis=1)
```

## その3 複数カラム使って条件分岐（旧バージョン）
※こちらは無駄の多い旧バージョン、コメント頂き上記新バージョンへ変更

結果

```python
# 他カラムからage_genderを生成した（単純な文字列結合処理ではない）
# check
tmp[['gender','young_old','age_gender']]
"""
   gender young_old    age_gender
0  female     young  young_female
1    male       old      old_male
2    male       old      old_male
3  female       old    old_female
4  female     young  young_female
5    male     young    young_male
"""
```

コード

```python
def my_func_switch(xdf):
    # 1行になる時だけちゃんと動く これ外して動いても意図した計算にならないと思うのでやらない方がいい
    if xdf.shape[0]>1:raise('xdf.shape[0]==1のみ有効')
    # 条件分岐
    if xdf['gender'].values[0]=='male':
        if xdf['young_old'].values[0]=='young':
            return 'young_male'
        elif xdf['young_old'].values[0]=='old':
            return 'old_male'
    elif xdf['gender'].values[0]=='female':
        if xdf['young_old'].values[0]=='young':
            return 'young_female'
        elif xdf['young_old'].values[0]=='old':
            return 'old_female'

tmp = df.copy()  # なくてもいい。ここではdfを書き換えたくないのでやってる
tmp['dummy'] = np.arange(6)  # ユニークな値を持つカラムを作る
tmp['age_gender'] = (tmp
                       .groupby('dummy')
                       .apply(my_func_switch)
                       .values
                       )
```

## その2 データフレームを受け取って複数カラム使う
結果

```python
# flgカラムを生成（x1<x2の判定結果）
# check
tmp[['x1', 'x2', 'flg']]
"""
   x1  x2    flg
0   3   5   True
1   8  10  False
2   2   7  False
3   9   0   True
4  11   6   True
5   1   4   True
"""
```

コード

```python
tmp = df.copy()  # なくてもいい。ここではdfを書き換えたくないのでやってる
tmp['flg'] = (tmp
               .groupby('gender')
               .apply(lambda xdf: xdf['x1'] < xdf['x2'])
               .values
               )
```

## その1 x/x.max()みたいな
結果

```python
# genderごとに最大値を算出し、最大値に対する割合のカラムを生成
# check
tmp[['x1','gender','genderごとのmaxに対する割合']]
"""
  x1  gender  genderごとのmaxに対する割合
0   3  female            0.272727
1   8    male            1.000000
2   2    male            0.250000
3   9  female            0.818182
4  11  female            1.000000
5   1    male            0.125000
"""
```

コード

```python
a = (df
     .groupby('gender')
     ['x1']
     .apply(lambda x: x/ x.max())
     )
# check
a
"""
0    0.272727
1    1.000000
2    0.250000
3    0.818182
4    1.000000
5    0.125000
Name: x1, dtype: float64
"""

tmp = df.copy()
tmp['genderごとのmaxに対する割合'] = a
```

# シンプルなgroupbyコード例
こちらは比較的シンプルなもの（当社比）
下記の内容です
・シンプル？なgroupby_shift
・シンプルなgroupby_apply&lambda利用
・シンプルなgroupby_apply&自作関数利用（引数あり）
・シンプルなgroupby_apply&自作関数利用（引数なし）
・シンプルなgroupby_apply&関数利用
・シンプルなgroupby_agg
・シンプルなgroupby 複数カラムをキーに
・シンプルなgroupby

```python
# シンプル？なgroupby_shift
tmp = df.copy()
tmp['shifted'] = (tmp
                 .groupby('gender')
                 ['x1']
                 .shift(-1)
                 .values
                 )
# check
tmp[['x1','gender','shifted']]
"""
   x1  gender  shifted
0   3  female      9.0
1   8    male      2.0
2   2    male      1.0
3   9  female     11.0
4  11  female      NaN
5   1    male      NaN
"""


# シンプルなgroupby_apply&lambda利用
(df
 .groupby('gender')
 ['x1']
 .apply(lambda srs: srs.max())
 .reset_index()
)
# check
"""
   gender  x1
0  female  11
1    male   8
"""


# シンプルなgroupby_apply&自作関数利用（引数あり）
def my_func_range(srs, ratio=1):
    range_ = srs.max() - srs.min()
    return range_ * ratio

(df
 .groupby('gender')
 ['x1']
 .apply(my_func_range, 0.5)
 .reset_index()
)
# check
"""
   gender   x1
0  female  2.5
1    male  1.5
"""


# シンプルなgroupby_apply&自作関数利用（引数なし）
def my_func_range(srs):
    range_ = srs.max() - srs.min()
    return range_

(df
 .groupby('gender')
 ['x1']
 .apply(my_func_range)
 .reset_index()
)
# check
"""
   gender  x1
0  female   5
1    male   3
"""


# シンプルなgroupby_apply&関数利用
(df
 .groupby('gender')
 ['x1']
 .apply(np.max)
 .reset_index()
)
# check
"""
   gender  x1
0  female  11
1    male   8
"""
# おまけ） 計算前にどのように値が分けられるか確認
# check
df.groupby('gender').groups
"""
{'female': Int64Index([0, 3, 4], dtype='int64'),
 'male': Int64Index([1, 2, 5], dtype='int64')}
"""


# シンプルなgroupby_agg
(df
 .groupby('gender')
 .agg({'x1':np.max, 'x2':np.max})
 .reset_index()
)
# check
"""
 gender  x1  x2
0  female  11   6
1    male   8  10
"""


# シンプルなgroupby 複数カラムをキーに
(df
 .groupby(['gender','young_old'])
 ['x1']
 .max()
 .reset_index()
)
# check
"""
   gender young_old  x1
0  female       old   9
1  female     young  11
2    male       old   8
3    male     young   1
"""


# シンプルなgroupby
(df
 .groupby('gender')
 ['x1']
 .max()
 .reset_index()
)
# check
"""
gender  x1
0  female  11
1    male   8
"""
```

# 参考ページ
groupbyの基本はこちらに良くまとまっています。素敵なページです。
https://qiita.com/propella/items/a9a32b878c77222630ae

終わり