はじめに
Pandas データフレームとしてデータを取り扱う場合、要件に従ってデータフレームの構造を修正する必要が出てきます。
Pandas データフレームを取り扱うにあたり、色々と便利だった小技をまとめました。
経験則に基づくものなので、当たり前ですが、自分が取り扱った中での便利だったものの中から抽出しています。網羅性の薄さはご了承ください。
縦持ち横持ち切り替え
CSV ファイルから、3 名の生徒の各教科の点数を、データフレームに読み込みます。
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() 関数を使うと、簡単にデータ変換されます。
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 |
クロス集計表から総当たりの相関係数を求める方法はこちらに記述しています。併せてご確 認ください。
今度は横持ちを縦持ちに戻します。
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() を指定します。
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 して、各教科の最高点を算出する場合、以下のようになります。
df = df.groupby(['教科'], as_index=False).agg({'点数': 'max'})
教科 | 点数 | |
---|---|---|
0 | フランス語 | 87 |
1 | 体育 | 84 |
2 | 図工 | 86 |
3 | 美術 | 84 |
4 | 音楽 | 95 |
aggregate 時に max と指定している個所を、mean、sum、min に変更可能です。
抽出
条件に合ったデータだけを、データフレームの中から抽出します。下記の例では、フランス語の点数だけを抽出しています。
df = df.query('教科 == "フランス語"')
名前 | 教科 | 点数 | |
---|---|---|---|
0 | たろう | フランス語 | 58 |
5 | ゆり | フランス語 | 56 |
10 | ジョナサン | フランス語 | 87 |
複数条件の場合は以下の通り。2 種類の記述の仕方を示しますが、いずれも同じ結果をもたらします。
df = df[df['教科'].isin(['フランス語', '美術'])]
df = df.query('教科 in ["フランス語", "美術"]')
名前 | 教科 | 点数 | |
---|---|---|---|
0 | たろう | フランス語 | 58 |
3 | たろう | 美術 | 83 |
5 | ゆり | フランス語 | 56 |
8 | ゆり | 美術 | 84 |
10 | ジョナサン | フランス語 | 87 |
13 | ジョナサン | 美術 | 64 |
数値条件も指定可能です。
df = df[df['点数'] > 90]
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 |
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 |
列名を変更したい時は、以下のような記述をします。
df = df.set_axis(['氏名', '教科', '今期','前期'], axis='columns')
更に、前期との差異、前期からの伸び率、の 2 点を算出して列として追加します。
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() を使って四捨五入することも可能です。
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() などを使ってデータ型変換してみる必要があります。
最後に
冒頭でも申し上げた通り、今回は経験則に基づく説明をいたしました。
更に詳細に確認するためには、下記のサイトなどをご参照ください。
サンプルコードと併せて、とても丁寧に説明がされているので、私もしばしば活用させていただいています。
では皆様、良いデータ分析を!! See you then!