kaggle再一次入門~經典入門級競賽~Titanic

Rachel~Liu發表於2020-12-19

題外話:想著更早的接觸到資料競賽,就可以更早的學到理論,練到技術,拿到名次,賺得獎金~功利心太重了,屢屢開始入門比賽,屢屢不了了之。加入俱樂部,發現大家本科就知道這些,已經參加了各種資料科學競賽,並且有了不小的學習成就和心得體會,當然還有白花花的銀子,然而,我沒有在第一時間找到小白同學共同學習,沒有抱緊大佬大腿努力學習,而是打算自己慢慢摸爬滾打入門,再找大家交流。結果自己沒有摸出門道,提前放棄,一起的小夥伴也在通往大佬的路上越走越遠,我還停在原地。再後來,瞭解到DataWhale這個神奇的開源組織,從街景字元識別開始,研究水哥、魚佬給出的baseline,還是我不夠努力,對於我而言,能弄懂baseline就挺費勁的了,在這個基礎上,增加特徵,我無從下手,也沒有請教別人。歸根結底,都是我懶惰,加急功近利的性子導致的,太害怕失敗,害怕困難,而選擇就此止步,可我又不甘如此,經常做著自我鬥爭。年輕人終歸是要奮鬥的,要拿出年輕人的朝氣,在此替過去的自己對現在的自己說聲抱歉。

在有了一定基礎之後,再次看泰坦尼克號這個題目,覺得好簡單。然而,還是有很多工作需要做,從資料分析,到如何建立一個有效的模型,再到調參分析等,都是新手需要鑽研的。以下是處理資料分析比賽的一個基本思路:

1 分析賽題

直譯:泰坦尼克號的沉沒是歷史上最臭名昭著的沉船事件之一。1912年4月15日,被廣泛認為是永不沉沒的皇家郵輪泰坦尼克號在處女航中與冰山相撞後沉沒。不幸的是,船上沒有足夠的救生艇運載所有乘客,導致2224名乘客和船員中有1502人死亡。雖然生存有一些運氣的因素,但似乎某些群體的人比其他人更有可能存活下來。在這次挑戰中,我們要求你建立一個預測模型來回答以下問題:什麼樣的人更有可能存活?使用乘客資料(如姓名、年齡、性別、社會經濟等級等)。

首先分析一下賽題,賽題定位是一個二分類問題,任務是通過train.csv(已經標註label的乘客資訊)來預測test.csv(未標註label的乘客資訊)中的label,其中label就是乘客是否存活,存活標記為1,未存活標記為0。然後我們需要將預測出來的結果存入submit.csv中,包括乘客idlabel即可。

Goal
It is your job to predict if a passenger survived the sinking of the Titanic or not.
For each in the test set, you must predict a 0 or 1 value for the variable.

Metric
Your score is the percentage of passengers you correctly predict. This is known as accuracy.
A c c u r a c y = ( T P + T N ) / ( T P + T N + F P + F N ) w h e r e : T P = T r u e p o s i t i v e ; F P = F a l s e p o s i t i v e ; T N = T r u e n e g a t i v e ; F N = F a l s e n e g a t i v e Accuracy = (TP + TN)/(TP + TN + FP + FN)\\ where: TP = True positive; FP = False positive; TN = True negative; FN = False negative Accuracy=(TP+TN)/(TP+TN+FP+FN)where:TP=Truepositive;FP=Falsepositive;TN=Truenegative;FN=Falsenegative

Submission File Format
You should submit a csv file with exactly 418 entries plus a header row. Your submission will show an error if you have extra columns (beyondPassengerId and Survived ) or rows.

The file should have exactly 2 columns:

  • PassengerId (sorted in any order)
  • Survived (contains your binary predictions: 1 for survived, 0 for deceased)

2 分析資料:

資料集欄位解釋:

pclass: A proxy for socio-economic status (SES)社會經濟地位的代表
1st = Upper
2nd = Middle
3rd = Lower

age: Age is fractional if less than 1. If the age is estimated, is it in the form of xx.5

sibsp: The dataset defines family relations in this way...
Sibling = brother, sister, stepbrother, stepsister
Spouse = husband, wife (mistresses and fiancés were ignored)

parch: The dataset defines family relations in this way...
Parent = mother, father
Child = daughter, son, stepdaughter, stepson
Some children travelled only with a nanny(保姆), therefore parch=0 for them.

在這裡插入圖片描述

2.1 資料集統計分析:

kaggle中,賽題的資料都在../input目錄下存放著,我們可以通過os.walk(dir_path)的方式遍歷到目錄dir_path中的所有檔名,然後將根目錄和檔名拼接起來就是資料檔案的絕對路徑了。

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))
# /kaggle/input/titanic/train.csv
# /kaggle/input/titanic/test.csv
# /kaggle/input/titanic/gender_submission.csv

知道資料集地址之後,使用pandas讀取資料集,pandas進行資料分析的一些常用方法可以自己簡單瞭解一下。pandas讀取的檔案結果是DataFrame型別,即二維表格型別;head()返回的是前幾條資料,tail()返回的後幾行資料,括號中可以填寫你想檢視的行數,預設是5。對於較大的資料集,我一般還會列印train_data.shape,train_data.columns檢視資料大小和列名,方便分析每一列的屬性。還可以通過train_data.describe()表示對資料集的統計性分析,詳細介紹看這裡.value_counts()表示對每一個屬性不同取值的計數;.count()表示每一個屬性的非零取值的計數,即value_counts()的和

train_data = pd.read_csv("/kaggle/input/titanic/train.csv")
print(train_data.shape,train_data.columns)
"""
((891, 12), Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype='object'))
"""
train_data.head() # 圖1
train_data.describe() # 圖2,count表示非空值個數,後面的意思分別是均值、方差、最大最小值、百分位數

head()
describe()
檢視每一列的取值範圍,train_data['Pclass'].unique()可以得到array([3, 1, 2]),即乘客的階級為三個等級。用.value_counts()可以得到每一個取值的樣本數,通過檢視len(train_data[i].unique())得到不同屬性的取值個數。同理,依次對每一個屬性進行分析:

for i in train_data.columns:
    print(i,len(train_data[i].unique()))
PassengerId 891 唯一性
Survived 2		{1,0}表示survive/die
Pclass 3		{1 = 1st, 2 = 2nd, 3 = 3rd}表示社會階層
Name 891		唯一性
Sex 2			{male,female}表示男/女
Age 89			{0.0,...,80}表示年齡
SibSp 7			兄弟姐妹/配偶
Parch 7			父母/孩子
Ticket 681		船票號
Fare 248		票價
Cabin 148		客艙號(房間號)
Embarked 4		{C = Cherbourg, Q = Queenstown, S = Southampton}表示出發港(上船地點)
for i in train_data.columns:
    print(train_data[i].value_counts(),'\n')
Name: PassengerId, Length: 891, dtype: int64

0    549
1    342
Name: Survived, dtype: int64

3    491
1    216
2    184
Name: Pclass, dtype: int64

Name: Name, Length: 891, dtype: int64

male      577
female    314
Name: Sex, dtype: int64

24.00    30
22.00    27
18.00    26
19.00    25
30.00    25
         ..
55.50     1
70.50     1
66.00     1
23.50     1
0.42      1
Name: Age, Length: 88, dtype: int64

0    608
1    209
2     28
4     18
3     16
8      7
5      5
Name: SibSp, dtype: int64

0    678
1    118
2     80
5      5
3      5
4      4
6      1
Name: Parch, dtype: int64

CA. 2343    7
347082      7
1601        7
3101295     6
CA 2144     6
           ..
3101264     1
226875      1
330980      1
315096      1
28664       1
Name: Ticket, Length: 681, dtype: int64

