Kaggle×pandera

Kaggleにおける一連の流れにおいて、panderaの活用例を紹介します。

https://www.kaggle.com/

https://pandera.readthedocs.io/en/stable/

これにより、以下のメリットが得られます。

- データのバリデーションが実現
- 各データの内容（および関連する処理が期待するデータの内容）の読み取りが可能

「Titanic - Machine Learning from Disaster」を題材とします。

https://www.kaggle.com/c/titanic

実行環境はKaggleのNotebookを使用しています。

はじめに、panderaをインストールします。

```sh
!pip install pandera
```

そして、必要なモジュールをインポートします。

```python
import pandas as pd
import pandera as pa
from pandera.typing import Series
```

まずは、以下のデータを読み込みます。

- 訓練データ（train.csv）
- テストデータ（test.csv）
- 提出用サンプルデータ（gender_submission.csv）

そこで、訓練データのスキーマを定義します。これにより、訓練データのスキーマの内容の補足、訓練データのスキーマのバリデーションが実現されます。スキーマは実際にデータを確認した上で定義しています。その過程については省略します。

```python
# 訓練データのスキーマ
class TrainSchema(pa.SchemaModel):
    PassengerId: Series[int] = pa.Field(ge=1)
    Survived: Series[int] = pa.Field(isin=(0, 1))
    Pclass: Series[int] = pa.Field(isin=(1, 2, 3))
    Name: Series[str]
    Sex: Series[str] = pa.Field(isin=("male", "female"))
    Age: Series[float] = pa.Field(nullable=True, ge=0)
    SibSp: Series[int]
    Parch: Series[int]
    Ticket: Series[str]
    Fare: Series[float] = pa.Field(nullable=True)
    Cabin: Series[str] = pa.Field(nullable=True)
    Embarked: Series[str] = pa.Field(nullable=True)


# 訓練データのの読み込み（バリデーションも実施）
train = TrainSchema.validate(pd.read_csv("../input/titanic/train.csv"))
```

テストデータにおいても同様にスキーマを定義します。

```python
# テストデータのスキーマ
class TestSchema(pa.SchemaModel):
    PassengerId: Series[int] = pa.Field(ge=1)
    Pclass: Series[int] = pa.Field(isin=(1, 2, 3))
    Name: Series[str]
    Sex: Series[str] = pa.Field(isin=("male", "female"))
    Age: Series[float] = pa.Field(nullable=True, ge=0)
    SibSp: Series[int]
    Parch: Series[int]
    Ticket: Series[str]
    Fare: Series[float] = pa.Field(nullable=True)
    Cabin: Series[str] = pa.Field(nullable=True)
    Embarked: Series[str] = pa.Field(nullable=True)


# テストデータの読み込み（バリデーションも実施）
test = TestSchema.validate(pd.read_csv("../input/titanic/test.csv"))
```

提出用データのスキーマにおいても同様にスキーマを定義します。

```python
# 提出用データのスキーマ
class SubmissionSchema(pa.SchemaModel):
    PassengerId: Series[int] = pa.Field(ge=1)
    Survived: Series[int] = pa.Field(isin=(0, 1))


# 提出用サンプルデータの読み込み（バリデーションも実施）
gender_submission = SubmissionSchema.validate(
    pd.read_csv("../input/titanic/gender_submission.csv"))
```

続いて、学習や予測のために、データの前処理を実施します。そこで、前処理済みのデータのスキーマを定義します。前処理を関数として実装し、返り値がスキーマに従うようにしています。前処理の内容については省略します。

```python
# データセットを目的変数と説明変数に分割
y_train = train["Survived"]
x_train = train.drop("Survived", axis=1)

x_test = test

# 前処理で必要な値の算出
fare_mean = x_train["Fare"].mean()
age_mean = x_train["Age"].mean()
embarked_mode = x_train["Embarked"].mode()[0]


# 前処理済みのデータのスキーマ
class PreprocessedSchema(pa.SchemaModel):
    Pclass: Series[int] = pa.Field(isin=(1, 2, 3))
    Sex: Series[int] = pa.Field(isin=(0, 1))
    Age: Series[int] = pa.Field(ge=0)
    SibSp: Series[int]
    Parch: Series[int]
    Fare: Series[float]
    Embarked: Series[int]


# 簡単な前処理を実施する関数（バリデーションも実施）
def preprocess(df):
    df["Sex"].replace({"male": 0, "female": 1}, inplace=True)
    df["Age"].fillna((age_mean), inplace=True)
    df["Age"] = df["Age"].astype(int)
    df["Fare"].fillna((fare_mean), inplace=True)
    df["Embarked"].fillna((embarked_mode), inplace=True)
    df["Embarked"].replace({"S": 0, "C": 1, "Q": 2}, inplace=True)
    df.drop(["PassengerId", "Name", "Ticket", "Cabin"], axis=1, inplace=True)
    return PreprocessedSchema.validate(df)


# 簡単な前処理
preprocessed_x_train = preprocess(x_train)
preprocessed_x_test = preprocess(x_test)
```

最後に学習、予測を実施します。先ほどの前処理済みの訓練データを学習させます。そして、前処理済みのテストデータで予測させます。前処理済みのデータのスキーマがあることにより訓練データ、テストデータの形式が同様であることの担保が可能です。予測の結果は提出用データのスキーマでバリデーションすることで提出が可能な形式の担保されます。

```python
from sklearn.linear_model import LogisticRegression

# ロジスティック回帰による学習
clf = LogisticRegression()
clf.fit(preprocessed_x_train, y_train)

# ロジスティック回帰による予測
y_pred = clf.predict(preprocessed_x_test)

# 提出用データの準備（バリデーションも実施）
submission = gender_submission
submission["Survived"] = y_pred
submission = SubmissionSchema.validate(submission)

submission.to_csv("submission.csv", index=False)
```

このように、Kaggleにおける一連の流れにおいて、panderaを活用することで、「データのバリデーションが実現」、「各データの内容（および関連する処理が期待するデータの内容）の読み取りが可能」というメリットが得られます。全体的にメリットが得られますが、特に、Kaggleにおいては「提出してみたらエラーだった」というというトラブルを減らすことにつながる可能性があります。