[deprecated] kaggleのタイタニックとたわむれる
【2021-06-15 更新】本記事の内容は時代遅れです。 ちょっと立て込んでいて修正できてませんが、 『Kappleで勝つデータ分析の技術』を難しいと感じる層向けに アップデートしようと考えています(非常に良い本です)。 まぁ、初学者の頃はこうしていた、というログできな意味で 残しておくのは価値があるかもしれないので、 その際は新規リンクを貼ります。
【2019-12-14 追記】もう少し内容を整理して書き直しますのでしばしお待ちを…
kaggleデビューしました
1月30日に初心者向けのkaggleチュートリアル があったので参加してきました。 サポーターズさんの勉強会にはしばしばでているのですが、 今回のチュートリアルは完成度が高くていろいろと参考になりました。
特に勉強になったのは以下の点です。
- データを分割して訓練データとテストデータにすると提出前にスコアが推測できる。 (授業で確かに聞いた覚えはあったのですが、実践できていませんでした。)
- 安直な前処理よくない。というのも、 前処理だけの本があるくらい奥深い世界みたいです。なんでも平均値で埋める、というのはよろしくないようです。
- 面白いのは分析(このデータは使えそう、みたいなやつ)
- kernelがチュートリアルには便利
というか資料の完成度も高いので今後自分がチュートリアルする時の参考にもしたいです。
タイタニック号の分析
べたべたにチュートリアルを賞賛して終わるのもなんなので、 いちおう目標の0.765も超えたので「解き方の例」みたいなのを残しておきます。
まずはデータを読み込みます。 それぞれの列のデータの説明は以下の通りです。 Training dataとTest dataがあって、 Testデータのほうには”Survived”がありません。
- PassengerId
- Survived: Survived (1) or died (0)
- Pclass: Passenger’s class
- Name: Passenger’s name
- Sex: Passenger’s sex
- Age: Passenger’s age
- SibSp: Number of siblings/spouses aboard
- Parch: Number of parents/children aboard
- Ticket: Ticket number
- Fare: Fare
- Cabin: Cabin
- Embarked: Port of embarkation
予測したい変数は “Survived” なので、 まずはデータを整理、可視化して役立ちそうな変数を見つけ、 次にTraining dataでモデルを立てて、 最後にTest dataのPassengerIdごとにSurvivedを求める、 という流れになります。
データの取得と整理、統計量の確認
まずはトレーニングデータとテストデータを読み込ます。 同時に欠損値があるかどうかも今のうちに見ておきます。 テストデータに欠損値があると 予測の時にNAになって成績を大きく下げるので対処せねばなりません。
#setwd("C:/Users/kishiyama/Document/formula-exercise")
train <- read_csv("../data/train.csv")
test <- read_csv("../data/test.csv")
sapply(train, anyNA)
## PassengerId Survived Pclass Name Sex Age
## FALSE FALSE FALSE FALSE FALSE TRUE
## SibSp Parch Ticket Fare Cabin Embarked
## FALSE FALSE FALSE FALSE TRUE TRUE
sapply(test, anyNA)
## PassengerId Pclass Name Sex Age SibSp
## FALSE FALSE FALSE FALSE TRUE FALSE
## Parch Ticket Fare Cabin Embarked
## FALSE FALSE TRUE TRUE FALSE
AgeとFare、その他の変数に欠損値がありそうです。 年齢と運賃はおそらく使うので平均と置き換えて対処します。 チュートリアルを受けるまえのコードなのですが、 いまだったら中央値とかをつかうかもしれません。 前処理も勉強が必要ですね。
train$Fare =ifelse(is.na(train$Fare),mean(na.omit(train$Fare)), train$Fare)
test$Fare = ifelse(is.na(test$Fare), mean(na.omit(test$Fare)), test$Fare)
train$Age = ifelse(is.na(train$Age), mean(na.omit(train$Age)), train$Age)
test$Age = ifelse(is.na(test$Age), mean(na.omit(test$Age)), test$Age)
この操作ですでに元のデータとは 違うものになっていますがまぁ妥協してよしとします。 この状態での統計量は以下の通りです。
train %>% summary
## PassengerId Survived Pclass Name
## Min. : 1.0 Min. :0.0000 Min. :1.000 Length:891
## 1st Qu.:223.5 1st Qu.:0.0000 1st Qu.:2.000 Class :character
## Median :446.0 Median :0.0000 Median :3.000 Mode :character
## Mean :446.0 Mean :0.3838 Mean :2.309
## 3rd Qu.:668.5 3rd Qu.:1.0000 3rd Qu.:3.000
## Max. :891.0 Max. :1.0000 Max. :3.000
## Sex Age SibSp Parch
## Length:891 Min. : 0.42 Min. :0.000 Min. :0.0000
## Class :character 1st Qu.:22.00 1st Qu.:0.000 1st Qu.:0.0000
## Mode :character Median :29.70 Median :0.000 Median :0.0000
## Mean :29.70 Mean :0.523 Mean :0.3816
## 3rd Qu.:35.00 3rd Qu.:1.000 3rd Qu.:0.0000
## Max. :80.00 Max. :8.000 Max. :6.0000
## Ticket Fare Cabin Embarked
## Length:891 Min. : 0.00 Length:891 Length:891
## Class :character 1st Qu.: 7.91 Class :character Class :character
## Mode :character Median : 14.45 Mode :character Mode :character
## Mean : 32.20
## 3rd Qu.: 31.00
## Max. :512.33
生存率はSurvivedの平均から38%くらいだとわかります。 年齢は月までの情報はいらないので四捨五入します。 名前も今回は使わないので落として、 生死の情報は実数でないので、ファクターとします。
train %>%
mutate(Age = round(Age),
Survived = as.factor(Survived),
Sex = as.factor(Sex),
PassengerId = NULL,
Ticket = NULL,
Name = NULL) %>%
na.omit ->
train.mutated
summary(train.mutated)
## Survived Pclass Sex Age SibSp
## 0: 68 Min. :1.000 female: 95 Min. : 1.00 Min. :0.0000
## 1:134 1st Qu.:1.000 male :107 1st Qu.:25.00 1st Qu.:0.0000
## Median :1.000 Median :33.50 Median :0.0000
## Mean :1.198 Mean :35.14 Mean :0.4455
## 3rd Qu.:1.000 3rd Qu.:46.75 3rd Qu.:1.0000
## Max. :3.000 Max. :80.00 Max. :3.0000
## Parch Fare Cabin Embarked
## Min. :0.0000 Min. : 0.00 Length:202 Length:202
## 1st Qu.:0.0000 1st Qu.: 28.96 Class :character Class :character
## Median :0.0000 Median : 55.00 Mode :character Mode :character
## Mean :0.4406 Mean : 76.10
## 3rd Qu.:1.0000 3rd Qu.: 89.78
## Max. :4.0000 Max. :512.33
データの整理はこれくらいにして、 何が生死を分けたのかを観察するためにデータを可視化します。
データの可視化
今回は変数が多いのでまずは GGally
パッケージで一度にそれぞれの変数間の関わり合いを見てみます。
library(GGally)
train.mutated %>%
ggpairs(mapping=aes(colour=Survived, alpha=0.5),
columns=c("Survived", "Pclass", "Sex", "Fare", "Embarked", "Age"),
upper=list(continuous="cor", combo="box", discrete="facetbar"),
diag=list(continuous="densityDiag", discrete="barDiag"),
lower=list(continuous="points", combo="facethist", discrete="facetbar"))
全体の傾向をつかむ分には割と使えそうです。 一番上の行の右を見ると”Survived”と書いてあります。 一行目の上の行は死亡(0,赤)、下の行は生存データ(1,青)が Pclass, Sex, Fare, Embarked, Ageの値でどう変わるか、を示しています。
そんなにがっつりとは分析しませんが、 気づいたことをまとめると以下の通り。
- 亡くなったのはだいたい男性。Sex列は右が男性、左が女性。
- どんな男性の生存率が下がるのか、が見どころかもしれません。 家族がいる男性でしょうか、お金のある、あるいは年齢の高い男性でしょうか。
- Pclass, Fareはわからない。
- 交互作用をにらむ
- Ageも死亡率が高い。
まぁ上のデータを見る限り女性だと大体助かりそうなので、 データを男性に絞って分析を続けます。
どんな男性がなくなったのかの分析
ここではtrain.mutated[with(train.mutated, Sex %in% c("male")), ]
として データを男性に絞ります。別にfilter
でもsubset
でもいいのですが。 すると以下のようなグラフになります。
library(GGally)
ggpairs(train.mutated[with(train.mutated, Sex %in% c("male")), ], mapping=aes(colour=Survived, alpha=0.5), columns=c("Survived", "Pclass", "Age", "SibSp", "Parch", "Fare", "Embarked"), upper=list(continuous="cor", combo="box", discrete="facetbar"), diag=list(continuous="densityDiag", discrete="barDiag"), lower=list(continuous="points", combo="facethist", discrete="facetbar"))
ちょっと興味深い点としてはSurvived列のAge行、 死亡は赤なのですが20未満で亡くなった男性は少ないように見えます。 また、Fareの行を見ると500あたりの人は生存しているように見えます。 ここから20歳未満か否か、という点と高額な運賃を払ったか、 という情報が性別と交互作用を持つと仮定しました。 (つまり、男性は死ぬ割合のほうが高いけど、若い/高い運賃は 生存率を上げる効果を持つ、という仮定です。) そこで新しい変数を作ってあげました。
train.mutated %>%
mutate(is.child=Age<20,
is.rich=Fare>400,
Age=NULL,
Fare=NULL,
SibSp=NULL,
Parch=NULL) ->
train.data
つかいそうなパラメターに絞って再度プロットします。
library(GGally)
ggpairs(train.data, mapping=aes(colour=Survived, alpha=0.5), columns=c("Survived", "Sex", "is.child", "is.rich"), upper=list(continuous="cor", combo="box", discrete="facetbar"), diag=list(continuous="densityDiag", discrete="barDiag"), lower=list(continuous="points", combo="facethist", discrete="facetbar"))
モデル作成
従属変数は0,1なので今回はロジスティック回帰します。 性別と年齢をモデルに入れて、あとは 性別と年齢、性別と運賃の交互作用を見ています。
set.seed(123)
model <- glm(Survived ~ Sex + is.child + is.child:Sex + is.rich:Sex,
family=binomial,
data =train.data)
summary(model)
##
## Call:
## glm(formula = Survived ~ Sex + is.child + is.child:Sex + is.rich:Sex,
## family = binomial, data = train.data)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.4374 -0.9718 0.3245 0.5003 1.3980
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 2.9178 0.5133 5.684 1.32e-08 ***
## Sexmale -3.4229 0.5562 -6.154 7.54e-10 ***
## is.childTRUE -0.9029 0.9111 -0.991 0.3217
## Sexmale:is.childTRUE 2.1011 1.1185 1.879 0.0603 .
## Sexfemale:is.richTRUE NA NA NA NA
## Sexmale:is.richTRUE 16.0712 1029.1215 0.016 0.9875
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 258.07 on 201 degrees of freedom
## Residual deviance: 182.32 on 197 degrees of freedom
## AIC: 192.32
##
## Number of Fisher Scoring iterations: 14
男性であると生存率は有意に落ちて、 子供であれば男性でも助かる可能性が上がる傾向があるようです。
モデルを用いた予測
テストデータにも同じ処理をしてあげます。
# survived
test %>%
mutate(Age = round(Age),
Sex = as.factor(Sex),
Ticket = NULL,
Name = NULL) %>%
mutate(is.child=Age<20,
is.rich=Fare>400,
Age=NULL,
Fare=NULL,
SibSp=NULL,
Parch=NULL) ->
test.mutate
そしてpredict関数にmodelとデータを与えます。 model
で指定した列を使ってデータを生成してくれます。 その結果をSurvivedという列に入れてデータフレームを作り、 閾値を0.5として0,1に変換します。 保存したcsvをkaggleに渡すと成績が出てきます。
prediction <- predict(model, test.mutate)
solution <-
data.frame(PassengerID = test$PassengerId, Survived = prediction) %>%
mutate(Survived=ifelse(Survived>0.5,1,0))
write.csv(solution, file = '../data/lr_solution.csv', row.names = F)
ドキドキ、という感じだったのですが精度は0.73で順位は9262位とまぁ、 僕の初kaggleは無事に爆死しました。
タイタニック号の分析2
とこのままでは悲しいので再チャレンジしました。
子供かどうか、運賃の情報などをわざわざ二値にしましたが、 あれは愚策化もしれません。というのもわざわざ使える情報を減らしているので…。 というわけで、今回は情報を削らずに試してみます。 さらに今回はほかの情報(Pclass, SibSp, Parch)も加えてモデルを作りました。
set.seed(123)
model.2 <- glm(Survived ~ Sex + Age + Fare + Pclass + SibSp + Parch +
Sex:Age + Sex:Fare,
family=binomial,
data =train)
summary(model.2)
##
## Call:
## glm(formula = Survived ~ Sex + Age + Fare + Pclass + SibSp +
## Parch + Sex:Age + Sex:Fare, family = binomial, data = train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.6485 -0.6128 -0.4178 0.6054 2.5105
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 4.155064 0.580493 7.158 8.20e-13 ***
## Sexmale -1.324626 0.458656 -2.888 0.00388 **
## Age -0.012600 0.011955 -1.054 0.29188
## Fare 0.007053 0.006108 1.155 0.24818
## Pclass -1.096441 0.143573 -7.637 2.23e-14 ***
## SibSp -0.334915 0.106961 -3.131 0.00174 **
## Parch -0.145330 0.117990 -1.232 0.21805
## Sexmale:Age -0.046595 0.015231 -3.059 0.00222 **
## Sexmale:Fare -0.005184 0.006130 -0.846 0.39773
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 1186.7 on 890 degrees of freedom
## Residual deviance: 776.8 on 882 degrees of freedom
## AIC: 794.8
##
## Number of Fisher Scoring iterations: 6
そして 再度テストデータに当てはめて予測させます。
test %>%
mutate(Age = round(Age),
Sex = as.factor(Sex),
Ticket = NULL,
Name = NULL) ->
test.mutate
prediction.2 <- predict(model.2, test.mutate)
solution.2 <-
data.frame(PassengerID = test$PassengerId, Survived = prediction.2) %>%
mutate(Survived=ifelse(Survived>0.5,1,0))
write.csv(solution.2, file = '../data/lr_solution.2.2.csv', row.names = F)
精度0.77で6303位に。若干改善されました。 目標は超えたのですが、もうちょっと頑張ります。
タイタニック号の分析3
もうほかの性別とほかの要因の交互作用も見てしまいます。
# survived
test %>%
mutate(Age = round(Age),
Sex = as.factor(Sex),
Ticket = NULL,
Name = NULL) ->
test.mutate
model.3 <- glm(Survived ~ Sex + Age + Fare + Pclass + SibSp + Parch +
Sex:Age + Sex:Fare + Sex:Pclass + Sex:Parch,
family=binomial,
data =train)
summary(model.3)
##
## Call:
## glm(formula = Survived ~ Sex + Age + Fare + Pclass + SibSp +
## Parch + Sex:Age + Sex:Fare + Sex:Pclass + Sex:Parch, family = binomial,
## data = train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.9068 -0.6186 -0.4613 0.4167 2.3752
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 7.849772 1.257028 6.245 4.25e-10 ***
## Sexmale -6.117563 1.376846 -4.443 8.86e-06 ***
## Age -0.025610 0.013754 -1.862 0.062597 .
## Fare -0.004721 0.005342 -0.884 0.376852
## Pclass -2.223577 0.378214 -5.879 4.12e-09 ***
## SibSp -0.339271 0.111152 -3.052 0.002271 **
## Parch -0.246737 0.151342 -1.630 0.103031
## Sexmale:Age -0.020016 0.016875 -1.186 0.235571
## Sexmale:Fare 0.006730 0.005880 1.145 0.252350
## Sexmale:Pclass 1.405349 0.409912 3.428 0.000607 ***
## Sexmale:Parch 0.566262 0.238164 2.378 0.017425 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 1186.66 on 890 degrees of freedom
## Residual deviance: 756.12 on 880 degrees of freedom
## AIC: 778.12
##
## Number of Fisher Scoring iterations: 6
prediction.3 <- predict(model.3, test.mutate)
solution.3 <-
data.frame(PassengerID = test$PassengerId, Survived = prediction.3) %>%
mutate(Survived=ifelse(Survived>0.5,1,0))
write.csv(solution.3, file = '../data/lr_solution.3.csv', row.names = F)
モデル3は精度0.78で5694位でした。うーん、という感じです。
以下、学んだことです。
- なんでも回帰、はちょっとよろしくないのかも。
- ランダムフォレストとか、分類に強い手法も知っておくとよいかも。
- データをつぶすと解釈は楽になるけど予測は悪くなる(当たり前)
- グラフと分析が同じである必要はないのでよしなに。
- Kaggleを取り掛かるまでにやったこととと、モチベーションの維持のために必要だったことというエントリーでの「挑み方」が大切だと感じた。このフローチャートを胸に刻みたい。
- 高い精度でかつ理解しやすいカーネルを覗きに行く。
- いい勉強になるけど自分の研究やったほうがコスパは良さそうなのでほどほどに
- 前処理も勉強が必要
- いろいろなモデルを試す
- モデルだけじゃなく、手法(ランダムフォレストとか)も変えてみる
適度に沼にはまれればな、と思いました。 NLPのチュートリアルをするのでkaggleのkernelでやろうかな、とか思ってます。 たとえばSentiment Analysis on Movie Reviewsとか、 適度に業務とも関係がありそうで楽しそうだと思いました。 (しかもNLPのチュートリアルのネタになる)