DataFrameの操作まとめ

# はじめに

Pythonでデータ分析をする際に使う操作を備忘録としてまとめた記事です。
まとめることで定着するかなと思ったので。（毎回ググるのは効率が悪い）
使うごとに更新していこうと思うので網羅率は悪いです。
基礎的なものからちょっとしたテクニックまで色々記載して日々増やしていければと思います。
項目が増えてきたら分類もできる限り整理していきます。

# データ
みんな大好き[タイタニック](https://www.kaggle.com/c/titanic)のデータを使って説明できるものは説明し、
難しい場合は別途データを定義して説明します。

```python
import pandas as pd
# Titanicのデータを読み込む
df = pd.read_csv("data/train.csv")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/ab89da6b-89de-a59d-bb8b-cc782ea5ab07.png)


# テクニック

## 基本

### 基本情報を表示

型が数値の列に対し、データ数、平均値、標準偏差、最小値、1/4番目の値、中央値、3/4番目の値、最大値を計算する。

```python
df.describe()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/7fc1ebd8-4d49-a3ac-20dd-4b1ccd729ccd.png)


### 特定の列の平均を求める

```python
df_time["Age"].mean()
# 29.69911764705882
```

### 列の最大値（最小値）を求める

```python
# 最大値
df_time["Age"].max()
# 80.0

# 最小値
df_time["Age"].min()
# 0.42
```

### 列の最大値（最小値）のインデックスを取得

```python
# 最大値のインデックス
df["Age"].idxmax()
# 630

# 最小値のインデックス
df["Age"].idxmin()
# 803
```

### 特定の列のUniqueの値をすべて表示

```Python
df["Sex"].unique()
# array(['male', 'female'], dtype=object)
```

### 特定の列のユニーク数（異なり数）を取得

```Python
df["Sex"].nunique()
# 2
```

### 並び替え

デフォルトは昇順のため、降順にしたい場合は`ascending=False`にする。

```python
df.sort_values("Fare", ascending=False).head(5)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/94949a42-da21-f631-5033-626ac647ca16.png)

### 四捨五入

`round`関数を利用。正数が小数点第n位、負数がnの位を四捨五入。

```Python
df["Fare"].mean().round(2)
# 32.2
```

### 各列の欠損値の数を求める

```python
df.isnull().sum()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/b1af653e-4bbf-d8f5-4948-4fb7d7f1f045.png)

### データを結合

```python
df_test = pd.DataFrame(
    [
        [892, 0, 2, "John Han", "male", 30, 0, 0, "84202", 23, "C147", "C"],
        [893, 1, 1, "Hannah May", "female", 22, 1, 0, "84205", 10.0, "B41", "S"],
    ],
    columns=[
        "PassengerId",
        "Survived",
        "Pclass",
        "Name",
        "Sex",
        "Age",
        "SibSp",
        "Parch",
        "Ticket",
        "Fare",
        "Cabin",
        "Embarked",
    ],
)

df_merge = pd.concat([df, df_test], ignore_index=True)
df_merge.tail()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/4510eea7-9265-bc8d-f385-20a787683886.png)

### 重複を消す

ある列に重複の値があった場合は重複を消す。
下記例の場合、"Survived"と"Pclass"で同じ組み合わせのデータは1つしかない。
個人的にはすっきりさせたいので、`reset_index`でインデックスをリセットさせる。

```python
df.drop_duplicates(subset=["Survived", "Pclass"]).reset_index(drop=True)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/9b75669b-3678-b313-c724-58166ace73bd.png)


## データ型に関する処理

### 各列の型を表示

```python
df.dtypes
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/7e4ff4f4-3b70-7c83-c6d0-6051e14ca89c.png)


### 日付に変換

to_datetimeを利用。formatで表示形式を指定できる。

```python
df_test = pd.DataFrame(
    [
        ["20200101", "001", "foo"],
        ["20200102", "002", "hoge"],
        ["20200103", "003", "foge"],
    ],
    columns=["date", "id", "value"],
)
df_test["date"] = pd.to_datetime(df_test["date"], format="%Y/%m/%d")
df_test
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/13bda2f6-b893-6ae1-fc9a-6e73c2ffee53.png)


## データ抽出
### 複数条件でデータを抽出

ポイントは条件の中身を()で囲うところ。
また演算子はPythonだが`and`は`&`、`or`は`|`を使うことに注意。

```python
# 男性でかつ20歳以上
df[(df["Sex"] == "male") & (df["Age"] >= 20)].head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/3ac96942-e041-6148-15f5-e347167020e8.png)


### 日付データの絞り込み

```python
import datetime
df_test = pd.DataFrame(
    [
        ["20200101", "001", "foo"],
        ["20200102", "002", "hoge"],
        ["20200103", "003", "foge"],
    ],
    columns=["date", "id", "value"],
)
df_test["date"] = pd.to_datetime(df_test["date"], format="%Y/%m/%d")

# 2020年1月のデータのみを抽出
df_test[(datetime.datetime(2020,1,1) <= df_test["date"]) & (df_test["date"] < datetime.datetime(2020,2,1))]
```
個人的にはすっきりしているのでBETWEEN的に使う場合は下記の書き方のほうが好き。

```python
df_test.query(
    "datetime.datetime(2020,1,1) <= date < datetime.datetime(2020,2,1)"
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/a61d599c-3498-b82d-ec73-047b104804b0.png)

### リストのいずれかの値が含まれる行を抽出

```python
# 年齢がゾロ目フラグを追加
zorome = [11, 22, 33, 44, 55, 66, 77, 88, 99]
df["zorome_flag"] = 0
df["zorome_flag"] = df["zorome_flag"].mask(
    df["Age"].isin(zorome),
    1,
)
df[df["zorome_flag"] == 1].head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/585bde93-e867-e0b4-a679-0f014f4275c9.png)

### 特定の文字列を含むか

```python
# 名前にJohnを含む人だけ抽出
df[df["Name"].str.contains("John")].head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/12492f0d-3043-e05d-a13e-d6c8bd207d18.png)

### ある列の文字列が別の列の文字列に含まれるか否か

```python
# 父親と同じ名前の人を抽出
df_test = df.head()
df_test["FatherName"] = ["Owen", "James", "Ryan", "David", "William"]
df_test[df_test.apply(lambda x: x.FatherName in x.Name, axis=1)]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/203de599-7d78-2412-64bd-01c05f960bdc.png)

### 時間データの絞り込み

```python
import datetime
df_test = pd.DataFrame(
    [
        ["20200101 09:05:00", "20200101 19:35:00", "001", "foo"],
        ["20200102 10:32:50", "20200102 17:11:16", "002", "hoge"],
        ["20200103 09:16:12", "20200103 16:54:43", "003", "foge"],
        ["20200201 10:21:42", "20200201 21:22:21", "004", "geho"],
        ["20200202 11:22:11", "20200202 18:31:01", "005", "foho"],
    ],
    columns=["time1", "time2", "id", "value"],
)

# Timedeltaに変換
df_test["time1"] = pd.to_datetime(df_test["time1"])
df_test["time2"] = pd.to_datetime(df_test["time2"])

# 時間の差を算出
df_test["time_diff"] = df_test["time2"] - df_test["time1"]

# 10時間以内だけに絞り込む
df_test[df_test["time_diff"] <= datetime.timedelta(hours=10)]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/2c38b7ba-03f7-6637-7f14-ecd8b38058bb.png)

## 集計
### 条件を絞ってデータを集計

```python
# 性別ごとに年齢と料金の平均値を算出
df.groupby("Sex").agg({"Age": "mean", "Fare": "mean"}).head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/e804158d-b29e-dfa5-626c-bdb58ab19c20.png)

### Groupbyして算出した情報をもとのDataFrameに追加

Groupbyした結果を元のデータに結合したい時に有効

```python
# Pclassごとに平均チケット料金を求めて元のデータに付与
df["PclassMean"] = df.groupby(["Pclass"]).transform(np.mean)["Fare"]
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/db32c3d1-df82-d926-fc5f-bf5506ab44db.png)

## 列・列同士の計算
### 列同士の計算結果を新しい列に追加

```python
# （特に意味はないけど） 年齢 x 料金 をスコアとして新しい列に追加
df["Score"] = df["Age"] * df["Fare"]
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/0eed01ca-46ec-a0df-5473-0ccd8cad9b4a.png)

### 文字列抽出して新しい列に追加

```python
# （特に意味はないけど） Ticket列の下2桁を抽出
df["TicketSuffix"] = df["Ticket"].str.extract("(..)$")
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/5139e3f7-008c-263c-a7b6-888668a464d7.png)

### 条件に合致する場合に値を代入

```python
# チケット代が30以上の場合、expensive_flagカラムに1を付与
df["expensive_flag"] = 0
df["expensive_flag"] = df["expensive_flag"].mask(df["Fare"] >= 30, 1)
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/bd3f0192-1953-99c7-5cd0-c4912c14989f.png)


## 時間操作

### timedeltaを秒とか時間に変換

```python
import datetime
import numpy as np

df_test = pd.DataFrame(
    [
        ["20200101 09:05:00", "20200101 19:35:00", "001", "foo"],
        ["20200102 10:32:50", "20200102 17:11:16", "002", "hoge"],
        ["20200103 09:16:12", "20200103 16:54:43", "003", "foge"],
        ["20200201 10:21:42", "20200201 21:22:21", "004", "geho"],
        ["20200202 11:22:11", "20200202 18:31:01", "005", "foho"],
    ],
    columns=["time1", "time2", "id", "value"],
)

# Timedeltaに変換
df_test["time1"] = pd.to_datetime(df_test["time1"])
df_test["time2"] = pd.to_datetime(df_test["time2"])

# 時間の差を算出
df_test["time_diff"] = df_test["time2"] - df_test["time1"]

# 分に変更
df_test["time_diff"] = df_test["time_diff"] / np.timedelta64(1, "m")
df_test
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/dab90779-91c2-4d12-fe96-e6e55b7b7a7e.png)


## その他
### dummy変数化するけど元のカラムを残したい

```python
df_dummy = pd.get_dummies(df, columns=["Sex"])
df = pd.concat([df_dummy, df["Sex"]], axis=1)
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/2e44cf8e-fefa-0ecc-4cbf-cfbacbff0840.png)


### マルチインデックスカラムを統合する

```python
# グループ化
df_group = df.groupby("Sex").agg({"Age": ["max", "min"], "Fare": ["max", "min"]}).reset_index()

# ここでハイフンでつなげる
df_group.columns = ["_".join(x) for x in df_group.columns.ravel()]

# マルチインデックスでないところにもハイフンがついてしまうのでリネームする
df_group = df_group.rename(columns={"Sex_": "Sex"})

df_group
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/2f8cf1f5-ff47-bbe3-1b5a-e64214726b17.png)

もっとスマートな方法があれば教えて下さい。。。

### Dataframe同士で重複している行を削除

特定のDataFrame以外の行を抽出したい！という時に便利です。

```python
import numpy as np

df_test = pd.DataFrame(
    [
        [
            1,
            0,
            3,
            "Braund, Mr. Owen Harris",
            "male",
            22.0,
            1,
            0,
            "A/5 21171",
            7.2500,
            np.nan,
            "S",
        ],
        [
            3,
            1,
            3,
            "Heikkinen, Miss. Laina",
            "female",
            26.0,
            0,
            0,
            "STON/O2. 3101282",
            7.9250,
            np.nan,
            "S",
        ],
    ],
    columns=[
        "PassengerId",
        "Survived",
        "Pclass",
        "Name",
        "Sex",
        "Age",
        "SibSp",
        "Parch",
        "Ticket",
        "Fare",
        "Cabin",
        "Embarked",
    ],
)
# PassengerIdの1,3が重複しているので削除される
pd.concat([df, df_test]).drop_duplicates(keep=False).head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/897ad126-7a64-923a-f940-23ffe1cf0c1d.png)

### 関数を適用

```python
# チケット料金のランクを追加
def fare_rank(price):
    if price < 10:
        return "cheap"
    elif price < 30:
        return "normal"
    else:
        return "expensive"
    
df["fare_rank"] = df["Fare"].apply(fare_rank)
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226827/88e93696-1e27-929d-5ac7-4b2df4ff199e.png)