Pandas データフレーム 形変換 小技集

## はじめに
Pandas データフレームとしてデータを取り扱う場合、要件に従ってデータフレームの構造を修正する必要が出てきます。

Pandas データフレームを取り扱うにあたり、色々と便利だった小技をまとめました。
経験則に基づくものなので、当たり前ですが、自分が取り扱った中での便利だったものの中から抽出しています。網羅性の薄さはご了承ください。

## 縦持ち横持ち切り替え
CSV ファイルから、3 名の生徒の各教科の点数を、データフレームに読み込みます。
```python:CSV 
import pandas as pd
df = pd.read_csv("score.csv")
```
データフレームの中身は以下のようになりました。
||名前|教科|点数|
|---|---|---|---|
|0|たろう|フランス語|58|
|1|たろう|体育|58|
|2|たろう|図工|86|
|3|たろう|美術|83|
|4|たろう|音楽|60|
|5|ゆり|フランス語|56|
|6|ゆり|体育|84|
|7|ゆり|図工|64|
|8|ゆり|美術|84|
|9|ゆり|音楽|79|
|10|ジョナサン|フランス語|87|
|11|ジョナサン|体育|81|
|12|ジョナサン|図工|83|
|13|ジョナサン|美術|64|
|14|ジョナサン|音楽|95|

テータを縦持ちから横持ちに変換します。pivot_table() 関数を使うと、簡単にデータ変換されます。
```python:pivot
df_pivot = df.pivot_table(values=['点数'],index=['名前'], columns=['教科'])
df_pivot.columns = df_pivot.columns.get_level_values(1)
```

この時点で、データフレーム内のデータは以下のような形になります。
縦軸に名前、横軸に教科の、クロス集計表の形に変換されました。
|教科|フランス語|体育|図工|美術|音楽|
|---|---|---|---|---|---|
|名前||||||
|たろう|58.0|58.0|86.0|83.0|60.0|
|ゆり|56.0|84.0|64.0|84.0|79.0|
|ジョナサン|87.0|81.0|83.0|64.0|95.0|

クロス集計表から総当たりの相関係数を求める方法は[こちら](https://qiita.com/ExtremeCarvFJ/items/19ec4ec35872411fbcc4)に記述しています。併せてご確 認ください。

今度は横持ちを縦持ちに戻します。
```python:column
df_col = df_pivot.stack()
```
|名前|教科||
|---|---|---|
|たろう|フランス語|58.0|
||体育|58.0|
||図工|86.0|
||美術|83.0|
||音楽|60.0|
|ゆり|フランス語|56.0|
||体育|84.0|
||図工|64.0|
||美術|84.0|
||音楽|79.0|
|ジョナサン|フランス語|87.0|
||体育|81.0|
||図工|83.0|
||美術|64.0|
||音楽|95.0|

連番の Index に戻すためには、reset_index() を指定します。
```python:column
df_col = df_pivot.stack().reset_index()
```
||名前|教科|点数|
|---|---|---|---|
|0|たろう|フランス語|58|
|1|たろう|体育|58|
|2|たろう|図工|86|
|3|たろう|美術|83|
|4|たろう|音楽|60|
|5|ゆり|フランス語|56|
|6|ゆり|体育|84|
|7|ゆり|図工|64|
|8|ゆり|美術|84|
|9|ゆり|音楽|79|
|10|ジョナサン|フランス語|87|
|11|ジョナサン|体育|81|
|12|ジョナサン|図工|83|
|13|ジョナサン|美術|64|
|14|ジョナサン|音楽|95|

## Group By
特定の列の値で Group By します。
記事の冒頭で CSV ファイルから読み取ったデータフレーム (df) の中で、教科単位に Group By して、各教科の最高点を算出する場合、以下のようになります。

```python:Group By
df = df.groupby(['教科'], as_index=False).agg({'点数': 'max'})
```
||教科|点数|
|---|---|---|
|0|フランス語|87|
|1|体育|84|
|2|図工|86|
|3|美術|84|
|4|音楽|95|

aggregate 時に max と指定している個所を、mean、sum、min に変更可能です。

## 抽出
条件に合ったデータだけを、データフレームの中から抽出します。下記の例では、フランス語の点数だけを抽出しています。
```python:抽出
df = df.query('教科 == "フランス語"')
```
||名前|教科|点数|
|---|---|---|---|
|0|たろう|フランス語|58|
|5|ゆり|フランス語|56|
|10|ジョナサン|フランス語|87|

複数条件の場合は以下の通り。２ 種類の記述の仕方を示しますが、いずれも同じ結果をもたらします。
```python:抽出
df = df[df['教科'].isin(['フランス語', '美術'])]
```

```python:抽出
df = df.query('教科 in ["フランス語", "美術"]')
```

||名前|教科|点数|
|---|---|---|---|
|0|たろう|フランス語|58|
|3|たろう|美術|83|
|5|ゆり|フランス語|56|
|8|ゆり|美術|84|
|10|ジョナサン|フランス語|87|
|13|ジョナサン|美術|64|

数値条件も指定可能です。

```python:抽出
df = df[df['点数'] > 90]
```

```python:抽出
df = df.query('点数 > 90')
```

||名前|教科|点数|
|---|---|---|---|
|14|ジョナサン|音楽|95|

## 結合

今回の点数を、前回の点数と結合して、両者を比較してみようと思います。
今回の点数のデータフレーム (df) を、構造は同じで、点数だけが前回のものが入った下記データフレーム (df_prev) と結合します。

||名前|教科|点数|
|---|---|---|---|
|0|たろう|フランス語|58|
|1|たろう|体育|58|
|2|たろう|図工|86|
|3|たろう|美術|83|
|4|たろう|音楽|60|
|5|ゆり|フランス語|56|
|6|ゆり|体育|84|
|7|ゆり|図工|64|
|8|ゆり|美術|84|
|9|ゆり|音楽|79|
|10|ジョナサン|フランス語|87|
|11|ジョナサン|体育|81|
|12|ジョナサン|図工|83|
|13|ジョナサン|美術|64|
|14|ジョナサン|音楽|95|

```python:結合
df = pd.merge(df, df_prev, on=['名前','教科'], how='inner')
```

how の中で Join の種類を指定します。inner 以外にも、left、right、outer、cross などの指定が可能です。

||名前|教科|点数_x|点数_y|
|---|---|---|---|---|
|0|たろう|フランス語|58|52|
|1|たろう|体育|58|51|
|2|たろう|図工|86|72|
|3|たろう|美術|83|59|
|4|たろう|音楽|60|50|
|5|ゆり|フランス語|56|78|
|6|ゆり|体育|84|51|
|7|ゆり|図工|64|69|
|8|ゆり|美術|84|83|
|9|ゆり|音楽|79|50|
|10|ジョナサン|フランス語|87|99|
|11|ジョナサン|体育|81|69|
|12|ジョナサン|図工|83|75|
|13|ジョナサン|美術|64|66|
|14|ジョナサン|音楽|95|67|

列名を変更したい時は、以下のような記述をします。
```python:列名変更
df = df.set_axis(['氏名', '教科', '今期','前期'], axis='columns')
```

更に、前期との差異、前期からの伸び率、の ２ 点を算出して列として追加します。
```python:列追加
df['前期差異']= df['今期'] - df['前期']
df['伸び率']= (df['今期'] - df['前期'])/df['前期']
```

||氏名|教科|今期|前期|前期差異|伸び率|
|---|---|---|---|---|---|---|
|0|たろう|フランス語|58|52|6|0.115385|
|1|たろう|体育|58|51|7|0.137255|
|2|たろう|図工|86|72|14|0.194444|
|3|たろう|美術|83|59|24|0.406780|
|4|たろう|音楽|60|50|10|0.200000|
|5|ゆり|フランス語|56|78|-22|-0.282051|
|6|ゆり|体育|84|51|33|0.647059|
|7|ゆり|図工|64|69|-5|-0.072464|
|8|ゆり|美術|84|83|1|0.012048|
|9|ゆり|音楽|79|50|29|0.580000|
|10|ジョナサン|フランス語|87|99|-12|-0.121212|
|11|ジョナサン|体育|81|69|12|0.173913|
|12|ジョナサン|図工|83|75|8|0.106667|
|13|ジョナサン|美術|64|66|-2|-0.030303|
|14|ジョナサン|音楽|95|67|28|0.417910|

伸び率の小数点以下の桁が大きすぎる場合、round() を使って四捨五入することも可能です。
```python:列追加
df['伸び率']= ((df['今期'] - df['前期'])/df['前期']).round(2)
```

||氏名|教科|今期|前期|前期差異|伸び率|
|---|---|---|---|---|---|---|
|0|たろう|フランス語|58|52|6|0.12|
|1|たろう|体育|58|51|7|0.14|
|2|たろう|図工|86|72|14|0.19|
|3|たろう|美術|83|59|24|0.41|
|4|たろう|音楽|60|50|10|0.20|
|5|ゆり|フランス語|56|78|-22|-0.28|
|6|ゆり|体育|84|51|33|0.65|
|7|ゆり|図工|64|69|-5|-0.07|
|8|ゆり|美術|84|83|1|0.01|
|9|ゆり|音楽|79|50|29|0.58|
|10|ジョナサン|フランス語|87|99|-12|-0.12|
|11|ジョナサン|体育|81|69|12|0.17|
|12|ジョナサン|図工|83|75|8|0.11|
|13|ジョナサン|美術|64|66|-2|-0.03|
|14|ジョナサン|音楽|95|67|28|0.42|

データ型のキャストを行う関数としては、astype() も便利です。
astype(int)、astype(float)、astype(str) のような感じで型変換を行うことができます。
ValueError が出る時などは、まずはエラーの出力を参考に、astype() などを使ってデータ型変換してみる必要があります。

## 最後に
冒頭でも申し上げた通り、今回は経験則に基づく説明をいたしました。
更に詳細に確認するためには、下記のサイトなどをご参照ください。
サンプルコードと併せて、とても丁寧に説明がされているので、私もしばしば活用させていただいています。

https://note.nkmk.me/python/

では皆様、良いデータ分析を!! See you then!