8.0500     43
13.0000    42
7.8958     38
7.7500     34
26.0000    31
           ..
8.4583      1
9.8375      1
8.3625      1
14.1083     1
17.4000     1
Name: Fare, Length: 248, dtype: int64

G6             4
B96 B98        4
C23 C25 C27    4
E101           3
C22 C26        3
              ..
D28            1
D19            1
C111           1
C118           1
D6             1
Name: Cabin, Length: 147, dtype: int64

S    644
C    168
Q     77
Name: Embarked, dtype: int64

可以看到,以上欄位中,PassengerIdName都具有唯一性,Survived取值{0,1},其餘的欄位意思請看上面程式碼輸出中標註。

2.2 對於缺失值的處理:

檢視是否有缺失值,對於缺失值的處理,其一,是用某種值去填充;其二,若該列不太重要,可以直接刪除該列。在describe()的描述中,可以看到年齡是714個非空值,有部分缺失;其餘字元型別的欄位沒有顯示,此處再次檢視一下他們的缺失值:

for i in train_data.columns:
    print(i,'\n',train_data[i].count())
# Age 714; Cabin 204; Embarked 889   

可以看到船艙號Cabin缺失佔比較大,年齡Age和出發港口Embarked都有部分缺失;其中我認為船艙號和社會階級Pclass是分不開的,且由於cabin取值類別太多,對缺失值不容易填充,因此直接刪除該屬性,暫時不做考慮。

其次,乘客ID和票號我覺得不具有參考價值,因此選擇刪除列;name中包含的資訊較多,包括Mr/Mrs/master等會蘊含一些資訊,但是性別和票價並無缺失,應該可以代替這個屬性,因此也刪除該列。

目前,剩餘有缺失的屬性只有年齡和港口,若缺失值在總資料集佔比不大的話,認為是壞樣本,可以直接將有缺失值的行刪除。age有將近20%的缺失值,embarked有兩個缺失值。暫時先不處理,下面參考的博文直接刪掉著20%,我覺得著資料量挺大的,怎麼能瞎刪呢?

2.3 測試集類比分析

同樣步驟對測試集進行分析,資料集為(418,11),年齡的非缺失值有332條,同樣缺失率為20%左右;fare有1條缺失值;缺失率最大的仍然是cabin僅有91條,缺失率在78%左右。測試集是不可以刪除行的,因此對fare和age進行填充,將cabin這一列去掉。如何填充需要對資料進行統計分析,如下是我將不同屬性的取值較多的值貼上的結果。同樣是參考部落格說fare的眾數是7.75,因此直接用該值填充,我覺得眾數並不明顯,以下四個取值數差不多

#處理一下測試集裡的缺失值,測試集的缺失資料不能刪
#處理Fare,先看一下分佈,發現明顯有個眾數非常突出,且訓練集和測試集眾數接近:
test_data['Fare']=test_data['Fare'].fillna(test_data['Fare'].dropna().mode()[0])
3    218
1    107
2     93
Name: Pclass, dtype: int64

male      266
female    152
Name: Sex, dtype: int64

24.0    17
21.0    17
22.0    16
30.0    15
18.0    13
Name: Age, Length: 79, dtype: int64

0    283
1    110
2     14
4      4
3      4
Name: SibSp, dtype: int64

0    324
1     52
2     33
Name: Parch, dtype: int64

7.7500     21
26.0000    19
8.0500     17
13.0000    17
Name: Fare, Length: 169, dtype: int64

S    270
C    102
Q     46
Name: Embarked, dtype: int64

2.3 資料模式探索/猜測

根據上面資料的統計結果,訓練集中存活率為38%,男女比例為:577:314
根據情境,當時看電影,女人和小孩先上船,由於年齡Age資訊缺失較大,我們先假設所有女性female都存活了,為了驗證我們的假設是否合理,因此,下面計算女性存活的比例:

women = train_data.loc[train_data.Sex == 'female']["Survived"] # 獲得性別為“female”倖存者資料
rate_women = sum(women)/len(women) # 計算女性倖存者比率

print("ratio of women who survived:", rate_women) # 0.7420382165605095,同理,男性存活率0.1889081456

這就為我們提供了一個思路,在分析資料的時候,我們可以按照類別分析不同屬性值所佔的比例,檢視樣本分佈是否均勻,分析出來對類別影響較大的特徵。

train_data = train_data.drop(columns=['PassengerId','Cabin','Ticket','Name'])
for column in train_data.columns:
    print(train_data.groupby('Survived')[column].value_counts()) # Series型別,[0]為died,[1]為survived
Survived  Survived
0         0           549
1         1           342
Name: Survived, dtype: int64
Survived  Pclass
0         3         372
          2          97
          1          80
1         1         136
          3         119
          2          87
Name: Pclass, dtype: int64
Survived  Sex   
0         male      468
          female     81
1         female    233
          male      109
Name: Sex, dtype: int64
Survived  Age 
0         21.0    19
          28.0    18
          18.0    17
          25.0    17
          19.0    16
                  ..
1         43.0     1
          47.0     1
          53.0     1
          55.0     1
          80.0     1
Name: Age, Length: 142, dtype: int64
Survived  SibSp
0         0        398
          1         97
          2         15
          4         15
          3         12
          8          7
          5          5
1         0        210
          1        112
          2         13
          3          4
          4          3
Name: SibSp, dtype: int64
Survived  Parch
0         0        445
          1         53
          2         40
          4          4
          5          4
          3          2
          6          1
1         0        233
          1         65
          2         40
          3          3
          5          1
Name: Parch, dtype: int64
Survived  Fare    
0         8.0500      38
          7.8958      37
          13.0000     26
          7.7500      22
          26.0000     16
                      ..
1         82.1708      1
          83.4750      1
          106.4250     1
          108.9000     1
          247.5208     1
Name: Fare, Length: 330, dtype: int64
Survived  Embarked
0         S           427
          C            75
          Q            47
1         S           217
          C            93
          Q            30
Name: Embarked, dtype: int64

分析每一個欄位:

  • 階級: 1 s t : 2 n d : 3 r d = 216 : 184 : 491 ≈ 2 : 2 : 5 1st:2nd:3rd=216:184:491\approx 2:2:5 1st:2nd:3rd=216:184:4912:2:5,死亡階級人數比例: 1 s t : 2 n d : 3 r d = 80 : 97 : 372 ≈ 1 : 1 : 4 1st:2nd:3rd=80:97:372\approx 1:1:4 1st:2nd:3rd=80:97:3721:1:4,生存階級人數比例: 1 s t : 2 n d : 3 r d = 136 : 87 : 119 ≈ 1.5 : 1 : 1.3 1st:2nd:3rd=136:87:119\approx 1.5:1:1.3 1st:2nd:3rd=136:87:1191.5:1:1.3,每一個階級的生還比例: 3 r d = 24.24 % ; 2 n d = 47.28 % ; 1 s t = 62.96 % 3rd=24.24\%;2nd=47.28\%;1st=62.96\% 3rd=24.24%;2nd=47.28%;1st=62.96%,看來窮人生還比例最低。那是否窮人裡面女性和兒童人數少呢?
  • 年齡:生還的人年齡分佈
import matplotlib.pyplot as plt
age_survived = train_data.groupby('Survived')['Age'].value_counts()[1]
plt.bar(age_survived.index,age_survived)

在這裡插入圖片描述

  • 兄弟姐妹數:可以看出來,少的生還率高些,人數太多的生還率低
sib_sur_cnt = train_data.groupby('Survived')['SibSp'].value_counts()[1]
sib_all_cnt = train_data['SibSp'].value_counts()
for i in sib_all_cnt.index:
    if i == 5 or i == 8: 
        sib_sur_cnt[i]=0
    print('sibling number:',i,' survived rate:',sib_sur_cnt[i]/sib_all_cnt[i])
