統計・データ解析のタイタニック号沈没事故でも扱ったデータをPythonでいじってみよう。
ここでは seaborn のデータレポジトリにあるデータを使う:
import seaborn as sns titanic = sns.load_dataset('titanic') titanic
survived pclass sex age sibsp ... adult_male deck embark_town alive alone 0 0 3 male 22.0 1 ... True NaN Southampton no False 1 1 1 female 38.0 1 ... False C Cherbourg yes False 2 1 3 female 26.0 0 ... False NaN Southampton yes True 3 1 1 female 35.0 1 ... False C Southampton yes False 4 0 3 male 35.0 0 ... True NaN Southampton no True .. ... ... ... ... ... ... ... ... ... ... ... 886 0 2 male 27.0 0 ... True NaN Southampton no True 887 1 1 female 19.0 0 ... False B Southampton yes True 888 0 3 female NaN 1 ... False NaN Southampton no False 889 1 1 male 26.0 0 ... True C Cherbourg yes True 890 0 3 male 32.0 0 ... True NaN Queenstown no True [891 rows x 15 columns]
乗客891人しか含んでいない。Rのデータセットは全体2201人,乗客1316人であった。これは Kaggle の titanic データセット の train.csv から生成したデータであるためである(test.csv の418人分のデータは入っていない)。
titanic.columns
Index(['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked', 'class', 'who', 'adult_male', 'deck', 'embark_town', 'alive', 'alone'], dtype='object')
それぞれ,生存(0/1),乗客クラス(1〜3),性別(male/female),年齢,同乗の兄弟姉妹配偶者の数,同乗の親子供の数,料金,乗船港(C = Cherbourg, Q = Queenstown, S = Southampton),乗客クラス(First/Second/Third),男女子供(man/woman/child),成人男性(True/False),デッキ(A〜G),乗船港(Cherbourg/Queenstown/Southampton),生存(yes/no),一人旅(True/False)である。内訳を見るには次のようにする:
titanic['survived'].value_counts()
0 549 1 342 Name: survived, dtype: int64
titanic['deck'].value_counts()
C 59 B 47 D 33 E 32 A 15 F 13 G 4 Name: deck, dtype: int64
足しても891人になりそうにない。欠測値(NA = Not Available)があるのではないか。欠測値を落とさないオプション dropna=False
を付けてみよう:
titanic['deck'].value_counts(dropna=False)
NaN 688 C 59 B 47 D 33 E 32 A 15 F 13 G 4 Name: deck, dtype: int64
NaN
(Not a Number)は欠測値である。
年齢が 1 以上で xx.5 のような小数になっているものは推定年齢が xx であることを意味する。
いくつかの項目は冗長である。後述のように,alone は sibsp + parch が 0 に等しいことを意味する。child は16歳未満(15歳以下)を意味する。成人男性と who == 'man' は同義である。
Kaggle のデータでは他に Name(氏名),Ticket(チケット番号),Cabin(客室番号)が含まれている。seaborn の deck は Kaggle の Cabin の頭1文字に相当する。例えば2番目の乗客は客室番号 C85 であるのでデッキは C である。
項目ごとの欠測値の数は次の通りである:
titanic.isna().sum()
survived 0 pclass 0 sex 0 age 177 sibsp 0 parch 0 fare 0 embarked 2 class 0 who 0 adult_male 0 deck 688 embark_town 2 alive 0 alone 0 dtype: int64
age には欠測値が多いが who にはない。Kaggle のデータに who に相当する項目がないので最初不思議に思ったが,どうやら単純に age < 16 を child としているだけで,欠測は自動的に大人にされてしまっているようだ:
((titanic['who'] == 'child') == (titanic['age'] < 16)).all()
True
adult_male も機械的に導いているようだ:
((titanic['who'] == 'man') == titanic['adult_male']).all()
True
alive が yes であることと survived が True であることは等価である:
((titanic['alive'] == 'yes') == titanic['survived']).all()
True
sibsp + parch が 0 であることと alone が True であることも等価である:
((titanic['sibsp'] + titanic['parch'] == 0) == titanic['alone']).all()
True
(特にする必要はないが)独立な変数だけに切り詰めるには次のようにすればよい:
titanic = titanic[['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked', 'deck']]
次のようにすれば数値データの項目について要約統計量が得られる:
titanic.describe()
survived pclass age sibsp parch fare count 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000 mean 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208 std 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429 min 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000 25% 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400 50% 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200 75% 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000 max 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200
いろいろ図を描いてみよう:
sns.boxplot(x='sex', y='age', data=titanic)
sns.boxplot(x='sex', y='age', hue='survived', data=titanic)
箱ひげ図ではあまりよくわからない。swarmplot を描いてみよう。swarm(スウォーム)は(ミツバチなどの)群れを意味する英語である。
sns.swarmplot(x='sex', y='age', hue='survived', data=titanic)
女性と子供が多く生存していることがわかる。
男性だけについて,乗客クラスと料金と生存の関係を調べよう:
sns.swarmplot(x='pclass', y='fare', hue='survived', data=titanic[titanic['sex'] == 'male'])
どちらかといえば first class が多く生存したようだ。
UserWarning: XX.X% of the points cannot be placed; you may want to decrease the size of the markers or use stripplot. のような警告が出るので,デフォルトのマーカーの大きさ size=5
を減らすか,sns.stripplot()
を使うか,その両方を行う。
titanic[titanic['sex'] == 'male']
は titanic.query("sex == 'male'")
でもよい(How to select rows from a DataFrame based on column values)。
このあとは,なんちゃって機械学習(本当はちゃんと勉強してからやろう)。
まずは年齢の欠測値を適当に埋める。ここでは中央値とした。
titanic['age'] = titanic['age'].fillna(titanic['age'].median())
女/男は 0/1 でエンコードする:
titanic['sex'] = titanic['sex'].map({'female': 0, 'male': 1})
あとは,以下では使わないが,他の欠測値も例えば次のようにして埋められる:
import pandas as pd titanic['embarked'] = titanic['embarked'].fillna('N') titanic['deck'] = pd.Categorical(titanic['deck'], categories=['A','B','C','D','E','F','G','N'] ).fillna('N')
とりあえず旅客クラス・性・年齢で機械学習してみる:
from sklearn.model_selection import train_test_split X = titanic[['pclass', 'sex', 'age']].values y = titanic['survived'].values X_train, X_test, y_train, y_test = train_test_split(X, y)
それぞれの度数分布は import numpy as np
して例えば np.unique(y_test, return_counts=True)
のようにすれば求められる。
決定木の場合:
from sklearn.tree import DecisionTreeClassifier model = DecisionTreeClassifier(max_depth=3)
ロジスティック回帰の場合:
from sklearn.linear_model import LogisticRegression model = LogisticRegression()
ニューラルネットの場合:
from sklearn.neural_network import MLPClassifier model = MLPClassifier()
フィットして性能を調べる:
model.fit(X_train, y_train) y_pred = model.predict(X_test)
正解率:
from sklearn.metrics import accuracy_score, confusion_matrix accuracy_score(y_test, y_pred)
混同行列:
confusion_matrix(y_test, y_pred)
結果は(数値を文字で置き換えて)次のような感じになる:
array([[a, b], [c, d]])
y_test
が 0(実際に死亡)が a + b,y_test
が 1(実際に生存)が c + d になる。また,y_pred
が 0(予測は死亡)が a + c,y_pred
が 1(予測が生存)が b + d になる。正解率 accuracy score は (a + d) / (a + b + c + d) である。
病気の診断の場合にも混同行列がよく使われる。例えば
陰性(病気なしと判断) | 陽性(病気ありと判断) | |
---|---|---|
実際に病気なし | 真陰性 | 偽陽性 |
実際に病気あり | 偽陰性 | 真陽性 |
真陽性 / (偽陰性 + 真陽性) を感度,真陰性 / (真陰性 + 偽陽性) を特異度という。例えば
陰性(病気なしと判断) | 陽性(病気ありと判断) | |
---|---|---|
実際に病気なし | 99 | 1 |
実際に病気あり | 3 | 7 |
の場合に感度・特異度を計算してみよう。
参考:
Last modified: