pythonのpandasの使い方をまとめてみた

#はじめに
今回はpandasの使い方をまとめていきます。

pandasについては多くの人が使い方をまとめているので、特に目新しいことはないかもしれませんがお付き合い頂ければ幸いです。

前回の記事でnumpyの使い方についてまとめているので、よろしければご確認ください。

[pythonのnumpyについてまとめてみた](https://qiita.com/renesisu727/items/6ef7ff8f61fe16b4c505)

#Seriesの生成
以下のようにすればSeriesを生成できます。Seriesは配列にindexがついた形になります。

```.py
import numpy as np
import pandas as pd

series = pd.Series(data=[1, 2, 3, 4, 5], index=['A', 'B', 'C', 'D', 'E'])
print(series)
```

>A    1
B    2
C    3
D    4
E    5
dtype: int64

numpyと組み合わせて生成することもできます。

```.py
series = pd.Series(data=np.arange(5), index=['A', 'B', 'C', 'D', 'E'])
print(series)
```

>A    0
B    1
C    2
D    3
E    4
dtype: int64

#Seriesからデータを抽出
Seriesはインデックス指定してデータを取り出すことができます。辞書型に近いですね。

```.py
series = pd.Series(data=np.arange(5), index=['A', 'B', 'C', 'D', 'E'])
print(series['A'])
```

>0

スライスを用いることもできます。

```.py
series = pd.Series(data=np.arange(5), index=['A', 'B', 'C', 'D', 'E'])
print(series['A':'D'])
```

>A    0
B    1
C    2
D    3
dtype: int64

通常のスライスの感覚では、Dの一つ前のCまでのデータが抽出されるはずなんですが、Seriesの場合はインデックスで指定した範囲まで抽出されています。

##locを用いて

しかし、このようにインデックスを指定してデータを取り出すときは、慣習的にlocメソッドが用いられます。

```.py
series = pd.Series(data=np.arange(5), index=['A', 'B', 'C', 'D', 'E'])
print(series.loc['A':'D'])
```

>A    0
B    1
C    2
D    3
dtype: int64

スライスを用いずに、インデックスを二つ指定することができます。

```.py
series = pd.Series(data=np.arange(5), index=['A', 'B', 'C', 'D', 'E'])
print(series.loc[['A', 'D']])
```

>A    0
D    3
dtype: int64


##ilocを用いて
Seriesのindexを用いるのではなく、頭から割り振られる数字のインデックスを指定して取り出すこともできます。

```.py
series = pd.Series(data=np.arange(5), index=['A', 'B', 'C', 'D', 'E'])
print(series.iloc[[0]])
```

>A    0
dtype: int64



#DataFrameの生成
以下のようにすればDataFrameが生成できます。

```.py
df = pd.DataFrame(data=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=['A', 'B', 'C'], columns=['A1', 'A2', 'A3'])
print(df)
```

>   A1  A2  A3
A   1   2   3
B   4   5   6
C   7   8   9

このように、DataFrameは`index`と`columns`を指定した二次元のデータになっています。

機械学習で用いるときは、indexはデータの種類を、columnsはデータの特徴量について表しています。

##ファイルの読み込み
基本的にpandasはファイルを読みこんで使用するので、この作業は非常によく行います。

ここでは、自分が適当に作ったcsvファイルを読みこみます。以下のデータです。



<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/2d264f6e-db7d-69c7-b48b-7b6aebc8b291.png" width=50%>
読みこんでみましょう。

```.py
df = pd.read_csv('train.csv')
print(df)
```

以下が実行結果です。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/2eca30f3-6bf4-0282-3c1b-1b35907d8e42.png" width=50%>

コピペしても見えにくかったのでスクショしました。

`takash`や`kenta`をインデックスにしたかったのですが、デフォルトではインデックスになっていません。

このように、インデックスがあるデータを読みこむときには、`index_col`で指定する必要があります。この例では、一番左のデータをインデックスとして扱うので、`index_col = 0`とします。


```.py
df = pd.read_csv('train.csv', index_col=0)
print(df)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/a5ad5f36-21bb-d5dc-c962-0e80419f2a54.png" width=50%>

また、デフォルトでは一番最初の行がヘッダーとして扱われています。一番最初の行をヘッダーとして指定しない場合には`header = None`を指定しましょう。

```.py
df = pd.read_csv('train.csv', header=None)
print(df)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/bcc8ae46-fe0c-c1a7-dc61-ff1df5925f5e.png" width=50%>




##データの内容の確認

###形状を確認
データの形状を確認しましょう。numpyなどと同じく、shape変数に次元のデータが格納されています。

```.py
df = pd.read_csv('train.csv', index_col=0)
print(df.shape)
```

>(3, 3)

###統計量の確認`
describeメソッドを用いれば、データの統計量を確認できます。

```.py
df = pd.read_csv('train.csv', index_col=0)
print(df.describe())
```



<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/6280cab5-40ec-c17d-9d9f-85d5645b2ceb.png" width=50%>


このように、各々のcolumnsに対してデータの数、平均、標準偏差、最小値、最大値、四分位数を得ることができます。

###データの数と型を確認
以下のコードで確認できます。

```.py
df = pd.read_csv('train.csv', index_col=0)
print(df.info())
```

>\<class 'pandas.core.frame.DataFrame'>
Index: 3 entries, takash to yoko
Data columns (total 3 columns):
math       3 non-null int64
Engrish    3 non-null int64
society    3 non-null int64
dtypes: int64(3)
memory usage: 96.0+ bytes
None

こんな感じのデータが確認できます。解説は不要ですね。

###重複を除いたデータの確認
nuniqueメソッドを用いれば、書くcolumnsごとの重複を除いたデータを確認できます。

```.py
df = pd.read_csv('train.csv', index_col=0)
print(df.nunique())
```

＞math       3
Engrish    3
society    3
dtype: int64

今回は重複が一つもないので、以上のような結果になりました。

###行名、列名の確認
index変数にインデックスが、columns変数に列名が格納されています。確認してみましょう。

```.py
df = pd.read_csv('train.csv', index_col=0)
print(df.index)
print(df.columns)
```

>Index(['takash', 'kenta', 'yoko'], dtype='object')
Index(['math', 'Engrish', 'society'], dtype='object')

###欠損値の合計を確認
以下のコードで、各々のcolumnsにおける欠損値の場所を確認できます。

```.py
df = pd.read_csv('train.csv', index_col=0)
print(df.isnull())
```

> math  Engrish  society
takash  False    False    False
kenta   False    False    False
yoko    False    False    False

それぞれの値は欠損値ではないのでFalseが返ってきます。

それでは、次のコードで欠損値の合計を取得しましょう。

```.py
df = pd.read_csv('train.csv', index_col=0)
print(df.isnull().sum())
```

>math       0
Engrish    0
society    0
dtype: int64

#DataFrameのデータの選択と抽出
それでは、DataFrameからデータを抽出していきましょう。とりあえず、以下のDataFrameを生成しました。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
print(df)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/96a137e7-dd2b-d44a-143c-6e0de4fff84c.png" width=50%>

`np.random.seed(0)`により、`np.random.rand`で生成する乱数を固定することができます。しかし、自分は毎回コードを実行するので、乱数は毎回変更されています。

`np.random.rand`は0から1までの乱数を生成するコードです。

以下のコードでcolumunsを選択して抽出してみましょう。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
print(df['A1'])
```

>A    0.165899
B    0.144862
C    0.974517
D    0.144633
E    0.806085
Name: A1, dtype: float64

このように、columnsを抽出することができました。

locメソッドを用いるとインデックスを指定して抽出することができます。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
print(df.loc['A'])
```

>A1    0.687867
A2    0.243104
A3    0.568371
A4    0.125892
A5    0.749777
Name: A, dtype: float64


インデックスを指定して抽出と書きましたが、DataFrameにおけるlocメソッドでの指定方法はnumpyの二次元配列の指定にかなり近いです。

`loc[行:列]`と指定することができます。

以下で使い方をみていきましょう。

```.py
print(df.loc[:, 'A1'])
```

>A    0.108650
B    0.819086
C    0.250341
D    0.950634
E    0.852035
Name: A1, dtype: float64

行の部分に`:`を指定したので、全ての行を指定したことになり、列に`A1`を指定したため、A1のcolumnsが抽出されています。

```.py
print(df.loc['C', ['A2', 'A4']])
```

>A2    0.129296
A4    0.367573
Name: C, dtype: float64

このようにすれば、C行のA2とA4のデータを抽出できます。

###条件による選択
DataFrameから、条件を選択して抽出しましょう。以下のコードで`df > 0.5`の挙動を確認しましょう。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
print(df)
print(df > 0.5)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/5a99093d-4f7d-9b27-4725-f82ce4403e00.png" width=50%>

このように、DataFrameの中の値が条件を満たすときにはTrueが、条件を満たさないときにはFalseが格納されます。

これを利用すると、以下のように条件を満たさない値を除くことができます。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
print(df > 0.5)
print(df[df > 0.5])
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/e70d0a4d-9698-1d52-3fca-5c02ec36b3f3.png" width=50%>

また、次のようにすれば特定のcolumnsが条件を満たす行のみを抽出することができます。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
print(df)
print(df[df['A3'] > 0.5])
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/473140f7-99bd-60ff-e6f2-9b7c9bbfa4e9.png" width=50%>

以下のように、&を用いて条件を追加することもできます。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
print(df)
print(df[(df['A3'] > 0.2) & (df['A3'] < 0.6)])
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/afd8dacc-80f6-fa97-6893-fd21755833f8.png" width=50%>

#DataFrameのデータの追加と削除
以下のようにすればcolumnsを追加することができます。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
df['new'] = np.arange(5)
print(df)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/a7a51a24-c2e9-700a-a3f8-93f41635598f.png" width=50%>

列名を指定することで列を削除することができます。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
df = df.drop(columns=['A1', 'A3'])
print(df)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/46f79a34-c5fe-dc73-7799-06ba24919827.png" width=50%>

行名を指定することで行を削除することができます。

```.py
df = pd.DataFrame(data=np.random.rand(5, 5),
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=('A1', 'A2', 'A3', 'A4', 'A5'))
np.random.seed(0)
df = df.drop(index=['A', 'D'])
print(df)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/27d6c5c5-0051-f8c6-dcec-21ce3f2fe27f.png" width=50%>

#欠損値の処理
以下のようにデータを準備しましょう。

```.py
df = pd.DataFrame([[1, 2, 3, np.nan, 5],
                   [np.nan, 7, 8, 9, 10],
                   [11, np.nan, 13, 14, 15],
                   [16, 17, np.nan, 19, 20],
                   [21, 22, 23, 24, np.nan]],
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=['A1', 'A2', 'A3', 'A4', 'A5'])
print(df)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/75bec714-1c94-b9ab-a95f-68cd713e9557.png" width=50%>

dropnaメソッドを用いれば、欠損値が含まれる行を削除してくれます。

```.py
df = df.dropna()
print(df)
```

>Columns: [A1, A2, A3, A4, A5]
Index: []

今回のデータは全ての行に欠損値が含まれているため、全て消えてしまいました。このように、強い制限をかけるとなかなかデータが残りません。


###特定の列の欠損値を削除
次のようにすれば、特定の列に対して欠損値がある部分を削除できます。

```.py
df = pd.DataFrame([[1, 2, 3, np.nan, 5],
                   [np.nan, 7, 8, 9, 10],
                   [11, np.nan, 13, 14, 15],
                   [16, 17, np.nan, 19, 20],
                   [21, 22, 23, 24, np.nan]],
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=['A1', 'A2', 'A3', 'A4', 'A5'])
df = df[df['A3'].isnull() == False]
print(df)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/efee2611-5a30-bf8f-24a1-0a205dcfb650.png" width=50%>

`isnull`を用いると、そのデータがnanであればTrueが、nanでなければFalseが返ってきます。そのため、上のようにA3に欠損値が含まれる行のみを削除できます。

###欠損値ではない数を指定して削除
`dropna`の引数`thresh`の引数を指定することで、引数で指定した数以上の欠損値ではない値を持つ行以外を削除することができます。

例えば、`thresh=4`とすると、欠損値ではない数が4以上ない行が削除されます。

```.py
df = pd.DataFrame([[1, 2, 3, np.nan, 5],
                   [np.nan, 7, 8, 9, 10],
                   [11, np.nan, 13, 14, 15],
                   [16, np.nan, np.nan, 19, 20],
                   [21, 22, 23, 24, np.nan]],
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=['A1', 'A2', 'A3', 'A4', 'A5'])
df = df.dropna(thresh=4)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/fa6ad958-075c-3d32-bf4f-a7bf63883419.png" width=50%>

`axis=1`とすれば、列に対して同様の操作が行えます。

```.py
df = pd.DataFrame([[1, 2, 3, np.nan, 5],
                   [np.nan, 7, 8, 9, 10],
                   [11, np.nan, 13, 14, 15],
                   [16, np.nan, np.nan, 19, 20],
                   [21, 22, 23, 24, np.nan]],
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=['A1', 'A2', 'A3', 'A4', 'A5'])
df = df.dropna(thresh=4, axis=1)
print(df)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/7419f64a-b7fc-c2c0-2020-402541d48f22.png" width=50%>

##欠損値を別の値で置換

以下のようにすると、特定の列に対してその列の欠損値にその列の平均を代入することができます。


```.py
df = pd.DataFrame([[1, 2, 3, np.nan, 5],
                   [np.nan, 7, 8, 9, 10],
                   [11, np.nan, 13, 14, 15],
                   [16, np.nan, np.nan, 19, 20],
                   [21, 22, 23, 24, np.nan]],
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=['A1', 'A2', 'A3', 'A4', 'A5'])
df['A3'] = df['A3'].fillna(df['A3'].mean())
print(df)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/8c474c3f-e33e-eb90-1e59-6dd286666058.png" width=50%>

以下のようにすると、全ての列に対して欠損値に平均値を代入することができます。

```.py
df = pd.DataFrame([[1, 2, 3, np.nan, 5],
                   [np.nan, 7, 8, 9, 10],
                   [11, np.nan, 13, 14, 15],
                   [16, np.nan, np.nan, 19, 20],
                   [21, 22, 23, 24, np.nan]],
                  index=['A', 'B', 'C', 'D', 'E'],
                  columns=['A1', 'A2', 'A3', 'A4', 'A5'])
df = df.fillna(df.mean())
print(df)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/a10d7a0b-348e-5c3f-75d8-7b75353a8420.png" width=50%>

##カテゴリカルなデータの欠損値
以下のDataFrameを作成しましょう。

```.py
df = pd.DataFrame({'A1': ['A', 'A', 'B', 'B', 'B', 'C', np.nan],
                   'A2': [1, 2, 3, 4, 5, 6, 7],
                   'A3': [8, 9, 10, 11, 12, 13, 14]})
print(df)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/3bf6112a-d0de-db10-03c6-f2eab220d291.png)

