Python初心者の備忘録 #07 ~DSに使われるライブラリ編02~

# はじめに
今回私は最近はやりのchatGPTに興味を持ち、深層学習について学んでみたいと思い立ちました！  
深層学習といえばPythonということなので、最終的にはPythonを使って深層学習ができるとこまでコツコツと学習していくことにしました。  
ただ、勉強するだけではなく少しでもアウトプットをしようということで、備忘録として学習した内容をまとめていこうと思います。  
この記事が少しでも誰かの糧になることを願っております！
**※投稿主の環境はWindowsなのでMacの方は多少違う部分が出てくると思いますが、ご了承ください。**
最初の記事：[Python初心者の備忘録 #01](https://qiita.com/Yushin-Tati/items/961dc47d6163f944f7e9)
前の記事：[Python初心者の備忘録 #06 ~DSに使われるライブラリ編01~](https://qiita.com/Yushin-Tati/items/5dcad7518a0f8a3fdbc1)
次の記事：[Python初心者の備忘録 #08 ~DSに使われるライブラリ編03~](https://qiita.com/Yushin-Tati/items/f58bcaa9562fc2596439)

今回は**Pandas**についてまとめております。
**※Dataframeを扱う都合上まとまった表データが必要となるので、実際に手を動かしながら記事を読みたいという方は、下記サイトからデータセットをダウンロードして、workフォルダの任意の場所に格納しておいてください**
※ダウンロードにはKaggleへのSignUpが必要となるので、読むだけも大丈夫な形にしてあります。
https://www.kaggle.com/datasets/tmdb/tmdb-movie-metadata
## ■学習に使用している資料
Udemy：[米国データサイエンティストがやさしく教えるデータサイエンスのためのPython講座](https://www.udemy.com/course/ds_for_python/)
## ■Pandas
### ▶Pandasとは
PythonでDSをするのに欠かせないライブラリの一つ。
使用する際は`import pandas as pd`とインポートする必要がある。
表形式のデータ処理が得意
DataframeとSeriesというデータ形式を扱う。
・Dataframe：エクセルなどの表のようなもの
・Series：Dataframeの1行、または１列を切り取ったもの
※Dataframe = Seriesの集まり
### ▶Seriesを作る
`pd.series()`の引数にdictionaryやndarrayを渡すことで、seriesを作成することができる。
それぞれのカラム名を渡さなかった場合は0～の連番をindexとして自動設定される。

ndarrayなどはカラム名を渡すのが困難だと思うが、第2引数に`index=['name']`とすることで、カラム名を設定することができる。

valueを取り出す際はdictionaryと同じ方法
`.values`で値をnumpyとして扱えたり、numpy同様に統計量の計算も可能
```python
import pandas as pd
import numpy as np

# dictionaryからSeriesを作る
data = {
    'name': 'John',
    'sex': 'male',
    'age': 22
}
john_series = pd.Series(data)  # ⇒ name    John
                               #    sex     male
                               #    age       22
                               #    dtype: object

# NumPy Arrayからも作成可能
array = np.array([100, 200, 300])
pd.Series(array)  # ⇒ 0    100
                  #    1    200
                  #    2    300
                  #    dtype: int64

# index引数で，indexにラベルをつけることが可能
labels = ['a', 'b', 'c']
pd.Series(array, index=labels)  # ⇒ a    100
                                #    b    200
                                #    c    300
                                #    dtype: int64

# 値を取り出す
john_series['name']  # ⇒ 'John'

# valuesで，値をnumpyとして扱える
series.values  # ⇒ array([100, 200, 300])

# NumPyと同じように統計量を計算できる
series.mean()  # ⇒ 200.0
```
### ▶Dataframeを作る
`.series()`と同様に、dictionaryやnumpyから作成することができる。

numpyの場合、引数に`index=` `columns=`を設定することで、それぞれ行・列のラベルを設定することができる。

`.read_csv('path')`や`.read_excel('path')`とすることで、csvやExcelファイルを読み込むことができる。ほかにも`.read_○○`は多数存在するので、都度確認するようにする。
```python
# NumPyからDataFrameを作成
ndarray = np.arange(0, 10).reshape(2, 5)
# columnsとindexを指定して作成する
columns = ['a', 'b', 'c', 'd', 'e']
index = ['index1', 'index2']
pd.DataFrame(ndarray, index=index, columns=columns)
```
```text
        a	b	c	d	e
index1	0	1	2	3	4
index2	5	6	7	8	9
```
```python
# Dictionaryから作成
data1 = {
    'name': 'John',
    'sex': 'male',
    'age': 22
}
data2 = {
    'name': 'Zack',
    'sex': 'male',
    'age': 30
}
data3 = {
    'name': 'Emily',
    'sex': 'female',
    'age': 32
}
pd.DataFrame([data1, data2, data3])

# 省略した書き方も可能
data = {
    'name': ['John', 'Zack', 'Emily'],
    'sex': ['male', 'male', 'female'],
    'age': [22, 30, 32]
}
# dfという変数をよく使う
df = pd.DataFrame(data)
```
```python
# ファイルから読み込むケース
# 今回はファイルを同じフォルダに入れた場合
df = pd.read_csv('tmdb_5000_movies.csv')
```
### ▶Dataframeの基本操作
#### ・len(df)
dfの行数を返す。
#### ・df.head(n=5)
引数に指定した行数のデータを表示する。
引数が空の場合は、最初の5行を表示する。
※dfは基本的に膨大なデータになるので表示するだけでもかなりの時間を要する場合があるが、表示数を限定することで時間とメモリの削減になる。
#### ・行、カラムの省略せずに表示
※処理にかなりの時間を要するので、基本的には使用しない。
pandas全体での設定となるので、今後すべての表示は省略されなくなるので注意
設定を元に戻す場合は更新をかけるか、処理を終了する。
```python
# カラムを省略せずに表示
pd.set_option('display.max_columns', None)
# 行を省略せずに表示
pd.set_option('display.max_rows', None)
```
#### ・df.describe()
dfの簡単な統計量をDataframeとして返す。
表示する統計量は`count、mean、std、min、25%、50%、75%、max`がある。
もちろん統計量もDataframeなので、統計量に対して関数を実施することも可能。
#### ・df.columns
dfの全カラム名を返す。
#### ・df['カラム名']
特定のカラムのデータのみをSeriesで返す。
カラム名をlistで指定した場合は、それぞれのカラムのデータをまとめてDataframeで返す。
`df[['column1', 'column2']]` `df[['column1']]`など、listで渡すとDataframeとなる。
#### ・df.iloc[n]とdf.loc['index']
`n`、もしくは`'index'`に指定したindexのデータをSeries、Dataframeで返す。

`.iloc[]`についてはSlicingで複数取得も可能。

`df.iloc[n]['column']`とすることで、特定のindexの特定のcolumnの要素の取得も可能。
#### ・columnの更新、追加
`df['column'] = Series`とすることで、既存の`'column'`の値を更新したり、指定したカラム名が存在しない場合は、そのカラム名でデータの追加が可能。
#### ・df.drop(n, axis=0)
指定した列や行を削除することができる。
列を削除する場合は、`axis=1`とすることで可能。
#### ・Dataframeの更新
今までの更新や`.drop()`を行っただけでは既存のdfの値は変化しないので、更新したい場合は`df = df.drop()`というように変数自体に再代入するか、`df.drop(inplace=True)`というように関数の引数に`inplace=True`を渡すことでも元のdfの更新が可能。
※dfはかなりの大きさになるので、**更新のたびに別の変数に格納していてはメモリを圧迫してしまう**。そのため、元の変数を使いまわす(元のデータを更新する)方法が推奨されている。
### ▶DataFrameのFilter
dfに対して、条件に合う特定のSeriesを抽出して、dfを作成する方法。
`df[条件]`とすることでフィルタリングできる。

複数条件を指定する場合は、`df[(条件) 論理演算子 (条件)]`というようにそれぞれの条件は()で囲み、それを論理演算子(&、|)でつなぐことで可能。

DataFrameのFilterでは、条件の結果が`True`のものを抽出するので、そもそもの値がbool型の要素であれば、条件を書かなくて`df[df['bool']]`とすることでSeriesを抽出できる。
```python
# DataFrameへのFilter
# 条件に対してTrueのものを抽出
filter = df[`age`] >= 30
df[filter]  # 'age'Seriesで30以上の要素を抽出

# こっちの書き方のほうが一般的
df[df['age'] >= 30]

# 複数条件の場合
df[(df['age'] >= 30) & (df['hight'] >= 170)]

# bool型の要素には条件式は不要
df[df['bool']]
```
### ▶DataFrameでのindex操作
`df.reset_index()`でindexの振り直しが可能。

Filterで抽出した後のDataFrameなどに使用することが多い。
この時、もとのindexは保持するので、もし元のindexを使用しないのであれば引数に`drop=True`を渡してあげることで、元のindexは保持しなくなる。

`df.set_index('column')`とすることで、指定したカラムをindexに置き換えることができる。
※indexには基本的に情報を追加しないほうがいいので`.set_index`はあまり使わない
```python
# indexを振り直す(元のindexは保持される)
df[df['age'] >= 30].reset_index()

# 元のindexを保持しない方法
df.reset_index(drop=True)

# indexを指定したSeriesに置き換える
df.set_index('column')
```
### ▶DataFrameでのNaNの取り扱い
DataFrameのNaNはnumpyのNaNと同じなので、`np.isnan()`で真偽判定を行うことができる。

`df[np.isnan(df['column'])]`とすることで、NaNについてのフィルタリング可能。
他にも`pd.isna(df['column'])`であったり、`df['column'].isna()`でも同様にNaNの判定ができる。

`.isna()`の場合は、NaNの場合はTrue、そうじゃなければFalseのに置き換えたDataFrame、またはSeriesを返すということに注意。

`df.dropna()`でNaNを含むデータを削除することができる。
特定のSeriesに対してのみ`.dropna()`を適用したい場合は、引数に`subset=['column1', 'column2']`とすることで、指定したカラムにおいてNaNを持っているデータを駆除してくれる。

`df['column'] = df['column'].fillna(value)`とすることで、特定のカラムに対してNaNの値をvalueで置き換えることができる。
```python
# DataFrameのNaN判定(NaNの場合True)
na.isnan(df)
pd.isna(df)
df.isna()

# filteringも可能
df[np.isnan(df['column'])]
df[~df['column'].isna()]

# NaNを含むデータの削除
df.dropna()

# 特定のSeriesに対して.dropna()
df.dropna(subset=['column1', 'column2'])

# NaNをvalueで置き換え
df['column'] = df['column'].fillna(value)
```
### ▶DataFrameのgroup by
指定したカラムの種類ごとに統計量を確認することができる。

`df.groupby('country').mean()`であれば、国ごとにグループ化して統計量を表せるカラムの平均値をDataFrameで返してくれる。

他にも`.count()、.max()、.min()、.describe()`などの関数も使用可能。

`.max()`や`.min()`については、`df.groupby('country')['column'].max()`というようにどのカラムの最大値を表示するのか指定しないとエラーとなるので注意。

`df.iloc(df.groupby('country')['column'].idxmax())`とすると、グループごとの特定のカラムの最大値がどんなデータなのかをDataFrameで返してくれる。
※`df.groupby('country')['column'].idxmax()`で最大値のデータのindexをSeries返すので、`.iloc()`でデータを取ってくることができる。
```python
# 特定のカラムに対して、データの種類ごとにグループ化し、統計量の表示が可能
df.groupby('column').describe()

# .min()や.max()はどのカラムの最大最小を表示するのか指定する必要がある
df.groupby('column1')['column2'].max()

# 最大最小の統計量を持つデータをグループごとに取得
df.iloc(df.groupby('column1')['column2'].idxmax())
```
### ▶表結合
SQLにもあるような、データ・表の結合が可能。
#### ・pd.concat([df1, df2], axis=0)
リスト形式で渡したDataFrameを結合してくれる。
デフォルトでは`axis=0`で縦に結合してくれるが、`axis=1`という引数を渡すことで横に結合してくれる。
#### ・df1.marge(df2)
df1とdf2を結合するのは同じなのだが、同じカラム(Key)を持っている場合は、そのKeyを統合して表を作成してくれる。
```python
# おなじKeyは統合して、表を結合
df1 = pd.DataFrame({ 'Key': ['k0', 'k1', 'k2'],
                       'A': ['a0', 'a1', 'a2'],
                       'B': ['b0', 'b1', 'b2']})
 
df2 = pd.DataFrame({ 'Key': ['k0', 'k1', 'k2'],
                       'C': ['c0', 'c1', 'c2'],
                       'D': ['d0', 'd1', 'd2']})

df1.marge(df2)  # ⇒        Key	A	B	C	D
                #      0	k0	a0	b0	c0	d0
                #      1	k1	a1	b1	c1	d1
                #      2	k2	a2	b2	c2	d2
```
#### ・.merge()の引数
.merge()には様々な引数が設定されており、それによりSQLでいうjoin操作などが可能となる。
```python
# inner join(デフォルトなので、引数を与えなかった場合はinnerでmergeされる)
df1.merge(df2, how='inner')

# outer join
df1.merge(df2, how='outer')

# left join
df1.merge(df2, how='left')

# right join
df1.merge(df2, how='right')

# 結合に使用するKeyの指定
df1.merge(df2, on='key')

# それぞれ使用したいKey名が違う場合
df1.merge(df2, left_on='key1', right_on='key2')

# indexをKeyにしての結合も可能
df1.merge(df2, left_index=True, right_index=True)

# Key以外に同じカラム名があった場合に、それぞれどちらのデータなのか分かりやするsuffixを追加可能
df1.merge(df2, suffixes=('_df1', '_df2'))
```
※それぞれの結合の際に残るデータのイメージ
![how to join.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/7fedc595-bf1f-17c4-27c7-ed382d17fb92.jpeg)

#### ・df1.join([df2, df3])
複数のDataFrameの同時結合が可能
※あまり推奨されない方法なので、複数のdfを結合したい場合は`.meger()`を複数繰り返すほうがいい
### ▶DataFrameとSeriesの便利な関数
#### ・.unique()
Dataframeで指定したカラム、またはSeriesにおいてユニークの値を返す。
```python
df['column'].unique()
# array(['value1', 'valule2', 'value3', ···]、)
```

#### ・.nunique()
ユニークな値がいくつ存在するのか返却する。
これを利用して、idがユニークな値になっているかの確認が可能。
```python
# idが本当にIDになっているのか
print(len(df) == df['id'].nunique())  # ⇒ True or False
```
#### ・.value_counts()
それぞれのユニークな値が何件ぞんざいするかを返却する。
```python
df['column'].value_counts()
# value1  10
# value2  2
# value3  21
#   ・
#   ・
#   ・
```
#### ・.sort_values('column', ascending=True)
指定したカラムに対して、ソートしたものを返却する。
デフォルトでは昇順だが、`ascending=False`引数に与えることで降順にすることができる。
```python
# 指定したカラムについて昇順でソート
df.sort_values('column')

# 降順でソート
df.sort_values('column', ascending=False)
```
### ▶DataFrameのイテレーション
DataFrameの各行に対して、処理を行いたいときに活用する関数がある。
#### ・.apply()
Dataframeの各行に対して、引数に与えた関数を実行したものを返却する。
この時に与える関数は簡単なものであればlambda式で書くのが一般的。
```python
# あるカラムに対して、0の場合NaNに置き換える処理
df['column'] = df['column'].apply(lambda x: np.nan if x == 0 else x)

# 各行ごとに2カラム以上のデータを活用したい場合
df['diff_column'] = df.apply(lambda row: row['column1'] - row['column2'], axis=1)
```
#### ・.iterrows()
DataFrameの各行をindexとSeriesのtupleとして返却するイテレーション関数。
もちろんイテレーション関数なので、`next()`で次の行のtupleを返却することができる。
よく使うのはfor文で回して、各行に対してデータの加工や抽出を行う。
```python
# next()で次々に値の取得が可能で、tuple型で返却
next(df.iterrows())  # ⇒ (idx, Seires)

# for文でよく使われる
for idx, row in df.iterrows():
    if row['column1'] == 10:
        print(row['column2'])

# indexを使用しないときは_と書くことで使用しないことを明記できる
for _, row in df.iterrows():
    if row['column1'] == 10:
        print(row['column2'])
```
### ▶DataFrameをcsvとして保存
`df.to_csv('path')`とすることで、pathにdfをcsv形式で保存することができる。
`df.to_csv('path', index=False)`とすることで、カラムにindexを含まない形で保存できる。
```python
# indexありでcsvとして保存
df.to_csv('path')

# indexなしでcsvとして保存
df.to_csv('path', index=False)
```
#### 次の記事
[Python初心者の備忘録 #08 ~DSに使われるライブラリ編03~](https://qiita.com/Yushin-Tati/items/f58bcaa9562fc2596439)