これまでに学んだデータの読み込み,可視化,代表値,ばらつき,相関を組み合わせて,一つのデータを分析しよう.本章の問いは,ペンギンの種類や性別によって,体の大きさはどのように違うのか である.
まず,記録の内容と欠損値を確認する.次に,ひれの長さの分布と体重の中心・ばらつきを種類ごとに比べる.最後に,性別による違いや,くちばしの長さと厚さの関係を調べる.全体をまとめて見たときと,グループに分けて見たときの違いに注目しよう.
どのようなデータか
南極で調査された 3 種類のペンギン(図 11.1 )のデータを使う (Gorman ほか 2014年 ; Horst ほか 2020年 ) .1 行が 1 羽のペンギンに対応し,種類,島,体の大きさ,性別,調査年が記録されている.体の大きさは,くちばしの長さ・厚さとひれの長さを mm,体重を g で表している.各列の意味とデータの詳しい出典は,セクション A.2.2 と 表 A.3 を参照すること.
図 11.1: データに含まれる 3 種類のペンギン.写真:Andrew Shiva / Wikipedia,CC BY-SA 4.0 .Wikimedia Commons の写真(a ,b ,c )をトリミングして使用した.
penguins.csv をダウンロードし,チャプター 4 と同じ手順でアップロードする.セクション 4.10.1 の方法で,https://zi-ang-liu.github.io/data-science-book/download/penguins.csv から直接読み込んでもよい.本章のコードは,上から順に実行する.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
penguins = pd.read_csv("penguins.csv" )
penguins.head()
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
行数・列数とデータ型
print (penguins.shape)
print (penguins.dtypes)
(344, 8)
species object
island object
bill_length_mm float64
bill_depth_mm float64
flipper_length_mm float64
body_mass_g float64
sex object
year int64
dtype: object
344 羽について,8 項目が記録されている.species(種類),island(島),sex(性別)には文字列が入っており,そのほかの列は数値である.
分析に使える記録はどれか
head() の表示にも,NaN と表示された値があった.各列の欠損値の数を調べよう.
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
体の大きさを表す 4 列ではそれぞれ 2 件,性別 sex では 11 件の欠損値がある.どの行で何が記録されていないのかを,条件を使って確かめる.penguins["body_mass_g"].isna() は,体重が欠損値の行で True となる.
missing_mass = penguins[penguins["body_mass_g" ].isna()]
missing_mass
3
Adelie
Torgersen
NaN
NaN
NaN
NaN
NaN
2007
271
Gentoo
Biscoe
NaN
NaN
NaN
NaN
NaN
2009
体重が記録されていない 2 羽は,くちばしとひれの大きさ,性別も記録されていない.この 2 羽については,種類,島,調査年しかわからない.
性別が記録されていない 11 羽についても,同じように調べる.取り出した表に isna().sum() を使うと,その中の欠損値の数がわかる.
missing_sex = penguins[penguins["sex" ].isna()]
missing_sex.isna().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
性別が記録されていない 11 羽のうち,体重などの欠損値は 2 件だけであり,残りの 9 羽は体の大きさが記録されている.体の大きさを調べるときに,性別が欠損しているという理由でこの 9 羽まで除くと,使えるデータが減ってしまう.欠損値をどう扱うかは,何を分析するかによって決めよう.
ひれの長さに二つの山があるのはなぜか
種類ごとの羽数
種類ごとの表を用意し,まず羽数を確かめる.
adelie = penguins[penguins["species" ] == "Adelie" ]
chinstrap = penguins[penguins["species" ] == "Chinstrap" ]
gentoo = penguins[penguins["species" ] == "Gentoo" ]
以下では,この三つの表を使って,種類ごとの違いを調べる.取り出した表の行数は,len() で求められる.
species_names = ["Adelie" , "Chinstrap" , "Gentoo" ]
counts = [len (adelie), len (chinstrap), len (gentoo)]
print (counts)
この羽数を棒グラフで比べる.
plt.figure(figsize= (6 , 4 ))
plt.bar(species_names, counts, color= "steelblue" )
plt.xlabel("Species" )
plt.ylabel("Number of penguins" )
plt.tight_layout()
plt.show()
図 11.2 から,Adelie が 152 羽と最も多く,Gentoo が 124 羽,Chinstrap が 68 羽であることがわかる.種類の名前には順序がない.このような項目の値を比べるときも,棒グラフを使う.
ひれの長さの分布
次に,ひれの長さ flipper_length_mm の分布を,区間の幅を 5 mm にしたヒストグラムで調べる.
bins = [170 , 175 , 180 , 185 , 190 , 195 , 200 , 205 , 210 , 215 , 220 , 225 , 230 , 235 ]
plt.figure(figsize= (7 , 4 ))
plt.hist(penguins["flipper_length_mm" ], bins= bins, color= "steelblue" , edgecolor= "white" )
plt.xlabel("Flipper length (mm)" )
plt.ylabel("Number of penguins" )
plt.xticks(bins)
plt.tight_layout()
plt.show()
ひれの長さが記録されていない 2 羽は,plt.hist() で自動的に除かれる.そのため,図 11.3 には 342 羽分の分布が描かれている.
図 11.3 には,190 mm 以上 195 mm 未満の区間を頂点とする山と,210 mm 以上 220 mm 未満あたりの山の,二つの山がある.その間の 200 mm 以上 210 mm 未満では,羽数が少ない.山が二つある分布は,性質の異なるグループが混ざっているときに現れやすい.このデータには 3 種類のペンギンが含まれているので,種類による違いが考えられる.
種類ごとに色を分けて描く
山が二つある理由を確かめるため,種類ごとに色を分けたヒストグラムを描く.plt.hist() に,種類ごとのデータをリストにまとめて渡し,stacked=True を指定すると,種類ごとの度数を積み重ねて描ける.label には,凡例に表示する種類の名前を,データと同じ順に指定する.
plt.figure(figsize= (7 , 4 ))
plt.hist(
[
adelie["flipper_length_mm" ],
chinstrap["flipper_length_mm" ],
gentoo["flipper_length_mm" ],
],
bins= bins,
stacked= True ,
label= species_names,
edgecolor= "white" ,
)
plt.xlabel("Flipper length (mm)" )
plt.ylabel("Number of penguins" )
plt.xticks(bins)
plt.legend()
plt.tight_layout()
plt.show()
各区間の棒全体の高さは,図 11.3 と同じである.図 11.4 から,左の山は主に Adelie と Chinstrap,右の山は主に Gentoo からなることがわかる.Gentoo のひれは,ほかの 2 種類より長い.
全体のヒストグラムだけでは,山が二つある理由はわからなかった.種類ごとに分けると,このデータでは種類による違いが二つの山に対応していることがわかる.体の大きさを比べるときは,全体の分布だけでなく,種類ごとの分布にも注目しよう.
種類によって体重はどのくらい違うか
重いペンギンはどの種類か
体重が 5000 g 以上のペンギンを取り出し,そのうち何羽が Gentoo(ジェンツーペンギン)かを数える.
heavy = penguins[penguins["body_mass_g" ] >= 5000 ]
heavy_gentoo = heavy[heavy["species" ] == "Gentoo" ]
print ("5000 g 以上の羽数:" , len (heavy))
print ("そのうち Gentoo の羽数:" , len (heavy_gentoo))
5000 g 以上の羽数: 67
そのうち Gentoo の羽数: 67
5000 g 以上の 67 羽は,すべて Gentoo である.体重が欠損している行は,条件を満たさないものとして扱われ,取り出されない.ひれの長さだけでなく,体重にも種類による違いがありそうだ.次に,箱ひげ図と数値で,体重の中心とばらつきを比べよう.
箱ひげ図で比べる
体重 body_mass_g には欠損値が 2 件ある.np.percentile() は,欠損値を含むデータに使うと結果が nan となり,箱ひげ図も正しく描けない.そこで,dropna() を使い,体重の欠損値を除いた Series を用意する.性別が欠損していても,体重が記録されている行は使う.
adelie_mass = adelie["body_mass_g" ].dropna()
chinstrap_mass = chinstrap["body_mass_g" ].dropna()
gentoo_mass = gentoo["body_mass_g" ].dropna()
plt.figure(figsize= (6 , 4 ))
plt.boxplot(
[adelie_mass, chinstrap_mass, gentoo_mass],
tick_labels= ["Adelie" , "Chinstrap" , "Gentoo" ],
)
plt.ylabel("Body mass (g)" )
plt.grid(axis= "y" , alpha= 0.3 )
plt.tight_layout()
plt.show()
図 11.5 では,Gentoo の箱が,ほかの 2 種類より高い位置にある.一方,Adelie と Chinstrap の中央値の線は,同じ高さにある.
数値で比べる
種類ごとに,中央値,四分位範囲,標準偏差を求める.ここでは,母集団のばらつきを標本から推定するときによく使われる,不偏分散の平方根を標準偏差として用いる.そのため,np.std() には ddof=1 を指定する.三つの Series をリストにまとめ,for 文で順に計算する.
masses = [adelie_mass, chinstrap_mass, gentoo_mass]
for i in range (3 ):
mass = masses[i]
q1 = np.percentile(mass, 25 )
q3 = np.percentile(mass, 75 )
std = np.std(mass, ddof= 1 )
print (species_names[i])
print (" 中央値:" , mass.median())
print (" 四分位範囲:" , q3 - q1)
print (" 標準偏差:" , round (std, 1 ))
Adelie
中央値: 3700.0
四分位範囲: 650.0
標準偏差: 458.6
Chinstrap
中央値: 3700.0
四分位範囲: 462.5
標準偏差: 384.3
Gentoo
中央値: 5000.0
四分位範囲: 800.0
標準偏差: 504.1
結果を表にまとめると,次のようになる.
中央値
3700 g
3700 g
5000 g
四分位範囲
650 g
462.5 g
800 g
標準偏差(ddof=1)
458.6 g
384.3 g
504.1 g
Gentoo の中央値は 5000 g で,ほかの 2 種類より 1300 g 大きい.Adelie と Chinstrap の中央値はどちらも 3700 g だが,四分位範囲も標準偏差も Chinstrap の方が小さい.Chinstrap の体重は,Adelie よりも中央値の近くに集まっている.中心が同じでも,ばらつきが同じとは限らない.
また,四分位範囲で比べても,標準偏差で比べても,ばらつきは小さい方から Chinstrap,Adelie,Gentoo の順である.
離れた点は誤った記録なのか
図 11.5 では,Chinstrap に,ひげの外の点が二つある.体重が 4800 g と 2700 g の 2 羽である.この 2 羽の記録を確かめる.
heaviest = chinstrap[chinstrap["body_mass_g" ] == 4800 ]
lightest = chinstrap[chinstrap["body_mass_g" ] == 2700 ]
print (heaviest[["species" , "body_mass_g" , "sex" ]])
print (lightest[["species" , "body_mass_g" , "sex" ]])
species body_mass_g sex
313 Chinstrap 4800.0 male
species body_mass_g sex
314 Chinstrap 2700.0 female
4800 g の個体は雄(male),2700 g の個体は雌(female)である.Chinstrap の体重の中央値を,性別ごとに求めてみよう.
chinstrap_male = chinstrap[chinstrap["sex" ] == "male" ]
chinstrap_female = chinstrap[chinstrap["sex" ] == "female" ]
print ("雄の中央値(g):" , chinstrap_male["body_mass_g" ].median())
print ("雌の中央値(g):" , chinstrap_female["body_mass_g" ].median())
雄の中央値(g): 3950.0
雌の中央値(g): 3550.0
雄の中央値は 3950 g,雌の中央値は 3550 g であり,雄の方が重い.重い方の点が雄,軽い方の点が雌であることは,この性別による違いと合っている.体重のばらつきには,種類だけでなく性別も関係していそうだ.ただし,これだけで,二つの記録が正しいかどうかを判断することはできない.箱ひげ図でひげの外にある点も,直ちに誤った記録とみなすことはできない.外れ値の候補を見つけたら,削除する前に,その個体のほかの記録や調査方法を確かめよう.
全体と種類別で相関は同じか
ここまでは,一つの変数の分布を種類ごとに比べた.発展課題として,二つの変数の関係についても,全体と種類ごとの結果を比べてみよう.
練習 11.1 本章で読み込んだ penguins を使い,くちばしの長さ bill_length_mm と厚さ bill_depth_mm の関係を調べよ.
すべてのペンギンについて,くちばしの長さを横軸,厚さを縦軸とする散布図を描き,相関係数を求めよ.
種類ごとに相関係数を求めよ.
1 と 2 の結果を比べ,わかることを説明せよ.
くちばしの大きさが記録されていない行は,あらかじめ除いておく.penguins.dropna(subset=["bill_length_mm", "bill_depth_mm"]) とすると,指定した 2 列のどちらかが欠損値である行を除いた表が得られる.
わかったことをまとめる
今回のデータから,次のことがわかった.
ひれの長さの分布には二つの山があり,左の山は主に Adelie と Chinstrap,右の山は主に Gentoo からなる.
Gentoo は,ほかの 2 種類より体重の中央値が大きい.Adelie と Chinstrap は中央値が同じでも,体重のばらつきが異なる.
Chinstrap では,雄の方が雌より体重の中央値が大きい.体重の違いを考えるには,種類だけでなく性別にも注目する必要がある.
これらは,このデータに記録された個体についての結果である.データに含まれる種類ごとの羽数から,南極全体に生息する羽数の割合まではわからない.どこで,いつ,どのように調査されたデータかを確かめ,結論の範囲を明確にしよう.
分析結果を説明するときは,「Gentoo は大きい」だけでなく,何を比べたのか,どの数値や図を根拠にしたのかを示す.例えば,「このデータでは,Gentoo の体重の中央値は 5000 g で,Adelie と Chinstrap より 1300 g 大きい」と書くと,違いが具体的に伝わる.
Gorman, Kristen B, Tony D Williams, と William R Fraser. 2014年.
「Ecological sexual dimorphism and environmental variability within a community of Antarctic penguins (genus Pygoscelis )」 .
PLoS One 9 (3): e90081.
https://doi.org/10.1371/journal.pone.0090081 .
Horst, Allison Marie, Alison Presmanes Hill, と Kristen B Gorman. 2020年.
palmerpenguins: Palmer Archipelago (Antarctica) penguin data .
https://doi.org/10.5281/zenodo.3960218 .