[python / pandas] DataFrame を扱う人が覚えておきたい、ちょっとレアな便利技16選

# 概要

- そこまでメジャーではない（？）
- けど、覚えておくと実装時間やコードの行数を大幅削減できる！

という便利な技をご紹介します！


「そういえば`pandas`ってあんなこともできたような気がするな。」
「自力で実装する前に調べてみようかな？」

と気付けると、時短 & コード量削減できる可能性が生まれます。
ではでは、お楽しみください！！


# Environment

以下の環境で動作確認を行いました。

項目|version など
--|--
OS|Ubuntu 22.04.3 LTS (Google Colaboratory)
Python|3.10.12
pandas|1.5.3

# 便利技たち！

## 0. 一覧

下に行けば行くほど、珍しく、使い道がよくわからなくなっていきます😂
これ見たことないな〜、っていうところだけでも見ていっていただけると嬉しいです^^

※「レア度」は、星が多くなるほど珍しい、もしくは難解な処理であることを意味しています。（独断と偏見により設定）


|No|レア度|メソッド|
|:--|:---|:----|
|1 |★    |[df.isin()](#1-1-isin---複数候補に対する完全一致判定)|
|2 |★    |[df.columns = ["列名", ..]](#1-2-dfcolumns----カラム名を一気にrename)|
|3 |★    |[df.between( , )](#1-3-dfbetween-----2つの引数でレコードを絞り込む)|
|4 |★★   |[df.to_dict(orient="records")](#2-1-to_dictorientrecords---dataframe-に対する-loop-を高速化)|
|5 |★★   |[pd.to_numeric(df["列名"], errors="coerce").notna()](#2-2-to_numericseries--errorscoerce---数値型に変換できない値を回避)|
|6 |★★   |[df.apply(func, axis=1)](#2-3-applyfunc-axis1---1行ごとに関数を実行)|
|7 |★★★  |[df.groupby()["列名"].size()](#3-1-groupby-と-size-のコンボ)|
|8 |★★★  |[df.groupby()["列名"].head(n)](#3-2-groupbyheadn---グループ別に先頭から-n-件データ抽出)|
|9 |★★★  |[df.filter(like="部分列名")](#3-3-filter---カラム名に対する部分一致検索)|
|10|★★★★ |[set_index(["列名"], append=True)](#4-1-set_index-appendtrue---multiindex化)|
|11|★★★★ |[df.groupby()["列名"].rank()](#4-2-groupby-と-rank-のコンボ)|
|12|★★★★ |[df.groupby()["列名"].shift()](#4-3-groupby-と-shift---グループごとに-n-行ずらす)|
|13|★★★★ |[df.groupby()["列名"].transform()](#4-4-groupby-と-transform)|
|14|★★★★★|[df["列名"].apply(pd.Series)](#5-1-df列名applypdseries)|
|15|★★★★★|[df.explode()](#5-2-explode---爆ぜろ)|
|16|★★★★★|[df.unstack()](#5-3-unstack---潰れろ)|



## 1. レア度:★

- ごく簡単に実装できるもの
- もしくは、頻出メソッド

### 1-1. isin - 複数候補に対する完全一致判定

動作確認のために、以下のようなサンプルデータを用意します。

```python:colaboratory
df = pd.DataFrame({
    "item_name": [
        "松屋サイダー",
        "pakemon card",
        "パリウス",
        "あいぽん12",
        "イーロン茶"
    ],
    "price": [
        140,
        350,
        4_000_000,
        150_000,
        120
    ]
})
df

# 結果
	item_name       price
0	松屋サイダー         140
1	pakemon card      350
2	パリウス         4000000
3	あいぽん12       150000
4	イーロン茶　          120
```

あなたは今、上記 `df` という `DataFrame` の中から飲み物 ("イーロン茶"、"松屋サイダー"、"QQレモン" のうちいずれか) が欲しいとします。
こういう時に `isin` を使うのです！

```python:colaboratory
drink_names = ["イーロン茶", "松屋サイダー", "QQレモン"]

# 複数の候補に対して、完全一致判定をする
drink_index = df["item_name"].isin(drink_names)
df_extracted: pd.DataFrame = df[drink_index]
df_extracted

# 見事「飲み物」をゲットした！
	item_name	price
0	松屋サイダー  140
4	イーロン茶    120
```

この結果は、0行目と4行目だけが `["イーロン茶", "松屋サイダー", "QQレモン"]` のどれかと一致したということ。

ちなみに `drink_index` の中身はこうなってる。

```python:colaboratory
drink_index

# 以下の通り
0     True
1    False
2    False
3    False
4     True
Name: column_name1, dtype: bool
```

`True` 行だけが `DataFrame` から抽出される仕組みなりぃ。


**関連記事**

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.isin.html#pandas-dataframe-isin


### 1-2. df.columns = - カラム名を一気にrename

今度は、 DataFrame の列名をまとめて全部書き換えるときに使えるワザです。

```python:colaboratory
df = pd.DataFrame({
    "item_name": [
        "松屋サイダー",
        "pakemon card",
        "パリウス",
        "あいぽん12",
        "イーロン茶"
    ],
    "price": [
        140,
        350,
        4_000_000,
        150_000,
        120
    ]
})
df

# 結果
	item_name     price
0	松屋サイダー      140
1	pakemon card    350
2	パリウス      4000000
3	あいぽん12     150000
4	イーロン茶　        120
```


さっきのこいつの、列名を書き換えてしまいましょう。

```python:colaboratory
# えいっ！と代入
df.columns = ["商品名", "価格"]
df

# こうなる
	商品名           価格
0	松屋サイダー       140
1	pokemon card     350
2	パリウス       4000000
3	あいぽん12      150000
4	イーロン茶          120
```

真面目に1列ずつ `df.rename(columns={"item_name": "商品名", "price": "価格"})` してあげても良いんですけどね。 `columns` に直代入した方が楽。

### 1-3. df.between( , ) - 2つの引数でレコードを絞り込む

別に、日付のデータである必要はないのですが、わかりやすいので日付の文字列でサンプルを示します。

```python:colaboratory
# サンプルデータ
df: pd.DataFrame = pd.DataFrame(
    {
        "date": [
            "2023-12-03",
            "2023-12-04",
            "2023-12-05",
            "2023-12-06",
            "2023-12-07",
            "2023-12-08",
            "2023-12-09",
        ]
    }
)
```

これを、 `between` で絞り込んでみよう！

```python:colaboratory
# between の両端に挟まれるデータを抽出
df[df['date'].between("2023-12-05", "2023-12-08")]

# 実行結果
	date
2	2023-12-05
3	2023-12-06
4	2023-12-07
5	2023-12-08
```

ちなみに、単純に `between` を実行した直後はこうなってるよ。

```python:colaboratory
df['date'].between("2023-12-05", "2023-12-08")

# 実行結果
0    False
1    False
2     True
3     True
4     True
5     True
6    False
Name: date, dtype: bool
```

この結果を見ると、引数に**指定した値と完全一致するレコードも `True` で返ってくる**みたいですね。


## 2. レア度:★★

- まぁまぁ簡単に使えるけど、あんまり見かけない。

### 2-1. to_dict(orient="records") - DataFrame に対する loop を高速化

今は `numpy` や `pandas` がありますし、できれば `loop` せずにベクトル演算、ブロードキャスト演算したいですね。

でも、どうしても大量の loop を回したい時はこうします。

```python:colaboratory
from typing import List


dict_data: List[dict] = df.to_dict(orient="records")
for dict in dict_data:
    # loop しないといけない処理

# from_dict で DataFrame に戻す
df_converted: pd.DataFrame = pd.from_dict(dict_data)
```

こうしないと、 `DataFrame` の loop は結構遅いんですよ|･`ω･´)ﾁﾗﾘｯ
(なんと**400倍**もの速度差になったりします)

**関連記事**

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_dict.html#pandas-dataframe-to-dict

https://qiita.com/siruku6/items/4846431198769b38bb41



### 2-2. to_numeric(series , errors="coerce") - 数値型に変換できない値を回避

たとえば以下のような DataFrame の `"numeric?"` 列を float 型に変換したいとします。

```python:colaboratory
df = pd.DataFrame(
    {
        "numeric?": [1, 2, "3", "4", "5.0", ".1", "l"],
        "alpha": ["a", "b", "c", "d", "e", "f", "g"]
    }
)

# 実行結果
	numeric?  alpha
0	1         a
1	2         b
2	3         c
3	4         d
4	5.0       e
5	.1        f
6	l         g
```


実行すると、最終行の "l" (エル) が float 型に変換できない値だったため、エラーになります。

```python:colaboratory
df["numeric?"].astype(float)

# 実行結果
ValueError: could not convert string to float: 'l'
```

このような大量のデータがある時に、 `to_numeric` を使うと、うまく float に変換できる値だけを取り出すことができます。

こんな感じ！

```python:colaboratory
pd.to_numeric(df["numeric?"], errors="coerce").notna()

# 結果
0     True
1     True
2     True
3     True
4     True
5     True
6    False
Name: numeric?, dtype: bool
```

これで6行目が数値に変換できないとわかったので、除外するのも簡単ですね(\*´ｖ｀)

```python:colaboratory
# あとはこうする
astypable_indices = pd.to_numeric(df["numeric?"], errors="coerce").notna()
df.loc[astypable_indices, "numeric?"].astype(float)

# 実行結果
0    1.0
1    2.0
2    3.0
3    4.0
4    5.0
5    0.1
Name: numeric?, dtype: float64
```


**関連記事**

https://max999blog.com/python-pandas-is-numeric/


### 2-3. apply(func, axis=1) - 1行ごとに関数を実行

DataFrame 内の1行1行を、1つのSeriesとして関数の引数に渡して処理したいときに使います。

たとえば、以下のような関数に1つの Series を渡してみます。

```python:colaboratory
import pandas as pd


# apply メソッドのサンプル
def sample_concat_str(series: pd.Series) -> str:
    """
    pd.Series を受け取り、 series 内の文字列を全て連結して返却

    Example
    ------
    >>> sample_series = pd.Series(["123", "456", "789"])
    >>> sample_concat_str(sample_series)
    "123-456-789"
    """
    return "-".join(list(series["list"]))


df: pd.DataFrame = pd.DataFrame({
    "dummy": [1, 2, 3],
    "list": [
        ["abc", "def"],
        ["123", "456", "789"],
        ["hoge"]
    ]
})

series: pd.Series = df.iloc[1]
series

# series の中身
dummy                  2
list     [123, 456, 789]
Name: 1, dtype: object
```

この `series` を関数に渡すと以下のようになります。

```python:colaboratory
sample_concat_str(series)

# 結果
'123-456-789'
```

これを、Series 1個だけではなくて、 DataFrame にある分全部に対して実行させたい場合に以下のようにします。

```python:colaboratory
df.apply(sample_concat_str, axis=1)

# 実行結果
0        abc-def
1    123-456-789
2           hoge
dtype: object
```

`apply` をもっと幅広く応用したい方には、以下の記事もおすすめ( \*¯ ꒳¯\*)✨

https://qiita.com/yubiquita/items/6d5966e8e10904076230



## 3. レア度:★★★

- 見かけることははっきりいってほとんどないですが、比較的動作が理解しやすい / イメージしやすい処理
- 覚えておくと、活用できるチャンスはきっと廻ってくる（気がする

### 3-1. groupby と size のコンボ

SQL で言うところの、 `GROUP BY` 結果に対する `COUNT(<列名>)` が実行できます！
普通に使う機会があると思うので、覚えておいて損はないです。

```python:colaboratory
df.groupby(["GROUP BY の対象列", "対象列2", ...])["COUNT したい列名"].size()

# Example
#    「各ユーザーが各商品を何回買ったのか」を知りたい時...
df.groupby(["username", "購入商品"])["購入商品"].size()
```

実際の使い方はこんな感じ。

```python:colaboratory
df = pd.DataFrame(
    {
        "username": [
            "たけち",
            "マティス",
            "かつみ",
            "マティス",
            "ありか",
            "ありか",
            "たけち",
            "たけち",
            "ありか",
            "マティス",
        ],
        "購入商品": [
            "キズサソリ",
            "おいしいおゆ",
            "アドバンテージボール",
            "おいしいおゆ",
            "おいしいおゆ",
            "たべかけ",
            "キズサソリ",
            "キズサソリ",
            "たべかけ",
            "アドバンテージボール"
        ]
    }
)
df

# 実行結果
	username	購入商品
0	たけち       キズサソリ
1	マティス	    おいしいおゆ
2	かつみ       アドバンテージボール
3	マティス      おいしいおゆ
4	ありか       おいしいおゆ
5	ありか       たべかけ
6	たけち       キズサソリ
7	たけち       キズサソリ
8	ありか       たべかけ
9	マティス      アドバンテージボール
```

ここで、誰が何を何個買ったかを調べるとき、これでOK！

```python:colaboratory
df.groupby(["username", "購入商品"])["購入商品"].size()

# 実行結果
username  購入商品      
ありか     おいしいおゆ         1
         たべかけ            2
かつみ    アドバンテージボール   1
たけち    キズサソリ           3
マティス   おいしいおゆ         2
         アドバンテージボール   1
Name: 購入商品, dtype: int64
```


### 3-2. groupby().head(n) - グループ別に先頭から n 件データ抽出

以下のようなマラソンの順位表を考えます。

```python:colaboratory
df: pd.DataFrame = pd.DataFrame(
    [
        {"名前": "桔梗", "性別": "female", "time": "04:58"},
        {"名前": "雅人", "性別": "male",   "time": "05:02"},
        {"名前": "真由", "性別": "female", "time": "05:15"},
        {"名前": "慎二", "性別": "male",   "time": "05:32"},
        {"名前": "達人", "性別": "male",   "time": "05:41"},
        {"名前": "祥平", "性別": "male",   "time": "06:12"}
    ]
)
df

# 実行結果
	名前	性別	time
0	桔梗	female	04:58
1	雅人	male	05:02
2	真由	female	05:15
3	慎二	male	05:32
4	達人	male	05:41
5	祥平	male	06:12
```

この順位表から、性別ごとに（男女別々に）タイムの短かった方を2人ずつ抜き出したいとしましょう。

これで行けます！

```python:colaboratory
df.groupby(["性別"])[["名前", "性別", "time"]].head(2)

# 実行結果
	名前	性別	time
0	桔梗	female	04:58
1	雅人	male	05:02
2	真由	female	05:15
3	慎二	male	05:32
```

この応用として、例えば「地区」列などがあったときに、「地区ごと」かつ「性別ごと」に上位 n 件を抜き出すこともできるので、結構応用範囲は広いです。


**関連記事**

https://blog.shikoan.com/pandas-groupby-head/#%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%94%E3%83%B3%E3%82%B0%E3%81%97%E3%81%A6%E6%8A%9C%E3%81%8D%E5%87%BA%E3%81%99%E3%81%A8%E3%81%8D%E3%81%AFhead


### 3-3. filter - カラム名に対する部分一致検索

こいつは超便利です！
絶対に頭の片隅に入れておいた方がいい！

こんな DataFrame があったとき、

```python:colaboratory
df = pd.DataFrame({
    "feat_1": np.arange(0, 5),
    "feat_2": np.arange(0, 5),
    "feat_3": np.arange(0, 5),
    "feat_4": np.arange(0, 5),
    "feat_5": np.arange(0, 5),
    "label_1": np.arange(0, 5),
    "label_2": np.arange(0, 5),
    "label_3": np.arange(0, 5)
})
df

# 実行結果
   feat_1  feat_2  feat_3  feat_4  feat_5  label_1  label_2  label_3
0       0       0       0       0       0        0        0        0
1       1       1       1       1       1        1        1        1
2       2       2       2       2       2        2        2        2
3       3       3       3       3       3        3        3        3
4       4       4       4       4       4        4        4        4
```

ここから、列名に "feat_"  が入っている列だけを取得したいとしましょう。

愚直に `df[["feat_1", "feat_2", ...]]` と書いてもいいのですが、以下のようにするともっと簡単に取得できます。

```python:colaboratory
df.filter(like="feat_")

# 実行結果
   feat_1  feat_2  feat_3  feat_4  feat_5
0       0       0       0       0       0
1       1       1       1       1       1
2       2       2       2       2       2
3       3       3       3       3       3
4       4       4       4       4       4
```

これ、覚えておいて損ないですよね？(\*´▽｀\*)
これぞ時短＆バグの削減ですよ。


**関連記事**

https://datasciencemore.com/python-pandas-filter/


## 4. レア度:★★★★

- 初見だと、何が起こるか全くイメージできない
- 理解して使えるようになっておくと、処理を自分で実装しなくて済むので、非常に時短になる
- 使うチャンスはもしかしたらないかもしれないけど、、、もしその時が来たらかなり役に立つ

### 4-1. set_index([], append=True) - MultiIndex化

- MultiIndex って、作り方あんまりイメージが沸かないんですけど、、、これはそこそこ覚えやすいです。
- `set_index` はみなさん使ってると思うんですけど、ポイントは `append=True` です。

```python
df.set_index(["index に追加したい column 名"], append=True)
```

`MultiIndex` に良く出くわす方には、ぜひ覚えておいてほしい珠玉の一品(?)です。

実際の使い方はこんな感じです（サンプルデータがしょぼくてすみません｡ﾟ･(´^ω^｀)･ﾟ｡）

```python:colaboratory
df = pd.DataFrame({
    "date": ["2023-12-31", "2023-01-01", "2023-01-02"],
    "腕立て伏せの回数": [197, 256, 0],
})
df

# 実行結果
	date	腕立て伏せの回数
0	2023-12-31	        197
1	2023-01-01	        256
2	2023-01-02	          0
```

この DataFrame の date 列を index に追加して、 MultiIndex な DataFrame を作りましょう。

これでOK！

```python:colaboratory
df.set_index(["date"], append=True)

# 実行結果
		        腕立て伏せの回数
          date	
0	2023-12-31	          197
1	2023-01-01	          256
2	2023-01-02	            0
```


### 4-2. groupby と rank のコンボ

グループごとに、特定の列の値の順位を返却してくれます。

```python
df.groupby(["GROUP BY の対象列", "対象列2", ...])["順位を知りたい列名"].rank()

# Example
#    「各ユーザー×商品のグループごとの、購入個数ランキング」が欲しい時...
df.groupby(["user_id", "商品名"])["購入個数"].rank(ascending=False)
```

更に具体的にを見てみましょう。

```python:colaboratory
df = pd.DataFrame(
    {
        "username": [
            "たけち",
            "ありか",
            "ありか",
            "たけち",
            "たけち",
            "ありか",
        ],
        "購入商品": [
            "キズサソリ",
            "おいしいおゆ",
            "おいしいおゆ",
            "おいしいおゆ",
            "キズサソリ",
            "おいしいおゆ",
        ],
        "購入個数": [
            12,
            1,
            10,
            6,
            4,
            2,
        ]
    }
)

df

# 出力
	username  購入商品    購入個数
0	たけち	  キズサソリ	     12
1	ありか      おいしいおゆ       1
2	ありか      おいしいおゆ      10
3	たけち      おいしいおゆ       6
4	たけち      キズサソリ        4
5	ありか      おいしいおゆ       2
```

このようなデータに対して適用すると、以下のように順位を出力してくれます。


```python:colaboratory
df.groupby(["username", "購入商品"])["購入個数"].rank(method="first", ascending=False)

# 出力
0    1.0
1    3.0
2    1.0
3    1.0
4    2.0
5    2.0
Name: 購入個数, dtype: float64
```

何が嬉しいのかちょっとわかりにくいので、元のデータフレームに代入してみましょう。
こうなります。

```python:colaboratory
df["購入数順位"] = (
    df.groupby(["username", "購入商品"])["購入個数"]
    .rank(method="first", ascending=False)
)
df.sort_values(by=["username", "購入数順位"])

# 出力結果
	username  購入商品    購入個数	購入数順位
2	ありか    おいしいおゆ        10      1.0
5	ありか    おいしいおゆ         2      2.0
1	ありか    おいしいおゆ         1      3.0
3	たけち    おいしいおゆ	      6      1.0
0	たけち    キズサソリ         12      1.0
4	たけち    キズサソリ          4      2.0
```

`sort_values` で、グループ化した列名と、ランキングが入力されている列を指定すると、人目にも理解しやすい結果が出力されますよ(\*´ｖ｀)
どなたかの役に立てれば嬉しいです。

これって、SQLだと何に当たるのかな...?


**関連記事**

https://pandas.pydata.org/pandas-docs/version/1.0.0/reference/api/pandas.core.groupby.DataFrameGroupBy.rank.html

### 4-3. groupby() と shift() - グループごとに n 行ずらす

これも使いどころがちょっと難しいのですが、たとえば、先程のような商品購入ログであれば、特定ユーザが同名商品を「前回購入した日」をリストに追加したいときなどに使えます。

サンプルデータはこんな感じ。

```python:colaboratory
df = pd.DataFrame(
    {
        "username": [
            "たけち",
            "ありか",
            "ありか",
            "たけち",
            "たけち",
            "ありか",
        ],
        "購入商品": [
            "キズサソリ",
            "おいしいおゆ",
            "おいしいおゆ",
            "おいしいおゆ",
            "キズサソリ",
            "おいしいおゆ",
        ],
        "購入日": [
            "2023-12-01",
            "2023-12-03",
            "2023-12-05",
            "2023-12-05",
            "2023-12-07",
            "2023-12-10",
        ]
    }
)

df

# 出力
	username 購入商品      購入日
0	たけち   キズサソリ    2023-12-01
1	ありか   おいしいおゆ   2023-12-03
2	ありか   おいしいおゆ   2023-12-05
3	たけち   おいしいおゆ   2023-12-05
4	たけち   キズサソリ    2023-12-07
5	ありか   おいしいおゆ   2023-12-10
```

このデータに対して、「前回購入日」列を追加します。

```python:colaboratory
df["前回購入日"] = (
    df.groupby(["username", "購入商品"])["購入日"]
    .shift(1)
)
df.sort_values(by=["username", "購入商品", "購入日"])

# 出力
	username  購入商品   購入日        前回購入日
1	ありか    おいしいおゆ   2023-12-03   NaN
2	ありか    おいしいおゆ   2023-12-05   2023-12-03
5	ありか    おいしいおゆ   2023-12-10   2023-12-05
3	たけち    おいしいおゆ   2023-12-05   NaN
0	たけち    キズサソリ    2023-12-01   NaN
4	たけち    キズサソリ    2023-12-07   2023-12-01
```

こんな感じで、前回購入した日付を一気に出力できます。
`shift` は、通常の DataFrame に対する shift と同じで負の値や2以上の値も入れられるので、もしかしたらもっと活用できる価値があるかも。


**関連記事**

以下の記事によると、処理速度的にも申し分ない早さみたいですよ(\*´ｖ｀)

https://qiita.com/ground0state/items/69480e4d0739045b0681#%E6%96%B9%E6%B3%955


### 4-4. groupby() と transform()

`groupby` がめっちゃ出てきているのでついでに紹介するぜ！(\*´▽｀\*)
しかし、これも使いどころが難しい。。。

以下の記事がわかりやすいので、これはこの記事に解説をお願いしたい(o*。_。)o

https://qiita.com/greenteabiscuit/items/132e0f9b1479926e07e0


## 5. レア度:★★★★★

- ハッキリ言って、いつ使うのか全くイメージが沸かない...( ˘·ω·˘ ).。oஇ
- でも、自分で実装しようとしたら大変手間のかかる処理を、関数で一発で実現できるので大助かり！
- 使い方は忘れてもいいけど、「そういえば、`pandas` で用意されているメソッドであんなことできたはずだよね...」って思い出せれば、かなりアドバンテージになります！



### 5-1. df["列名"].apply(pd.Series)

Series の 1要素内に list型 が入っている際に、それらを展開してくれる。

たとえば、こんな困ったデータがあったとする。

```python:colaboratory
df = pd.DataFrame({
    "a": [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]],
    "b": [["a", "b", "c", "d", "e"], ["f", "g", "h", "i", "j"]]
})

# 出力
    a                 b
0	[1, 2, 3, 4, 5]	  [a, b, c, d, e]
1	[6, 7, 8, 9, 10]  [f, g, h, i, j]
```

"a" 列にも "b" 列にも、1要素にまるまるリスト型が入り込んでいる。
これをなんとかして、1要素につき値は一つだけ、という形に変換したい。
そんな時にこうする。


```python
df["b"].apply(pd.Series)

# 実行結果
	0	1	2	3	4
0	a	b	c	d	e
1	f	g	h	i	j
```

上記は "b" 列を展開した場合の話。 "a" 列も展開したければ、別途 `.apply(pd.Series)` してあげるとよい。

**関連記事**

https://qiita.com/pm00/items/8c2b4515301809b62f4c#%E3%82%B3%E3%83%BC%E3%83%89


#### 「5-1.」の補足 (2024/09/12 追記)

よく考えたら、`.apply` ってデータ量が大きいと遅いんですよね。。。

以下のやり方の方がおそらく早いのでおすすめです。

```python
# データの準備
df_dummy = pd.DataFrame({
    "a": [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]],
    "b": [["a", "b", "c", "d", "e"], ["f", "g", "h", "i", "j"]]
})

# 横へカラム内の配列を複数カラムに分散
pd.DataFrame(df_dummy["a"].tolist(), index=df_dummy.index)

# 実行結果
	0	1	2	3	4
0	1	2	3	4	5
1	6	7	8	9	10
```


### 5-2. explode - 爆ぜろ！

こちらも、Series の 1要素内に list型 が入っている際に、それらを展開してくれる。
`df["列名"].apply(pd.Series)` との大きな違いは、各配列の要素数が異なる（ジャグ配列の場合）でも上手く処理してくれるところだろうか。

```python:colaboratory
df = pd.DataFrame({'A': [[0, 1, 2], 'foo', [], [3, 4]],
                   'B': [5, 1, 1, 6],
                   'C': ['a', np.nan, [], 'd']})
df

# 実行結果
           A  B      C
0  [0, 1, 2]  5      a
1        foo  1    NaN
2         []  1     []
3     [3, 4]  6      d
```

爆発（して縦に分裂）します。

```python:colaboratory
# "A"列に入っている配列を、行方向に分割 (爆発？)
df.explode('A')

# 実行結果
     A  B      C
0    0  5      a
0    1  5      a 
0    2  5      a
1  foo  1    NaN
2  NaN  1     []
3    3  6      d
3    4  6      d
```

`explode`メソッドの引数に指定した列以外は、単純に複製される。

メソッド名も動作も楽しいけど、使い所が難しい。。。


**関連記事**

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html

https://www.guruguru.science/competitions/16/discussions/3bed606a-20a2-44b6-ab84-b9bd31bb4193/


### 5-3. unstack - 潰れろ!

`MultiIndex` な `Series` のいずれかの Index を、 DataFrame の列名へと変換してくれる。

サンプルデータはこちら。
鬼と桃太郎の腕立て伏せ競争？の結果データを考える（）

```python:colaboratory
index = pd.MultiIndex.from_tuples([
    ('ももたろ', '2023-03-04'), ('ももたろ', '2023-03-05'), ('ももたろ', '2023-03-06'),
    ('鬼', '2023-03-04'), ('鬼', '2023-03-06')
])
series = pd.Series(
    [591, 528, 580, 1209, 1221],
    index=index,
    name="腕立て伏せの回数"
)
series

# 出力
ももたろ   2023-03-04     591
         2023-03-05     528
         2023-03-06     580
鬼       2023-03-04    1209
         2023-03-06    1221
Name: 腕立て伏せの回数, dtype: int64
```

ここで、後ろから数えて1つめ(`level=-1`)の Index を新たな列名として DataFrame にすると、こうなる。

```python:colaboratory
series.unstack(level=-1)

# 出力
         2023-03-04	2023-03-05	2023-03-06
ももたろ        591.0      528.0       580.0
鬼           1209.0        NaN      1221.0
```

これ、いつ使うんじゃ？という感じ。
でも私は仕事で使うことになった。頭の片隅に入れておいて損はない。


**関連記事**

公式サイトだけど、実行例も載ってるよ

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.unstack.html#pandas-dataframe-unstack


# あとがき

こういったメソッドを覚えておくと、難解な処理を自力で実装する手間が省けます。

その結果、以下のような効果が見込まれます！

- ソース量が減る
- 多重ネストが減る
- バグが減る
- 作業工数が減る
- 引継ぎが少しスムーズになる？？？（これは微妙か？）

というわけで...みなさま、是非是非ご活用くださいませ(*´ｖ｀)

## p.s.

もう多重ネストのコードは読みたくない...
`if` と `for` だけで力技で処理を書くのは、どうかどうか辞めてほしい。。。
スマートに行きましょう...\_(-ω-`\_)⌒)\_