🐧

【Python】seabornのpenguins datasetを使って分類問題をといてみる

2023/06/29に公開

seabornには様々な公開データがあります。今回はその中の「prnguins dataset」を使って単回帰分析や重回帰分析をしてみます。

目次[clickで開く/閉じる]
  • データの概要
  • データの中身の確認
  • 分類問題を解く
  • ソースコード
  • まとめ

データの概要

seabornはPythonデータ視覚化ライブラリでいくつかデータセットが用意されています。今回はその中でも「Paalmer Penguins」というペンギンの測定データを含んでいる「penguins dataset」(ペンギンデータセット)を用いて分類問題を解きます。

では、実際のデータの中身についてみていきましょう。

  • データ数:344
  • カラム数:7
変数名 詳細
island ペンギンが生息する島の名前('Torgersen', 'Biscoe', 'Dream')
bill_length_mm ペンギンのくちばしの長さ(mm)
bill_depth_mm ペンギンのくちばしの奥行き(mm)
flipper_length_mm ペンギンのヒレの長さ(mm)
body_mass_g ペンギンの体重(g)
sex ペンギンの性別('Male', 'Female')
species ペンギンの種類('Adelie', 'Chinstrap', 'Gentoo')
year データが収集された年

各データの詳細については以下のページもご参考ください。
https://github.com/mwaskom/seaborn-data/blob/master/penguins.csv
https://github.com/allisonhorst/palmerpenguins

データの中身の確認

まずは基本的な必要ライブラリをまとめてインポートしておきましょう。

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

データを出力してみます。この時についでにデータの大きさやデータの型、欠損値の有無について確認しておきましょう。

# データセットの読み込み
data = sns.load_dataset("penguins")
print(data.shape)
display(data.head())
print(data.dtypes)
print(data.isnull().sum())

dataframeで表示した結果欠損値が含まれていることがわかりました。それでは欠損値を含む行を削除していきましょう。

# 欠損値削除
data = data.dropna()
print(data.shape)
display(data.head())
print(data.dtypes)
print(data.isnull().sum())

きちんと削除できたか確かめます。

欠損値が全て0になり、データの大きさも元々あったsexの欠損値11行分少なくなり(333, 7)に更新されています。これで欠損値のないきれいなデータを作成できました。

回帰分析をするにはもう1つデータを変更しなければならないところがあります。island, sex, speciesなどのように定性的なデータを回帰分析で使用する場合はsexのMale = 0, Female = 1のように数値データとして扱う必要があります。これをダミー変数と言います。このようにカテゴリカルなデータを使用するときはダミー変数に変換することでデータの特徴を適切に表すことができます。それではisland, sex, speciesをダミー変数に変換していきましょう。

# speciesをダミー変数にする
data['species'] = data['species'].replace('Adelie', 0)
data['species'] = data['species'].replace('Chinstrap', 1)
data['species'] = data['species'].replace('Gentoo', 2)

# islandをダミー変数にする
data['island'] = data['island'].replace('Torgersen', 0)
data['island'] = data['island'].replace('Dream', 1)
data['island'] = data['island'].replace('Biscoe', 2)

# sexをダミー変数にする
data['sex'] = data['sex'].replace('Male', 0)
data['sex'] = data['sex'].replace('Female', 1)

print(data.shape)
display(data.head())

それではデータを確認してみます。

island, sex, species列の値が0, 1, 2で表されています。これでようやく分析の準備が整いました。
ここで一度データをペアプロットとヒートマップで可視化してみます。

# データの可視化
# 10s位かかります
sns.pairplot(data, hue='species', palette = 'muted')
plt.legend()
plt.title('Pairplot of penguins')
plt.savefig('pairplot.png')

# ヒートマップを表示
plt.figure(figsize=(12, 9))
sns.heatmap(data.corr(), annot=True, cmap='coolwarm')
plt.title('Correlation Matrix')
plt.savefig('heatmap.png')

ついでに相関係数も確認しておきます。今回はわかりやすいように相関係数の大きさで色を付けてDataFrameで表示します。

# 相関係数を計算
corr = data.corr()
corr.style.background_gradient(cmap='coolwarm', axis=None)

分類問題を解く