以下のコードでカテゴリとデータの数を確認しましょう。

```.py
print(df['A1'].value_counts())
```

>B    3
A    2
C    1

以下のコードで特定のカテゴリのデータだけを取り出すことができます。


```.py
print(df[df['A1'] == 'B'])
```

>A1  A2  A3
2  B   3  10
3  B   4  11
4  B   5  12

以下のコードで、カテゴリカルなデータの欠損値を埋めることができます。`mode()[0]`により最頻値が返ってくるので、その最頻値を欠損値に代入しています。

```.py
df = pd.DataFrame({'A1': ['A', 'A', 'B', 'B', 'B', 'C', np.nan],
                   'A2': [1, 2, 3, 4, 5, 6, 7],
                   'A3': [8, 9, 10, 11, 12, 13, 14]})
df['A1'] = df['A1'].fillna(df['A1'].mode()[0])
print(df)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/e142e948-5c79-749d-f90b-020df40ed728.png)

以下のコードでカテゴリカルなデータの割合を計算しましょう。

```.py
df = pd.DataFrame({'A1': ['A', 'A', 'B', 'B', 'B', 'C', np.nan],
                   'A2': [1, 2, 3, 4, 5, 6, 7],
                   'A3': [8, 9, 10, 11, 12, 13, 14]})
