データ前処理のための pandas 操作

# この記事について

データの前処理に使う pandas の操作で、よく使うものを自分のためにメモ。
「よく使う」がポイントで、たまに pandas 関係ないものも出てきます。

みなさんにも何かのお役に立てば。

# 本編

## Google Colab で content drive をマウントする

```python
from google.colab import drive
drive.mount('/content/drive')
```

:::note warn
さっそく pandas 関係ないですが、毎回使うので。
:::

## データを読み込む

```python
path = "csvが入っているデータの path "
df = read_csv(path, delimiter=',')
# delimter はカンマ区切りの場合は省略してもよいが、;など変な区切りになっていた場合は指定が必要
```

## データの概要を見る

```python
df.head(10) # 先頭の10行見る
df.tail(10) # おしりの10行見る
df.describe() # 全列の要約統計量を見る
df.info() # 列ごとに、non-null の値の数と型を教えてくれる

# カテゴリカルな列について、各カテゴリに該当するデータの数を見る。NaNの数も数える。
df['列名'].value_counts(dropna=False) 

# 行数と列数を (行数,列数) の形式で表示する
df.shape

# 列名の一覧
df.columns

# インデックスの一覧
df.index
```

:::note
df.columns と df.index は上書きができる。
:::

## 前処理（加工系）

前処理の中で実施するであろう順番になるべく並べます。
といいつつ、珍しい処理（あまり使う頻度が多くなさそうな処理）は後ろの方に持っていきます。

### 2つのデータフレームの結合

基本的に、データ結合が必要になるのは left join, つまり以下のケースだと想定して書きます。

- df1 -> データ量の多いテーブル（←これが左に来る）
- df2 -> df1で使っている項目の名称や詳細情報を定義しているテーブル（←これが右に来る）
- df1を基準に（=df1のデータを失わずに）、df1とキーが一致する項目がdf2にあればその情報を足し、なければ空欄にしておく

```python: 特定の列の値をキーに、列方向に結合する場合
new_df = pd.merge(df1, df2, left_on='df1のキーとなる列', right_on='df2のキーとなる列', how='left')
```

特殊なケースとして、列名ではなくインデックスが結合キーになる場合は以下の方が鮮やか。

```python: インデックスをキーに left join する場合
new_df = pd.merge(df1, df2, left_index=True, right_index=True how='left')
```

もうひとつの特殊なケースとして、キーを当てる必要がそもそもなく、2つのデータフレームのデータの数や対応する並び順が完全に揃っている場合は、以下の方法で結合できる

```python: 単純な結合
new_df = pd.concat([df1,df2], axis=1)
```
### 値の加工 (lambda)

```python:

# 特定の列全体に、特定の加工をする場合 - price列を一律2倍にする
df['price'] = df['price'].apply(lambda x: x * 2)

# 特定の行インデックスと列名を指定して、値を加工する場合
index = [10,20,30,40]
colname = 'price'
df.loc[index, colname] = df.loc[index, colname].apply(lambda x: x * 2)
```

### 列の名称を一括変更する

:::note
df.columns は `pandas.core.indexes.base.Index` という特殊な形になっていて、このままでは操作ができない。
ただし、ここにリストを代入すると、中身をまるまる上書きしてくれる。
加工する場合は、df.columnsを一旦リストの形にして加工し、それを戻す形をとる。
:::

```python:

# カラム名の一覧を一旦リストにする
cols = list(df.columns)

# カラム名の5列目以降が「123-砂浜」「456-磯」のような書式になっている場合に、
# 「-」以降の部分だけを取り出して「砂浜」「磯」のような列名にしたい場合
cols[4:] = list(map(lambda x: x.split("-")[1], cols[4:]))

# リストをカラムに戻す
df.columns = cols
```

:::note warn
apply は DataFrame に対しては使えるがリストに対しては使えない。
リストの場合は、mapを使う。
:::

### 列の順番を入れ替える

マージなどを実施した場合、right側で指定した列は一律いちばん右側に入る。
分類の名称などを分類コードのすぐ隣に差し込みたい、という場合は、以下の要領で実施する。

```python:「用途分類」という名前の列を、3列目に持ってきたい場合
# カラム名の一覧を一旦リストにする
cols = list(df.columns)

# 「用途分類」という名前の列を取り出して、index=2の位置（つまり3番目）に移動する
cols.insert(2, cols.pop(cols.index('用途分類')))

# その移動した列名の並びで、dfを上書き
df = df[cols]

```

### 行列の入れ替え

`.T` を使う。

```python: データフレームの行列をまるごと入れ替えたい場合
df = df.T
```

実際には、データフレーム全体の行列を入れ替えたいケースはそんなになく、特定の列の範囲を選んで行列入れ替えすることが多いと思います。
その場合は、以下の要領で指定します。

```python: 範囲を指定して行列変換
# 3列目だけを行列変換
new_df = df.iloc[:,2].T

# 3列目以降を行列変換
new_df = df.iloc[:,2:].T

# 3列目から10列目までを行列変換
new_df = df.iloc[:,2:11].T
```

:::note
ilocについて

書式は以下のとおり。
`df.iloc[行のインデックス範囲, 列のインデックス範囲]`
:::

### 欠損値処理

#### 欠損を含む行の単純な除外

```python: 欠損を含む行の除外

# これをやると、ひとつでもNaNの列がある行は一律削除になる。
df = df.dropna()

# 特定の列が NaN の場合だけ削除したい場合は以下のようにする。
# 以下は、都道府県名と市町村名のいずれか一方または両方が欠損している行を除外する
df = df.dropna(subset=['都道府県名','市町村名'])

```

#### 欠損を埋める

欠損を埋めるための値が単純に決まる場合は、以下の要領でOK。

```python: 欠損を単純な値で埋める
# price が欠損している場合は 0 で埋める
df['price'] = df['price'].fillna(0)

# sales が欠損している場合は、欠損していない sales の平均値で埋める
df['sales'] = df['sales'].fillna(df['sales'].mean())
```

時系列や累積のデータなど、「前の行の値を埋める」というという操作をしたいときは以下の要領で実施。

```python:
# 1行目が NaN になっている列がある場合は、先にこれをやる
df.fillna(value={'列1':0, '列2':0},
          limit=1,
          inplace=True)

# 前の行の値で埋める
cols = ['列1','列2','列3']
df[cols] = df[cols].fillna(method='ffill')
          
```

### インデックスの操作

日付単位のデータなどは、日付を単純な列としてではなく、インデックスとして持っていると便利だったりするみたい？

```python: date列に文字列型で入っている日付を、datetime型のインデックスとして扱う
df.index = pd.to_datetime(df['date'])

# 上記の処理を行なった時点では、date列は自動的には削除されない。消したい場合は以下を実施。
df = df.drop('date', axis=1)
```

:::note
pandas の to_datetime は便利で、もとの文字列の日付がどういうフォーマットなのか(2024年3月9日のことを、2024-03-09と表記するのか3/9/24と表記するのか)を明示しなくても、空気を読んでうまいこと日付にしてくれる。
自分でフォーマットを明記したい場合は、以下のように定義する。

```python
# 2024-03-09 形式の場合
df.index = pd.to_datetime(df['date'], format='%Y-%m-%d')

# 3/9/24 形式の場合
df.index = pd.to_datetime(df['date'], format='%m/%d/%y')
```

日付のフォーマットのパターンは、こちらの記事にいっぱい載っている
https://atmarkit.itmedia.co.jp/ait/articles/2111/09/news015.html
:::

# 以上？

今のところ思いついているネタは以上です。
随時追記していきます。