Ebitia's Log

技術的なことや考えたことを雑につぶやいていきたいブログ

ペンギンと南極でデータ分析練習①

はじめに

みなさんデータ分析の勉強するときにどんなデータを最初に触りましたか?

よくチュートリアルとして使われるのは、Kaggleのタイタニックであったり、ほかにもirisのアヤメだったりを使ってるものがひじょーに多いですよね。

そんなirisデータセットになり替わることを目標(?)に掲げている、palmerpenguinsというペンギンのデータセットというものを見つけたので触っていきます。

irisで花弁いじいじするよりもペンギンのほうが可愛いです。間違いない。

palmerpenguinsについて

allisonhorst/palmerpenguins

こちらで公開されてる南極大陸のペンギンのデータセットです。

いくつかの種類のペンギンの性別やサイズなどのデータが集められたデータセットになってます。

データのもととなっている研究では餌の量と性別比の関係性?の生物学的な理論があるらしく、それに関してペンギンで調査をしたようです。


いくつかアートワークも載せられていてとても愛らしいです。

Ecological Sexual Dimorphism and Environmental Variability within a Community of Antarctic Penguins (Genus Pygoscelis)

f:id:yamat_hat:20200907005029p:plain
Artwork by @allison_horst

可愛い。とてもかわいい。すごくかわいい。

実行環境

Google Colaboratory

データの分析

さて、実際にデータの分析を行ってみましょう。まずはGitHubからデータをダウンロードします。

#data dowmload
! git clone https://github.com/allisonhorst/palmerpenguins.git
#path
rawdata_path = "./palmerpenguins/inst/extdata/penguins_raw.csv"
data_path = "./palmerpenguins/inst/extdata/penguins.csv"
!pip install plotly
#import module
import os
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.offline import init_notebook_mode, iplot
#reading the data
rawpenguins_df = pd.read_csv(rawdata_path)
penguins_df = pd.read_csv(data_path)

display(rawpenguins_df.head())
display(penguins_df.head())
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
0 PAL0708 1 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen Adult, 1 Egg Stage N1A1 Yes 2007-11-11 39.1 18.7 181.0 3750.0 MALE NaN NaN Not enough blood for isotopes.
1 PAL0708 2 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen Adult, 1 Egg Stage N1A2 Yes 2007-11-11 39.5 17.4 186.0 3800.0 FEMALE 8.94956 -24.69454 NaN
2 PAL0708 3 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen Adult, 1 Egg Stage N2A1 Yes 2007-11-16 40.3 18.0 195.0 3250.0 FEMALE 8.36821 -25.33302 NaN
3 PAL0708 4 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen Adult, 1 Egg Stage N2A2 Yes 2007-11-16 NaN NaN NaN NaN NaN NaN NaN Adult not sampled.
4 PAL0708 5 Adelie Penguin (Pygoscelis adeliae) Anvers Torgersen Adult, 1 Egg Stage N3A1 Yes 2007-11-16 36.7 19.3 193.0 3450.0 FEMALE 8.76651 -25.32426 NaN
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
0 Adelie Torgersen 39.1 18.7 181.0 3750.0 male 2007
1 Adelie Torgersen 39.5 17.4 186.0 3800.0 female 2007
2 Adelie Torgersen 40.3 18.0 195.0 3250.0 female 2007
3 Adelie Torgersen NaN NaN NaN NaN NaN 2007
4 Adelie Torgersen 36.7 19.3 193.0 3450.0 female 2007


csvファイルとして生データのpenguins_raw.csvと、多少整形されたpenguins.csvが用意されてます。今回はpenguins.csvを使っていきます。