print(round(df['A1'].value_counts() / len(df), 3))
```

>B    0.429
A    0.286
C    0.143

以下のコードで、カテゴリカルなデータをグループ化して統計量を計算することができます。

```.py
df = pd.DataFrame({'A1': ['A', 'A', 'B', 'B', 'B', 'C', np.nan],
                   'A2': [1, 2, 3, 4, 5, 6, 7],
                   'A3': [8, 9, 10, 11, 12, 13, 14]})
print(df.groupby('A1').sum())
print(df.groupby('A1').mean())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/8e361dd0-b443-d245-1e61-8d97175a1a29.png)

#DataFrameの結合
デフォルトでは`axis=0`となっているため、縦方向に結合されます。

```.py
df1 = pd.DataFrame(data=np.random.rand(3, 3),
                   index=['A', 'B', 'C'],
                   columns=['A1', 'A2', 'A3'])
df2 = pd.DataFrame(data=np.random.rand(3, 3),
                   index=['D', 'E', 'F'],
                   columns=['A1', 'A2', 'A3'])

df3 = pd.concat([df1, df2])
print(df3)
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/af5667aa-1700-50bc-c1f4-581cc5a9f02b.png" width=50%>

以下のように`axis=1`を指定すれば、横方向に結合することができます。

縦方向に結合するときには`columns`を、横方向に結合するときは`index`をそれぞれ合わせる必要があります。

```.py
df1 = pd.DataFrame(data=np.random.rand(3, 3),
                   index=['A', 'B', 'C'],
                   columns=['A1', 'A2', 'A3'])
