ペンギンデータセットでデータエンジニアリングの基礎を学ぼう【Python/Pandas/palmerpenguins】
目次
こんにちは。
さて、昨今データサイエンス界隈を賑わせているpalmerpenguins
なるペンギンデータセット。前回の記事で既に生データをダウンロードはされたと思いますが、これをそのまま分析に使うにはちょっと面倒です。
そんなわけで今回は機械学習の初学者の第一歩、ペンギンデータセットでデータエンジニアリングとは何たるかを学びましょう。
という記事です。
よろしく。
ちなみに本記事ではプログラムにPython3を使います。
今回は第2弾として、データの中身の確認とクレンジングをします。
データを分析・機械学習に使うためには事前の準備が大事なので、ぜひ本記事でデータエンジニアリングの基礎を実践してみましょう。
まずはペンギンデータセット(palmerpenguins)をダウンロードしよう
データのダウンロードについては以下の記事にまとめました。
【ペンギンデータセットで機械学習/データサイエンスをはじめよう〜ダウンロード編【Python/palmerpenguins】
palmerpenguins データの列名を見直す
上記の記事でダウンロードして書き出したCSVファイルを読み込んでみます。
import pandas as pd
df = pd.read_csv('penguins_raw.csv')
どんなカラム(列)があるか確認してみると...
df.columns.to_list()
結果
['Unnamed: 0', 'studyName', 'Sample Number', 'Species', 'Region', 'Island', 'Stage', 'Individual ID', 'Clutch Completion', 'Date Egg', 'Culmen Length (mm)', 'Culmen Depth (mm)', 'Flipper Length (mm)', 'Body Mass (g)', 'Sex', 'Delta 15 N (o/oo)', 'Delta 13 C (o/oo)', 'Comments']
カラム名がスペースや大文字が混在していて使いにくそうなので変換しておきます。これは手作業。
col_name = [
'index',
'study_name',
'sample_number',
'species',
'region',
'island',
'stage',
'indivisual_id',
'clutch_completion',
'date_egg',
'culmen_length_mm',
'culmen_depth_mm',
'flipper_length_mm',
'body_mass_g',
'sex',
'delta_15_n',
'delta_13_c',
'comments'
]
df.columns = col_name
データのサイズ・欠損値(NaN)の有無を調べる
まずは基本から。
データのサイズを調べる。
df.shape
(344, 18)
344行 18列。
これくらいのデータならCSVをExcelで開いても中身がなんとなくわかりますが、
一応データサイエンスっぽくPythonでどんな値が入っているのかみてみましょう。
df.head(10)
先頭10行が表示されます。
NULLもありそうなので確認してみる。
df.isnull().sum()
index 0
study_name 0
sample_number 0
species 0
region 0
island 0
stage 0
indivisual_id 0
clutch_completion 0
date_egg 0
culmen_length_mm 2
culmen_depth_mm 2
flipper_length_mm 2
body_mass_g 2
sex 10
delta_15_n 14
delta_13_c 13
comments 290
データの型・ユニーク値・ユニークな値の種類を調べる
データを扱う上では、まずどんな値が入っているのかを知る必要があります。変な値が入っていると、思い通りの計算ができなかったり、計算結果が変わってきたりします。
それでは、各列のデータ型、ユニークな値、種類の数を調べてみます。
for c in col_name:
l = df[c].unique().tolist()
print('------------------------')
print(c)
print(f'dtype : {df[c].dtype}')
print(f'unique count : {len(l)}')
print(l)
indexも含んでるので、ぎゃー!って感じの出力量になりますが、1個1個見ていくと、各列の特徴がわかります。
たとえばspecies
は
------------------------
species
dtype : object
unique count : 3
['Adelie Penguin (Pygoscelis adeliae)', 'Gentoo penguin (Pygoscelis papua)', 'Chinstrap penguin (Pygoscelis antarctica)']
------------------------
3種類。データをダウンロードするところで3種類持ってきたので、まあ想定通り。
sex
の列はというと、
------------------------
sex
dtype : object
unique count : 4
['MALE', 'FEMALE', nan, '.']
------------------------
んん?nan
はわかるけど.
ってなんだ。。
こういうところが怪しげでバグに繋がりますね。性別とかめっちゃ使いそうだし。
データを使うときはちゃんとクレンジングしましょう。
分析に必要な列を抽出する
機械学習で使うときは性別を1,0のフラグ列にしたり目的に合わせて色々データを整形することになると思いますが、
ここではとりあえず分析に使えそうな列を残しておくくらいにしておきます。
機械学習については別記事で。
まず先程見たデータの中身と列の説明から必要な列を選択します。
今回は以下を選別しました。
usecols = [
'species',
'island',
'date_egg',
'culmen_length_mm',
'culmen_depth_mm',
'flipper_length_mm',
'body_mass_g',
'sex',
]
df1 = df[usecols].copy()
分析に使うデータの文字列と数値を整形する
ひとつずつ見ていきます。
まずはspecies
。
やたら文字列が長いのとカッコがついてたりするので短くします。
今回はたまたま半角スペースで区切れているので、str.split
の第一引数に半角スペースを指定すればいけそう。
df1['species_short'] = df1.species.str.split(' ',expand=True)[0]
確認
df1.species_short.unique()
array(['Adelie', 'Gentoo', 'Chinstrap'], dtype=object)
成功。スッキリ。
もし規則性のない列だったら辞書を作って置換していくのかなーと思います。
不要になった元の列は削除します。
df2 = df1.drop('species', axis=1).rename(columns={'species_short':'species'})
次は、date_egg
から生まれた年
を抽出します。
df2['year'] = pd.to_datetime(df2.date_egg).dt.year
df3 = df2.drop('date_egg', axis=1)
もともとがobject
だったので、最初にpd.to_datetime()
でdatetime
型に変換し、dt.year
で年を抽出しました。元の列はいらない子なので削除。
次は、sex
の意味不明な.
をnan
に置き換えて、大文字を小文字に揃えます。
import numpy as np
df3['sex'] = df3.sex.replace('.',np.nan)
df3['sex'] = df3.sex.str.lower()
nan
を使いたいのでnumpy
をインポート。.str.lower()
で大文字は小文字に変換されます。
array(['male', 'female', nan], dtype=object)
成功。
あと、実はflipper_length_mm
とbody_mass_g
の数値は小数点以下がないのにNaN
があるせいでfloat64
型になってしまっているんですよね。
これはデータの無駄なので、どうにかしたいところ。
とはいえ.astype(int)
はNaN
を含む列には使えないので、事前に.fillna(0)
とかしておく必要がある。
0置換は実際のデータと異なってしまうので、あまりよろしくなさそう。
とりあえず今回は無視して、そのままCSVに吐き出します。分析のときにNaN
の処理をどうするか考えることにします。
列の順番を整える
最後に列の順番を整えて、CSVに出力します。順番は本家に倣います。
outcols = [
'species',
'island',
'culmen_length_mm',
'culmen_depth_mm',
'flipper_length_mm',
'body_mass_g',
'sex',
'year'
]
df4 = df3[outcols].copy()
やってることは単純。並び替えた列名のリストを作って、その列を選択したデータフレームを別のデータフレーム(ここではdf4
)に入れただけ。
列の名前を変換する
palmerpenguinsではculmen
をbill
に置換しているので、倣います。
ちなみにculmen
は嘴峰長(しほうちょう)の英語で、上嘴(くちばし)の基部から嘴先端までの稜線の長さのこと。bill
は嘴のことです。勉強になるなあ。
参考 : Wikipedia (https://ja.wikipedia.org/wiki/鳥類用語)
df4 = df4.rename(columns={
'culmen_length_mm':'bill_length_mm',
'culmen_depth_mm':'bill_depth_mm'
})
最後にデータの中身が問題ないか、念押し確認
最後に一応、変な値がないか軽くチェックしておきます。先程と同じ方法。データを確認する癖はつけておいたほうがいい。
for c in df4.columns:
l = df4[c].unique().tolist()
print('------------------------')
print(c)
print(f'dtype : {df4[c].dtype}')
print(f'unique count : {len(l)}')
print(l)
今回は少量のデータなので簡単に目視で確認できますが、ビッグデータになると各列の特性に応じてエラーチェックのやり方を考える必要があります。たとえば数値の場合は散布図にプロットして、外れ値がないか確認するとか。
あ、そういえば今回も数値の列があるので、一番簡単な方法で見ておきましょう。
df4.describe()
.describe()
のメソッドでデータフレームの数値の統計量を一括で出してくれます。こいつ超便利。
今回注目するのはmax
とmin
ですかね。最初からdf4.max()
とdf4.min()
を使ったほうが見やすいかも。
たとえばbill_length_mm
は単位がmm
のペンギンの嘴の長さですが、最小値が32.1
、最大値が59.6
ということで、大体3cm~6cmくらい。まあそんなもんかな、という感じです。(3cmって小さい?子どもペンギンならそんなもんかな。)
ここが1000mm
とか1mm
とかあまりにも常識と異なるような値がでていたら要注意で、外れ値の除外処理とか必要になるかと思います。
データセットをCSVファイルに出力する
というわけで最終チェックも問題なさそうだったので出力します。
df4.to_csv('penguins.csv')
お疲れさまでした。
まとめ
というわけで機械学習データセットの定番、palmerpenguins
のデータを使いやすい状態に整えてみました。
ぶっちゃけ、すでに加工済みのデータはダウンロードできるのですが(でもって、その加工済みのやつがpalmerpenguins
なのだと思うのですが)、こういうのって自分で生データ見て考えながら加工することが、データエンジニアやデータサイエンティストの成長の一歩だと思います。
ぜひこの記事を参考に色々試してみてください〜
みんなでペンギンデータセットを盛り上げていきましょう!