sibling number: 0  survived rate: 0.34539473684210525
sibling number: 1  survived rate: 0.5358851674641149
sibling number: 2  survived rate: 0.4642857142857143
sibling number: 4  survived rate: 0.16666666666666666
sibling number: 3  survived rate: 0.25
sibling number: 8  survived rate: 0.0
sibling number: 5  survived rate: 0.0
  • 父母孩子:人數太多的生還率低
pc_sur_cnt = train_data.groupby('Survived')['Parch'].value_counts()[1]
pc_all_cnt = train_data['Parch'].value_counts()
# print(pc_all_cnt.index,pc_sur_cnt.index)
for i in pc_all_cnt.index:
    if i == 4 or i == 6: 
        pc_sur_cnt[i]=0
    print('parent-child number:',i,' survived rate:',pc_sur_cnt[i]/pc_all_cnt[i])
parent-child number: 0  survived rate: 0.34365781710914456
parent-child number: 1  survived rate: 0.5508474576271186
parent-child number: 2  survived rate: 0.5
parent-child number: 5  survived rate: 0.2
parent-child number: 3  survived rate: 0.6
parent-child number: 4  survived rate: 0.0
parent-child number: 6  survived rate: 0.0
  • Fare
train_data.groupby('Pclass')['Fare'].sum()/train_data.groupby('Pclass')['Survived'].count()
# Pclass 各階級人均車票錢:  1    84.154687;   2    20.662183;   3    13.675550
  • 上車站:覺得這個影響因素本身就不大
parent-child number: S  survived rate: 0.33695652173913043
parent-child number: C  survived rate: 0.5535714285714286
parent-child number: Q  survived rate: 0.38961038961038963

因此,在以上分析過程中,生還可能性與階級、性別、年齡、家人數都有關係,只是關係強弱還未分析,目測與上車口關係不大,車票價錢其實在階級上應該有所反應,應該聯合起來分析一下。

然而,手動提取特徵非常受限;因此,我們採用機器學習的方式進行自動選取特徵,首先要學習的是“隨機森林”,森林由多棵“決策樹”構成,每一棵決策樹會得到一個結果,最後採用“投票法”選擇票數最高的特徵作為結果。(一般的資料科學比賽最初都用的是樹模型,比如較火的lightGBM,XgBoost,CatBoost等)

3 隨機森林

下面是’咖金’那裡拷來的程式碼,直接用sklearn封裝好的庫好容易,但還是要深入學習原理,才能更好地利用它:

from sklearn.ensemble import RandomForestClassifier # 匯入隨機森林模組

# 獲取用於預測的X,y
y = train_data["Survived"]
# 和上面分析的結果一致,年齡缺失較多,從圖上分析,可能也沒那麼重要
features = ["Pclass", "Sex", "SibSp", "Parch"] 
X = pd.get_dummies(train_data[features])
X_test = pd.get_dummies(test_data[features])

# 訓練模型
model = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=1)
model.fit(X, y)
predictions = model.predict(X_test)

# 輸出預測並儲存為本地檔案
output = pd.DataFrame({'PassengerId': test_data.PassengerId, 'Survived': predictions})
output.to_csv('my_submission.csv', index=False)
print("Your submission was successfully saved!")

pd.get_dummies():在使用隨機森林時,需要將非數值列轉換成數值型或者one-hot編碼,這個方法就是將feature中的Sex進行了onehot編碼(或者可以用sklearnsklearn.preprocessing.OneHotEncoder
在這裡插入圖片描述

4 XGboost

kaggle開源xgboost

5 參考博文

後記:
想把思路寫的清楚一些,寫完之後發現有些繁瑣了,不夠有條理,湊活能看,也是學習的過程,加油!

相關文章