Kaggleコードを読む #1 - 脳卒中データのクリーニング・前処理
はじめに
最近AIによるコード生成能力が凄い。だからこそきちんと読めなければいけないそんな思いから他の方のkaggleを読み、処理の意図を理解することに挑戦。
早速、KaggleのStroke Prediction Datasetを使ったデータクリーニングのnotebookを読んで、「なぜこの処理をしているか」を1行ずつ理解していきます。(完全に自分の学習用)
参考notebook:Stroke Dataset - Data Cleaning & Preprocessing(muhammedriswan7)
データの概要
- データセット:Kaggle - Stroke Prediction Dataset
- サイズ:5110行 × 12列
- 目的変数:stroke(0: 脳卒中なし / 1: 脳卒中あり)
- 不均衡:stroke=1 は約5%
1. データの読み込みと確認
コード
import pandas as pd
df = pd.read_csv("/kaggle/input/stroke-prediction-dataset/healthcare-dataset-stroke-data.csv")
df.info()
pdと記載する理由:
pdはpandasの慣習的な省略形(import pandas as pdで定義)。毎回pandas.と書くより短くて楽。
df.info()で列名・データ型・欠損値の有無を一括確認。前処理の前に必ずデータの全体像を把握する。
2. id列の除外
コード
df.drop('id', axis=1, inplace=True)
idを除外する理由:
idは個人識別子であり、脳卒中の予測とは無関係。特徴量に含めるとノイズになる。
-
axis=1:列を削除(axis=0は行の削除) -
inplace=True:元のdfを直接書き換える
注意:元のデータを残したい場合は先に
df_original = df.copy()でコピーを作成しておく。
3. カテゴリ変数の値の確認
コード
column_names = ['gender', 'hypertension', 'heart_disease', 'ever_married',
'work_type', 'Residence_type', 'smoking_status', 'stroke']
for col in column_names:
print(df[col].value_counts())
print("*" * 50)
カテゴリ変数の値ごとの件数確認:
指定したカテゴリ変数の値ごとの件数を確認する。表記のばらつきや、件数が極端に少ないカテゴリを発見するため。
"*" * 50は変数ごとの区切り線。視認性のための工夫。こういう工夫あまりしたことないな。
4. Otherの除外
コード
df = df[df['gender'] != 'Other']
df['gender'].value_counts()
Otherを除外する理由:
genderのOtherは1件のみ。
1件しかないカテゴリは:
- モデルが学習できない(パターンが見つけられない)
- One-Hot Encodingすると
gender_Otherという列が生まれるが、ほぼ全行が0でノイズになる
そのためOtherに該当する1行を削除。gender列自体は残る。
df[df['gender'] != 'Other']は各行がOtherでなければTrue、OtherならFalseを返し、Trueの行だけ選択している。
5. 表記の統一(work_type)
コード
df['work_type'] = df['work_type'].replace({
'children': 'Children',
'Govt_job': 'Govt_Job',
'Never_worked': 'Never_Worked',
'Self-employed': 'Self_Employed'
})
df['work_type'].unique()
表記を統一する理由:
'children'と'Children'はコンピューターにとって別の値として扱われる。表記がバラついたままOne-Hot Encodingすると、同じ意味なのに別の列が生まれてしまう。
処理後にunique()で意図通りに変更されたか確認している。
6. 重複行の確認
コード
df.duplicated().sum()
重複行を確認する理由:
duplicated()は各行に対してTrue/Falseを返す。最初に出てきた行はFalse、2回目以降の重複行がTrue。.sum()でTrueの数=重複行数を確認。
重複行があるとモデルが同じパターンを複数回学習してしまい、過学習につながる可能性がある。
このデータでは0件だったので削除処理は不要。
7. BMIの欠損値補完
コード
df['bmi'].isna().sum() # 欠損数を確認
df['bmi'] = df['bmi'].fillna(df['bmi'].median())
なぜ中央値か:
df.describe()でmean(平均値)と50%(中央値)を比較する。乖離が大きい=分布が歪んでいる=外れ値の影響がある。そのような場合、平均値は外れ値に引っ張られるため中央値が安全。
補完方針の目安
状況 補完方法 歪みが小さい数値列 平均値 or 中央値 歪みが大きい数値列 中央値 カテゴリ列 最頻値(mode)
8. 表記の統一(smoking_status)
コード
df['smoking_status'] = df['smoking_status'].str.title()
表記の統一:
never smoked → Never Smokedのように先頭を大文字に統一。work_typeと同じ理由で表記のばらつきを防ぐ。
.str.はpandasのSeriesに対して文字列操作をするためのアクセサ。文字列メソッドを使うための記法。
9. カラム名の統一
コード
df.columns = [col.title() for col in df.columns]
df = df.rename(columns={'Bmi': 'BMI'})
カラム名を統一する理由:
全カラム名をタイトルケース(先頭大文字)に統一。データの中身ではなく、カラム名自体を整える処理。
title()でbmi → Bmiになるが、正しい表記はBMI(全部大文字)。title()では対応できないため個別にrename()で修正。
10. カテゴリ型への変換
コード
cat_cols = ['gender', 'ever_married', 'work_type', 'Residence_type']
for col in cat_cols:
df[col] = df[col].astype('category')
何故カテゴリ型に(オブジェクト型でも良いのでは。。?):
カテゴリ型にするメリット:
-
メモリ効率:同じ文字列を繰り返し保存するより、番号で管理する方が軽い(
Male=0, Female=1など) - 意図の明示:コードを読む人に「これはカテゴリ変数だ」と伝えられる
カテゴリ型の番号はアルファベット順で割り振られる。
Female=0, Male=1。
11. 外れ値の確認(箱ひげ図)
コード
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 8))
sns.boxplot(data=df, x='BMI')
plt.show()
sns.boxplot(x=df['Avg_Glucose_Level'])
plt.show()
グラフで確認する理由:
外れ値の有無と分布を視覚的に確認するため。
箱ひげ図の読み方:
- 青い箱:25%〜75%(四分位範囲・IQR)
- 箱の中の線:中央値(50%)
- ひげ:外れ値を除いた最小・最大
- ひし形:外れ値(75% + 1.5×IQR を超えた値)
外れ値を除外しなかった理由:
BMIや血糖値の高い値は肥満・高血糖として臨床的に意味がある。医療データの外れ値は安易に除外せず、ドメイン知識で判断する。
12. 処理後の確認と保存
コード
df.info()
df.to_csv("cleaned_day2.csv", index=False)
infoメソッドを利用する理由:
前処理の後は必ずinfo()で意図通りに変更されたか確認する。
index=Falseをつけないと、CSVの先頭に0,1,2...の不要なインデックス列が追加されてしまう。
終わりに
簡単なコードだったら少しづつ理解出来るようになったけど、長くなると途端に億劫になる。皆さんどうやって勉強しているのか、コメントもらえると嬉しいです。