今回は、以下の条件の分類問題を解いてみます。

  • クラス:Adelie, Chinstrap, Gentoo
  • 特徴量:bill_length_mm, flipper_length_mm
  • 分類タスク:クラス分類(k-近傍法)
  • 判定方法:判定対象と近い点をkこ見つけ、各店がどのクラスに属するかをみて、一番多いクラスを判定結果とする。(距離はユークリッド距離とする)

では、早速コードを書いていきます。まずは、散布図を描画してどのような分布かを確かめます。

散布図描画

# 特徴量とターゲット変数の準備
features = ['bill_length_mm', 'body_mass_g']
target = 'species'
X = df[features]
y = df[target]

# 散布図描画
fig = plt.figure(figsize=(5, 5))
palette = sns.color_palette()
palette3 = {'Adelie':palette[2], 'Chinstrap':palette[1], 'Gentoo':palette[0]}
sns.scatterplot(data=df, x=features[0], y=features[1], hue=target, palette=palette3)

この散布図を見ることでどのような決定境界が引けそうかわかりますね。
では続いて、訓練データとテストデータに分割して予測を行います。

訓練データとテストデータに分割

# データセットをトレーニングセットとテストセットに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

テストデータで分類できているか散布図で確認

データセットを訓練:テスト = 8 : 2に分割しました。テストデータについてkをいくらに設定したら精度の高い分類ができるかどうか散布図で確認していきます。散布図だけでは視覚的にしか確かめられないので、決定係数も一緒に出力させます。
kの値はk = 1, 3, 5, 9, 15で試しました。

k_list = [1, 3, 5, 9, 15]
for k in k_list:
    clf = KNeighborsClassifier(n_neighbors=k)
    clf.fit(X_train, y_train)

    # テストデータの予測
    y_pred = clf.predict(X_test)

    # 分類精度の評価
    accuracy = accuracy_score(y_test, y_pred)

    # 予測データの散布図を描画
    plt.figure(figsize=(5, 5))
    sns.scatterplot(data=X_test, x='bill_length_mm', y='body_mass_g', hue=y_pred, palette=palette3)
    plt.title(f'k = {k}, Acc = {accuracy:.3f}')
    plt.legend()
    plt.savefig(f'knn_k{k}.png')
    plt.show()

上記のコードを実行するとkの値ぶんの散布図が描画されると思います。今回は私のマシン上で一番精度の良かった、k = 5の散布図を貼り付けます。

決定係数が0.821となり、なかなか良い精度で分類できたと思いますが、散布図を見るとイマイチかなあと思ってしまいます。テストデータのデータ数が少ない分散布図が"疎"になってしまったせいですかね、、、。

今回は訓練データとテストデータに分割して分類予測を行いましたが、データを分割せず、シンプルに分類問題を解くこともできます。決定境界を工夫したりなどです。それについては只今勉強中ですのでしばらくお待ちください。

ソースコード

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# データの準備
df = sns.load_dataset('penguins')
df.dropna(inplace=True)  # 欠損値のある行を削除

# 特徴量とターゲット変数の準備
features = ['bill_length_mm', 'body_mass_g']
target = 'species'
X = df[features]
y = df[target]

# 散布図描画
fig = plt.figure(figsize=(5, 5))
palette = sns.color_palette()
palette3 = {'Adelie':palette[2], 'Chinstrap':palette[1], 'Gentoo':palette[0]}
sns.scatterplot(data=df, x=features[0], y=features[1], hue=target, palette=palette3)
plt.savefig("scatter.png")

# データセットをトレーニングセットとテストセットに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

k_list = [1, 3, 5, 9, 15]
for k in k_list:
    clf = KNeighborsClassifier(n_neighbors=k)
    clf.fit(X_train, y_train)

    # テストデータの予測
    y_pred = clf.predict(X_test)

    # 分類精度の評価
    accuracy = accuracy_score(y_test, y_pred)

    # 予測データの散布図を描画
    plt.figure(figsize=(5, 5))
    sns.scatterplot(data=X_test, x='bill_length_mm', y='body_mass_g', hue=y_pred, palette=palette3)
    plt.title(f'k = {k}, Acc = {accuracy:.3f}')
    plt.legend()
    plt.savefig(f'knn_k{k}.png')
    plt.show()

まとめ

今回はpenguinsセータセットを使って分類予測を行いました。
精度の高い予測ができましたが、決定境界の描画をして実際どのような境界なのかを可視化したいと思います。

内容に不備がありましたらご連絡いただけると幸いです。

Discussion