penguins_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 344 entries, 0 to 343
Data columns (total 8 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   species            344 non-null    object 
 1   island             344 non-null    object 
 2   bill_length_mm     342 non-null    float64
 3   bill_depth_mm      342 non-null    float64
 4   flipper_length_mm  342 non-null    float64
 5   body_mass_g        342 non-null    float64
 6   sex                333 non-null    object 
 7   year               344 non-null    int64  
dtypes: float64(4), int64(1), object(3)
memory usage: 21.6+ KB

344匹のペンギンのデータです。

penguins_df.isnull().sum()
species               0
island                0
bill_length_mm        2
bill_depth_mm         2
flipper_length_mm     2
body_mass_g           2
sex                  11
year                  0
dtype: int64

どうやらデータには欠損値が含まれてるようですね。

欠損値が含まれてる場合には、


①欠損値が含まれ列or行の削除

②別の数値で穴埋めする


などのアプローチがあります。

...が、今回はデータ数が344と少なめなため、欠損値処理で平均値をとっても正確でない可能性があります。

なのでずっぱしと削除してしまいます。

penguins_df = penguins_df.dropna()


さて、欠損値を削除したので、ここからデータを見ていきましょう

penguins_df.describe()
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g year
count 333.000000 333.000000 333.000000 333.000000 333.000000
mean 43.992793 17.164865 200.966967 4207.057057 2008.042042
std 5.468668 1.969235 14.015765 805.215802 0.812944
min 32.100000 13.100000 172.000000 2700.000000 2007.000000
25% 39.500000 15.600000 190.000000 3550.000000 2007.000000
50% 44.500000 17.300000 197.000000 4050.000000 2008.000000
75% 48.600000 18.700000 213.000000 4775.000000 2009.000000
max 59.600000 21.500000 231.000000 6300.000000 2009.000000

統計量はこんな感じですね。

まずは島ごとにどのペンギンが住んでるか見てみましょう

pd.DataFrame(penguins_df.groupby(["island","species"])["species"].count())
species
island species
Biscoe Adelie 44
Gentoo 119
Dream Adelie 55
Chinstrap 68
Torgersen Adelie 47

提供されてるデータには3つの島があり、アデリーペンギンだけはどの島にも住んでいるようです。

この島は南極大陸のどこにあるのかもfoliumを使って可視化してみます。

!pip install folium
import folium

biscoe = [-65.433333,-65.5]
dream = [-64.733333,-64.233333]
torgersen = [-64.766667,-64.083333]

LAT = (biscoe[0]+dream[0]+torgersen[0])/3
LNG = (biscoe[1]+dream[1]+torgersen[1])/3

m = folium.Map(location=[-64.766667,-64.083333],zoom_start=7)

folium.CircleMarker(
    radius = 10,
    location = biscoe,
    popup = "<b>" + "biscoe" + "</b>",
    color = "#7fffd4",
    fill_color= "#7fffd4",
    fill = True,
).add_to(m)

folium.CircleMarker(
    radius = 10,
    location = dream,
    popup = "<b>" + "dream" + "</b>",
    color = "#ffd700",
    fill_color= "#ffd700",
    fill = True,
).add_to(m)

folium.CircleMarker(
    radius = 10,
    location = torgersen,
    popup = "<b>" + "torgersen" + "</b>",
    color = "#ff7f50",
    fill_color= "#ff7f50",
    fill = True,
).add_to(m)

m

See the Pen zYqpEZM by yamatia (@yamatia) on CodePen.

jupyter book上ではもう少しスタイリッシュな形式で見れるのですが、 はてなブログに張り付ける都合上、CodePenを使ってhtmlを張り付けています。

地形的にはBiscoeだけ海を挟んだ先のようですね。

まあ正直なところ南極の地図見てもどこがどこだかさっぱりです。北極と差し替えられてても気づかないでしょう...


次に3種のペンギンはどのような違いがあるのでしょうか。GitHubにアートワークがあげられてました。

試しに画像を表示してみましょう。

from IPython.display import Image,display_png
display_png(Image("./palmerpenguins/man/figures/lter_penguins.png",height=400,width=800))

f:id:yamat_hat:20200907012002p:plain
Artwork by @allison_horst

可愛い。もう可愛い。たまらんですね...

提供されてるデータはどのようなものでしょうか?

penguins_df.columns
Index(['species', 'island', 'bill_length_mm', 'bill_depth_mm',
       'flipper_length_mm', 'body_mass_g', 'sex', 'year'],
      dtype='object')
display_png(Image("./palmerpenguins/man/figures/culmen_depth.png",height=600,width=800))

f:id:yamat_hat:20200907005029p:plain
Artwork by @allison_horst

くちばし回りは画像の部分ですね。

他には、speciesはペンギンの種類、islandは島の名前、sexはオスかメスか、flipper_lengthはつばさの長さ、body_mass_gは体重ですね。

yearについては...データ元の論文読みましたがどうやら卵1つの段階(one-egg stage)で生体捕獲してサイズなど測定しているのでその記録日でしょうか?


さて、ここから本格的にデータを見ていきましょう。

penguins_df.head()
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
0 Adelie Torgersen 39.1 18.7 181.0 3750.0 male 2007
1 Adelie Torgersen 39.5 17.4 186.0 3800.0 female 2007
2 Adelie Torgersen 40.3 18.0 195.0 3250.0 female 2007
4 Adelie Torgersen 36.7 19.3 193.0 3450.0 female 2007
5 Adelie Torgersen 39.3 20.6 190.0 3650.0 male 2007

まずは種類や島ごとの個体数、性別について見ていきます。

count_df = pd.DataFrame(penguins_df.groupby(["species","island","sex"])["species"].count()).rename(columns={"species":"counts"})
count_df
counts
species island sex
Adelie Biscoe female 22
male 22
Dream female 27
male 28
Torgersen female 24
male 23
Chinstrap Dream female 34
male 34
Gentoo Biscoe female 58
male 61
import plotly.express as px
fig = px.bar(count_df.reset_index(),x="sex",y="counts",color="sex",barmode="group",facet_row="island",facet_col="species",
             category_orders={"species":["Adelie","Chinstrap","Gentoo"],"island":["Biscoe","Dream","Torgersen"],})

fig.show()

f:id:yamat_hat:20200907013017p:plain

アデリーペンギンの島ごとの個体数や、それぞれ性差での個体数は大きな違いは見られませんね。

次はサイズについて見ていきましょう。

島ごとにペンギンの生育環境が大きく違っていると、今回のように島ごとに種類が違うデータを比較するときに不都合が生まれる可能性もあります、

なのでまずは、 3つの島のすべてに生息しているアデリーペンギンのデータを比較してみてみます。

import plotly.figure_factory as ff
from plotly.subplots import make_subplots
import plotly.graph_objects as go

adelie_df = penguins_df[penguins_df["species"]=="Adelie"]

features = ["bill_length_mm","bill_depth_mm","flipper_length_mm","body_mass_g"]

for feature in features:
    df1 = adelie_df[adelie_df["island"]=="Biscoe"][feature]
    df2 = adelie_df[adelie_df["island"]=="Dream"][feature]
    df3 = adelie_df[adelie_df["island"]=="Torgersen"][feature]

    hist_data = [df1,df2,df3]
    group_labels = ["Biscoe","Dream","Torgersen"]

    fig = ff.create_distplot(hist_data,group_labels, histnorm= 'probability', bin_size=adelie_df[feature].max()/100,curve_type="kde")
    fig.update_layout(title=feature,bargap=0.01)
    fig.show()

f:id:yamat_hat:20200907013037p:plain f:id:yamat_hat:20200907013051p:plain f:id:yamat_hat:20200907013126p:plain f:id:yamat_hat:20200907013139p:plain

あまり顕著な差は見られませんね...地形的にもちょっと海を挟んだだけなので大きな差はないのかもしれません。

島ごとにあまり差はなさそうなことが分かったので、次は島の差は無視して種類ごとのサイズの違いを見ていきましょう。

import plotly.express as px
fig = px.scatter_matrix(penguins_df,
                        dimensions=["bill_length_mm","bill_depth_mm","flipper_length_mm","body_mass_g"],
                        color="species",
                        width=800,height=800)
fig.show()

f:id:yamat_hat:20200907013156p:plain わりと種類ごとのサイズには違いがあるようですね。

例えば、ジェンツーペンギンなどはbill_depth_lengthが小さいものが多いですね。

他にも、bill_depth_mmとbill_length_mmのプロットを見ればかなりはっきり分離しているので、くちばしのサイズデータがあればペンギンの種類が見分けられそう...と考えられますね。

こんな風に特徴量を見てると、機械学習でサイズデータからペンギンの種類を予測できそうかどうか...といった予想や方針を考えられると思います。


さらに詳しく見ていきます。

features = ["bill_length_mm","bill_depth_mm","flipper_length_mm","body_mass_g"]

for feature in features:
    df1 = penguins_df[penguins_df["species"]=="Adelie"][feature]
    df2 = penguins_df[penguins_df["species"]=="Gentoo"][feature]
    df3 = penguins_df[penguins_df["species"]=="Chinstrap"][feature]

    hist_data = [df1,df2,df3]
    group_labels = ["Adelie","Gentoo","Chinstrap"]

    fig = ff.create_distplot(hist_data,group_labels, histnorm= 'probability', bin_size=penguins_df[feature].max()/100,curve_type="kde")
    fig.update_layout(title=feature,bargap=0.01)
    fig.show()

f:id:yamat_hat:20200907013208p:plainf:id:yamat_hat:20200907013220p:plainf:id:yamat_hat:20200907013229p:plainf:id:yamat_hat:20200907013237p:plain

割とはっきりと分布の違いが見て取れますね。ジェンツーペンギンは羽は長めで重いことが多いようです。ぽっちゃり系ですね。

くちばしの長さと深さの2軸でデータを見てみます。

import plotly.express as px
fig = px.scatter(penguins_df, x="bill_length_mm", y="bill_depth_mm",                        color="species", marginal_y="violin",
           marginal_x="box", trendline="ols", template="simple_white")
fig.show()

f:id:yamat_hat:20200907202045p:plain

かなりくっきり分かれてて気持ちがいいですね。

ペンギン博士となれば目隠ししててもくちばしを触るだけで種類がわかるのかもしれません。

最後に特徴量どうしの相関について見てみましょう

import seaborn as sns
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(15,15),facecolor="white")
sns.heatmap(pd.get_dummies(penguins_df).corr(),cmap="coolwarm")

f:id:yamat_hat:20200907013336p:plain

まあデータの分布をみて直感的に感じることと一致します。例えば、Gentooとflipper_length_mmは高い相関となっています。

まとめ

さて、こんな感じでペンギンのデータを使って可視化をしてみました。

pandasやplotlyのいい練習になりますね。

それだけではありません。データに触れてるだけでペンギンへの信仰心が高まるという副次効果もあります。

植物よりは動物のほうが強いに決まってるので(?)、皆さんもぜひペンギンのデータをいじってみてください。

データ数が少ないので微妙ですが、またいつかこちらのデータを使って機械学習の練習をしてみる予定です。

参考記事