df2 = pd.DataFrame(data=np.random.rand(3, 3),
                   index=['A', 'B', 'C'],
                   columns=['A4', 'A5', 'A6'])

df3 = pd.concat([df1, df2], axis=1)
print(df3)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/c2aeba27-5800-8795-6712-0f81d6af5db4.png" width=50%>


#DataFrameへの関数の適用
`apply`を用いることで、特定のデータに関数を適用することができます。

```.py
df = pd.DataFrame(data=np.random.rand(3, 3),
                  index=['A', 'B', 'C'],
                  columns=['A1', 'A2', 'A3'])

print(df)
df['A1'] = df['A1'].apply(lambda x: x ** 2)
print(df)
```



<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/6050db59-1a2f-fd65-79dc-0e2ac00a28b6.png" width=50%>

複数の引数を持つ関数をDataFrameに適用する場合は、DataFrameを引数にもつ関数を定義すると便利です。

```.py
df = pd.DataFrame(data=np.random.rand(3, 3),
                  index=['A', 'B', 'C'],
                  columns=['A1', 'A2', 'A3'])
print(df)
def matmul(df):
    return df['A1'] * df['A2']
df['A4'] = df.apply(matmul, axis=1)
print(df)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/f6fb4be4-8fdf-67df-9e9d-85a98be49892.png" width=50%>


複数の戻り値がある場合、以下のようにすれば受け取ることができます。

```.py
df = pd.DataFrame(data=np.random.rand(3, 3),
                  index=['A', 'B', 'C'],
                  columns=['A1', 'A2', 'A3'])

def square_and_twice(x):
    return pd.Series([x**2, x*2])
df[['square', 'twice']] = df['A3'].apply(square_and_twice)
print(df)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/81c947bd-c77e-2608-a7d5-db4513af70e2.png" width=50%>

#終わりに
ここまでで今回の記事は終了です。

お付き合い頂きありがとうございました。