本文根據阿里天池學習賽《快來一起挖掘幸福感!》撰寫
載入資料
載入的是完整版的資料 happiness_train_complete.csv
。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
sns.set_style('whitegrid')
# 將 id 列作為 DataFrame 的 index 並且指定 survey_time 為時間序列
data_origin = pd.read_csv('./data/happiness_train_complete.csv', index_col='id', parse_dates=['survey_time'], encoding='gbk')
資料集基本資訊的探索
下面簡單輸出前5行檢視。
data_origin.head()
happiness | survey_type | province | city | county | survey_time | gender | birth | nationality | religion | ... | neighbor_familiarity | public_service_1 | public_service_2 | public_service_3 | public_service_4 | public_service_5 | public_service_6 | public_service_7 | public_service_8 | public_service_9 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||||||||||
1 | 4 | 1 | 12 | 32 | 59 | 2015-08-04 14:18:00 | 1 | 1959 | 1 | 1 | ... | 4 | 50 | 60 | 50 | 50 | 30.0 | 30 | 50 | 50 | 50 |
2 | 4 | 2 | 18 | 52 | 85 | 2015-07-21 15:04:00 | 1 | 1992 | 1 | 1 | ... | 3 | 90 | 70 | 70 | 80 | 85.0 | 70 | 90 | 60 | 60 |
3 | 4 | 2 | 29 | 83 | 126 | 2015-07-21 13:24:00 | 2 | 1967 | 1 | 0 | ... | 4 | 90 | 80 | 75 | 79 | 80.0 | 90 | 90 | 90 | 75 |
4 | 5 | 2 | 10 | 28 | 51 | 2015-07-25 17:33:00 | 2 | 1943 | 1 | 1 | ... | 3 | 100 | 90 | 70 | 80 | 80.0 | 90 | 90 | 80 | 80 |
5 | 4 | 1 | 7 | 18 | 36 | 2015-08-10 09:50:00 | 2 | 1994 | 1 | 1 | ... | 2 | 50 | 50 | 50 | 50 | 50.0 | 50 | 50 | 50 | 50 |
5 rows × 139 columns
檢視資料的詳細資訊,共8000條記錄,139個特徵。
第二列為特證名、第三列為非空記錄個數、第四列為特徵的資料格式。
data_origin.info(verbose=True, null_counts=True)
<class 'pandas.core.frame.DataFrame'>
Int64Index: 8000 entries, 1 to 8000
Data columns (total 139 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 happiness 8000 non-null int64
1 survey_type 8000 non-null int64
2 province 8000 non-null int64
3 city 8000 non-null int64
4 county 8000 non-null int64
5 survey_time 8000 non-null datetime64[ns]
6 gender 8000 non-null int64
7 birth 8000 non-null int64
8 nationality 8000 non-null int64
9 religion 8000 non-null int64
10 religion_freq 8000 non-null int64
11 edu 8000 non-null int64
12 edu_other 3 non-null object
13 edu_status 6880 non-null float64
14 edu_yr 6028 non-null float64
15 income 8000 non-null int64
16 political 8000 non-null int64
17 join_party 824 non-null float64
18 floor_area 8000 non-null float64
19 property_0 8000 non-null int64
20 property_1 8000 non-null int64
21 property_2 8000 non-null int64
22 property_3 8000 non-null int64
23 property_4 8000 non-null int64
24 property_5 8000 non-null int64
25 property_6 8000 non-null int64
26 property_7 8000 non-null int64
27 property_8 8000 non-null int64
28 property_other 66 non-null object
29 height_cm 8000 non-null int64
30 weight_jin 8000 non-null int64
31 health 8000 non-null int64
32 health_problem 8000 non-null int64
33 depression 8000 non-null int64
34 hukou 8000 non-null int64
35 hukou_loc 7996 non-null float64
36 media_1 8000 non-null int64
37 media_2 8000 non-null int64
38 media_3 8000 non-null int64
39 media_4 8000 non-null int64
40 media_5 8000 non-null int64
41 media_6 8000 non-null int64
42 leisure_1 8000 non-null int64
43 leisure_2 8000 non-null int64
44 leisure_3 8000 non-null int64
45 leisure_4 8000 non-null int64
46 leisure_5 8000 non-null int64
47 leisure_6 8000 non-null int64
48 leisure_7 8000 non-null int64
49 leisure_8 8000 non-null int64
50 leisure_9 8000 non-null int64
51 leisure_10 8000 non-null int64
52 leisure_11 8000 non-null int64
53 leisure_12 8000 non-null int64
54 socialize 8000 non-null int64
55 relax 8000 non-null int64
56 learn 8000 non-null int64
57 social_neighbor 7204 non-null float64
58 social_friend 7204 non-null float64
59 socia_outing 8000 non-null int64
60 equity 8000 non-null int64
61 class 8000 non-null int64
62 class_10_before 8000 non-null int64
63 class_10_after 8000 non-null int64
64 class_14 8000 non-null int64
65 work_exper 8000 non-null int64
66 work_status 2951 non-null float64
67 work_yr 2951 non-null float64
68 work_type 2951 non-null float64
69 work_manage 2951 non-null float64
70 insur_1 8000 non-null int64
71 insur_2 8000 non-null int64
72 insur_3 8000 non-null int64
73 insur_4 8000 non-null int64
74 family_income 7999 non-null float64
75 family_m 8000 non-null int64
76 family_status 8000 non-null int64
77 house 8000 non-null int64
78 car 8000 non-null int64
79 invest_0 8000 non-null int64
80 invest_1 8000 non-null int64
81 invest_2 8000 non-null int64
82 invest_3 8000 non-null int64
83 invest_4 8000 non-null int64
84 invest_5 8000 non-null int64
85 invest_6 8000 non-null int64
86 invest_7 8000 non-null int64
87 invest_8 8000 non-null int64
88 invest_other 29 non-null object
89 son 8000 non-null int64
90 daughter 8000 non-null int64
91 minor_child 6934 non-null float64
92 marital 8000 non-null int64
93 marital_1st 7172 non-null float64
94 s_birth 6282 non-null float64
95 marital_now 6230 non-null float64
96 s_edu 6282 non-null float64
97 s_political 6282 non-null float64
98 s_hukou 6282 non-null float64
99 s_income 6282 non-null float64
100 s_work_exper 6282 non-null float64
101 s_work_status 2565 non-null float64
102 s_work_type 2565 non-null float64
103 f_birth 8000 non-null int64
104 f_edu 8000 non-null int64
105 f_political 8000 non-null int64
106 f_work_14 8000 non-null int64
107 m_birth 8000 non-null int64
108 m_edu 8000 non-null int64
109 m_political 8000 non-null int64
110 m_work_14 8000 non-null int64
111 status_peer 8000 non-null int64
112 status_3_before 8000 non-null int64
113 view 8000 non-null int64
114 inc_ability 8000 non-null int64
115 inc_exp 8000 non-null float64
116 trust_1 8000 non-null int64
117 trust_2 8000 non-null int64
118 trust_3 8000 non-null int64
119 trust_4 8000 non-null int64
120 trust_5 8000 non-null int64
121 trust_6 8000 non-null int64
122 trust_7 8000 non-null int64
123 trust_8 8000 non-null int64
124 trust_9 8000 non-null int64
125 trust_10 8000 non-null int64
126 trust_11 8000 non-null int64
127 trust_12 8000 non-null int64
128 trust_13 8000 non-null int64
129 neighbor_familiarity 8000 non-null int64
130 public_service_1 8000 non-null int64
131 public_service_2 8000 non-null int64
132 public_service_3 8000 non-null int64
133 public_service_4 8000 non-null int64
134 public_service_5 8000 non-null float64
135 public_service_6 8000 non-null int64
136 public_service_7 8000 non-null int64
137 public_service_8 8000 non-null int64
138 public_service_9 8000 non-null int64
dtypes: datetime64[ns](1), float64(25), int64(110), object(3)
memory usage: 8.5+ MB
檢視資料總體統計量。
data_origin.describe()
happiness | survey_type | province | city | county | gender | birth | nationality | religion | religion_freq | ... | neighbor_familiarity | public_service_1 | public_service_2 | public_service_3 | public_service_4 | public_service_5 | public_service_6 | public_service_7 | public_service_8 | public_service_9 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 8000.000000 | 8000.000000 | 8000.000000 | 8000.000000 | 8000.000000 | 8000.00000 | 8000.000000 | 8000.00000 | 8000.000000 | 8000.000000 | ... | 8000.000000 | 8000.000000 | 8000.000000 | 8000.000000 | 8000.000000 | 8000.000000 | 8000.000000 | 8000.00000 | 8000.000000 | 8000.000000 |
mean | 3.850125 | 1.405500 | 15.155375 | 42.564750 | 70.619000 | 1.53000 | 1964.707625 | 1.37350 | 0.772250 | 1.427250 | ... | 3.722250 | 70.809500 | 68.170000 | 62.737625 | 66.320125 | 62.794187 | 67.064000 | 66.09625 | 65.626750 | 67.153750 |
std | 0.938228 | 0.491019 | 8.917100 | 27.187404 | 38.747503 | 0.49913 | 16.842865 | 1.52882 | 1.071459 | 1.408441 | ... | 1.143358 | 21.184742 | 20.549943 | 24.771319 | 22.049437 | 23.463162 | 21.586817 | 23.08568 | 23.827493 | 22.502203 |
min | -8.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.00000 | 1921.000000 | -8.00000 | -8.000000 | -8.000000 | ... | -8.000000 | -3.000000 | -3.000000 | -3.000000 | -3.000000 | -3.000000 | -3.000000 | -3.00000 | -3.000000 | -3.000000 |
25% | 4.000000 | 1.000000 | 7.000000 | 18.000000 | 37.000000 | 1.00000 | 1952.000000 | 1.00000 | 1.000000 | 1.000000 | ... | 3.000000 | 60.000000 | 60.000000 | 50.000000 | 60.000000 | 55.000000 | 60.000000 | 60.00000 | 60.000000 | 60.000000 |
50% | 4.000000 | 1.000000 | 15.000000 | 42.000000 | 73.000000 | 2.00000 | 1965.000000 | 1.00000 | 1.000000 | 1.000000 | ... | 4.000000 | 79.000000 | 70.000000 | 70.000000 | 70.000000 | 70.000000 | 70.000000 | 70.00000 | 70.000000 | 70.000000 |
75% | 4.000000 | 2.000000 | 22.000000 | 65.000000 | 104.000000 | 2.00000 | 1977.000000 | 1.00000 | 1.000000 | 1.000000 | ... | 5.000000 | 80.000000 | 80.000000 | 80.000000 | 80.000000 | 80.000000 | 80.000000 | 80.00000 | 80.000000 | 80.000000 |
max | 5.000000 | 2.000000 | 31.000000 | 89.000000 | 134.000000 | 2.00000 | 1997.000000 | 8.00000 | 1.000000 | 9.000000 | ... | 5.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.00000 | 100.000000 | 100.000000 |
8 rows × 135 columns
資料預處理
缺失值處理
檢視子特徵的缺失情況,其中
required_list
表示特徵中的必填項continuous_list
表示特徵屬性為連續型變數categorical_list
表示分型別變數
其餘特徵均為等級(ordinal)型的分類變數。
required_list = ['survey_type', 'province', 'city', 'county', 'survey_time', 'gender', 'birth', 'nationality', 'religion',
'religion_freq', 'edu', 'income', 'political', 'floor_area', 'height_cm', 'weight_jin', 'health', 'health_problem',
'depression', 'hukou', 'socialize', 'relax', 'learn', 'equity', 'class', 'work_exper', 'work_status', 'work_yr', 'work_type',
'work_manage', 'family_income', 'family_m', 'family_status', 'house', 'car', 'marital', 'status_peer', 'status_3_before',
'view', 'inc_ability']
continuous_list = ['birth', 'edu_yr', 'income', 'floor_area', 'height_cm', 'weight_jin', 'work_yr', 'family_income', 'family_m', 'house', 'son',
'daughter', 'minor_child', 'marital_1st', 's_birth', 'marital_now', 's_income', 'f_birth', 'm_birth', 'inc_exp',
'public_service_1', 'public_service_2', 'public_service_3', 'public_service_4', 'public_service_5', 'public_service_6',
'public_service_7', 'public_service_8', 'public_service_9']
categorical_list = ['survey_type', 'province', 'gender', 'nationality']
必填項的缺失值分析
檢視必填項中缺失值的情況。
data_origin[required_list].isna().sum()[data_origin[required_list].isna().sum() > 0].to_frame().T
work_status | work_yr | work_type | work_manage | family_income | |
---|---|---|---|---|---|
0 | 5049 | 5049 | 5049 | 5049 | 1 |
其中
work_status
表示 目前工作的狀況work_yr
表示 一共工作了多少年work_type
表示 目前工作的性質work_manage
表示 目前工作的管理活動情況family_income
表示 去年全年家庭總收入
首先分析 work_
開頭的四項特徵的缺失情況,它們的缺失計數一樣,可能說明調查問卷的填寫方式,可能被跳過了。
首先檢查調查問卷,找到對應的問卷問題,發現在 work_exper
特徵中,即 工作經歷及狀況,根據不同的工作經歷,將上面四個問題跳過。
檢視 work_exper
對應的問卷。
可以發現 work_exper
除了 1
分類,其它問題均被跳問;所以將上面四列的缺失記錄的 work_exper
輸出,檢視是否都為非 1
類的記錄。
通過下面的輸出可以看到,在上面四項特徵為缺失值的情況下,其記錄對應的 work_exper
的取值大部分不為 1
。
data_origin.loc[data_origin[required_list].isna().sum(axis=1)[data_origin[required_list].isna().sum(axis=1) > 0].index, 'work_exper'].to_frame().plot.hist()
pd.value_counts(data_origin.loc[data_origin[required_list].isna().sum(axis=1)[data_origin[required_list].isna().sum(axis=1) > 0].index, 'work_exper'])
5 1968
3 1242
4 1065
2 387
6 380
1 7
Name: work_exper, dtype: int64
進一步檢視取值為 1
的記錄。
(data_origin[data_origin[required_list].isna().sum(axis=1) > 0])[(data_origin[data_origin[required_list].isna().sum(axis=1) > 0].work_exper == 1)]
happiness | survey_type | province | city | county | survey_time | gender | birth | nationality | religion | ... | neighbor_familiarity | public_service_1 | public_service_2 | public_service_3 | public_service_4 | public_service_5 | public_service_6 | public_service_7 | public_service_8 | public_service_9 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||||||||||
692 | 4 | 2 | 21 | 64 | 101 | 2015-07-20 11:12:00 | 2 | 1975 | 1 | 1 | ... | 5 | 80 | 70 | 80 | 80 | 80.0 | 80 | 80 | 80 | 80 |
841 | 4 | 2 | 31 | 88 | 133 | 2015-08-17 13:49:00 | 2 | 1971 | 1 | 0 | ... | 4 | 50 | 30 | -2 | -2 | -2.0 | 50 | 50 | 50 | 70 |
1411 | 4 | 2 | 2 | 2 | 9 | 2015-07-23 09:25:00 | 1 | 1967 | 8 | 1 | ... | 4 | 90 | 85 | 80 | 90 | 90.0 | 92 | 93 | 94 | 90 |
3117 | 4 | 1 | 4 | 7 | 18 | 2015-10-03 16:02:00 | 1 | 1980 | 1 | 1 | ... | 2 | 30 | 35 | 30 | 40 | 60.0 | 40 | 30 | 70 | 70 |
4783 | 5 | 2 | 22 | 65 | 103 | 2015-07-08 18:45:00 | 1 | 1955 | 1 | 1 | ... | 5 | 90 | 90 | 90 | 90 | 80.0 | 90 | 80 | 90 | 90 |
5589 | 5 | 2 | 16 | 46 | 78 | 2015-07-29 11:34:00 | 2 | 1964 | 1 | 1 | ... | 3 | 89 | 63 | 67 | 75 | 74.0 | 67 | 65 | 78 | 79 |
7368 | 4 | 2 | 21 | 64 | 101 | 2015-07-19 08:32:00 | 2 | 1963 | 1 | 1 | ... | 5 | 70 | 70 | 70 | 60 | 70.0 | 70 | 60 | 60 | 60 |
7 rows × 139 columns
可以發現 work_exper
為 1
的記錄存在7條,故將此刪除。
data_origin.drop((data_origin[data_origin[required_list].isna().sum(axis=1) > 0])[(data_origin[data_origin[required_list].isna().sum(axis=1) > 0].work_exper == 1)].index, inplace=True)
因為 family_income
缺失個數只有1條,不影響資料規模,所以直接將其刪除。
data_origin.drop(data_origin['family_income'].isna()[data_origin['family_income'].isna()].index, inplace=True)
連續型特徵缺失值分析
檢視連續型特徵的卻失情況。
data_origin[continuous_list].isna().sum()[data_origin[continuous_list].isna().sum() > 0].to_frame().T
edu_yr | work_yr | minor_child | marital_1st | s_birth | marital_now | s_income | |
---|---|---|---|---|---|---|---|
0 | 1970 | 5041 | 1066 | 828 | 1718 | 1770 | 1718 |
其中
edu_yr
表示 已經完成的最高學歷是哪一年獲得的work_yr
表示 第一份非農工作到目前的工作一共工作了多少年minor_child
表示 有幾個18週歲以下未成年子女marital_1st
表示 第一次結婚的時間s_birth
表示 目前的配偶或同居伴侶是哪一年出生的martital_now
表示 與目前的配偶是哪一年結婚的s_income
表示 配偶或同居伴侶去年全年的總收入
對於 edu_yr
即 已經完成的最高學歷是哪一年獲得的,檢視缺失記錄的 edu_status
取值分佈情況。
data_origin[data_origin['edu_yr'].isna()]['edu_status'].plot.hist()
pd.value_counts(data_origin[data_origin['edu_yr'].isna()]['edu_status'])
2.0 746
3.0 103
4.0 1
1.0 1
Name: edu_status, dtype: int64
檢視 edu_yr
缺失的記錄的 edu_status
特徵後,只有選項 4
即 畢業的記錄才應該填寫 edu_yr
的畢業年份,所以應該刪除記錄。
data_origin.drop(data_origin[(data_origin['edu_status'] == 4) & (data_origin['edu_yr'].isna())].index, inplace=True)
data_origin.shape
(7991, 139)
對於 minor_child
特徵,可以檢查這個特徵缺失的記錄另外兩項特徵 son
和 daughter
分別表示兒子、女兒的數量,如果為0,則將 minor_child
也填充為0。
print(data_origin[np.array(data_origin['minor_child'].isna())].loc[:, 'son'].sum())
print(data_origin[np.array(data_origin['minor_child'].isna())].loc[:, 'daughter'].sum())
data_origin[np.array(data_origin['minor_child'].isna())].loc[:, 'son':'daughter']
0
0
son | daughter | |
---|---|---|
id | ||
2 | 0 | 0 |
5 | 0 | 0 |
9 | 0 | 0 |
29 | 0 | 0 |
31 | 0 | 0 |
... | ... | ... |
7967 | 0 | 0 |
7972 | 0 | 0 |
7991 | 0 | 0 |
7999 | 0 | 0 |
8000 | 0 | 0 |
1066 rows × 2 columns
可以看對 minor_child
缺失的記錄,其兒子和女兒的個數也為0,所以將 minor_child
缺失值填充為0。
data_origin['minor_child'].fillna(0, inplace=True)
對於 marital_1st
的記錄的缺失情況,可以檢視對應的記錄的 marital
的取值是否為 1
表示 未婚。
print(data_origin[np.array(data_origin['marital_1st'].isna())]['marital'].sum() == data_origin[np.array(data_origin['marital_1st'].isna())]['marital'].shape[0])
data_origin[np.array(data_origin['marital_1st'].isna())]['marital'].plot.hist()
pd.value_counts(data_origin[np.array(data_origin['marital_1st'].isna())]['marital'])
True
1 828
Name: marital, dtype: int64
可以看到輸出結果表明對於 marital_1st
缺失的記錄都是未婚人士,所以缺失值正常。
下面檢視 s_birth
即 目前的配偶或同居伴侶是哪一年出生的的缺失情況,首先檢視缺失的記錄的 marital
狀態,檢視是否滿足無配偶或同居伴侶的情況。
data_origin[data_origin['s_birth'].isna()]['marital'].plot.hist()
pd.value_counts(data_origin[data_origin['s_birth'].isna()]['marital'])
1 828
7 718
6 171
2 1
Name: marital, dtype: int64
根據輸出可以看到,marital
取值為 1
、6
、7
分別表示 未婚、離婚和喪偶,所以 s_birth
缺失屬於正常;而且取值為 2
表示同居的缺失記錄只有一條,所以直接將其刪除即可。
data_origin.drop(data_origin[data_origin['s_birth'].isna()]['marital'][data_origin[data_origin['s_birth'].isna()]['marital'] == 2].index, inplace=True)
對於 marital_now
即 與目前的配偶是哪一年結婚的,首先輸出 marital
檢視婚姻的狀態,是否滿足沒結婚的條件。
data_origin[data_origin['marital_now'].isna()]['marital'].plot.hist()
pd.value_counts(data_origin[data_origin['marital_now'].isna()]['marital'])
1 828
7 718
6 171
2 51
3 1
Name: marital, dtype: int64
根據輸出可以得到 1
和 2
表示沒有結婚的情況,所以缺失屬於正常;
對於 3
、6
、7
分別表示 初婚有配偶、離婚、喪偶;只有 3
屬於目前有配偶並結婚的情況,所以應該刪除。
data_origin.drop(data_origin[data_origin['marital_now'].isna()].loc[data_origin[data_origin['marital_now'].isna()]['marital'] == 3].index, inplace=True)
data_origin.shape
(7989, 139)
對於 s_income
即 配偶或同居伴侶去年全年的總收入的缺失情況,可以檢查對於 marital
檢視其是否滿足無配偶或伴侶的條件。
data_origin[data_origin['s_income'].isna()]['marital'].plot.hist()
pd.value_counts(data_origin[data_origin['s_income'].isna()]['marital'])
1 828
7 718
6 171
Name: marital, dtype: int64
可以看到對於 s_income
的缺失值,其記錄對應的婚姻狀態都為未婚、離婚或喪偶,所以 s_income
缺失是正常的。
分類變數缺失值分析
檢視分型別(categorical)變數的缺失情況,全部為0,則沒有缺失值。
data_origin[categorical_list].isna().sum().to_frame().T
survey_type | province | gender | nationality | |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
所有特徵缺失值分析
檢視所有特徵的缺失情況。
data_origin.isna().sum()[data_origin.isna().sum() > 0].to_frame().T
edu_other | edu_status | edu_yr | join_party | property_other | hukou_loc | social_neighbor | social_friend | work_status | work_yr | ... | marital_1st | s_birth | marital_now | s_edu | s_political | s_hukou | s_income | s_work_exper | s_work_status | s_work_type | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 7986 | 1119 | 1969 | 7167 | 7923 | 4 | 795 | 795 | 5038 | 5038 | ... | 828 | 1717 | 1768 | 1717 | 1717 | 1717 | 1717 | 1717 | 5427 | 5427 |
1 rows × 23 columns
首先對於 edu_other
特徵,只有在 edu
填寫了 14
的情況下才填寫,首先檢查 edu_other
缺失的記錄的 edu
是否為 14
若為 14
則說明 edu_other
不應該為缺失,應該將其刪除。
data_origin[data_origin['edu_other'].isna()][data_origin[data_origin['edu_other'].isna()]['edu'] == 14]
happiness | survey_type | province | city | county | survey_time | gender | birth | nationality | religion | ... | neighbor_familiarity | public_service_1 | public_service_2 | public_service_3 | public_service_4 | public_service_5 | public_service_6 | public_service_7 | public_service_8 | public_service_9 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||||||||||
1242 | 4 | 2 | 3 | 6 | 13 | 2015-09-24 17:58:00 | 1 | 1971 | 1 | 1 | ... | 5 | 100 | 90 | 60 | 80 | 70.0 | 80 | 70 | 60 | 50 |
3651 | 3 | 2 | 3 | 6 | 13 | 2015-09-24 20:25:00 | 1 | 1953 | 1 | 1 | ... | 5 | 100 | 100 | 60 | 50 | 70.0 | 50 | 30 | 70 | 40 |
5330 | 2 | 2 | 3 | 6 | 13 | 2015-09-25 07:57:00 | 1 | 1953 | 1 | 1 | ... | 5 | 100 | 100 | 100 | 100 | 100.0 | 100 | 30 | 100 | 50 |
3 rows × 139 columns
可以看到 edu
為 14
的記錄中,有3條記錄 edu_other
也為缺失;所以將3條記錄刪除。
data_origin.drop(data_origin[data_origin['edu_other'].isna()][data_origin[data_origin['edu_other'].isna()]['edu'] == 14].index, inplace=True)
對於 edu_status
的缺失記錄,可以先檢查記錄對應的 edu
是取的何值。
data_origin[data_origin['edu_status'].isna()]['edu'].plot.hist()
pd.value_counts(data_origin[data_origin['edu_status'].isna()]['edu'])
1 1052
2 65
3 2
Name: edu, dtype: int64
可以看到對於 edu_status
缺失的記錄,其對應的 edu
教育程度為別為沒有受過任何教育、私塾、掃盲班和小學;對於取值為 1
和 2
的情況,屬於跳問選項,對應的 edu_status
屬於缺失是正常的;所以將 edu
取值為 3
的記錄刪除。
data_origin.drop(data_origin[data_origin['edu_status'].isna()][data_origin[data_origin['edu_status'].isna()]['edu'] == 3].index, inplace=True)
對於 join_party
即 目前政治面貌是黨員的入黨時間,只有政治面貌不是黨員的缺失值才算正確,檢視分佈情況。
data_origin[data_origin['join_party'].isna()]['political'].plot.hist()
pd.value_counts(data_origin[data_origin['join_party'].isna()]['political'])
1 6703
2 402
-8 41
3 11
4 5
Name: political, dtype: int64
根據直方圖看到,有5條記錄的 partical
的取值是 4
而入黨時間沒有填寫,所以將這5條記錄刪除。
data_origin.drop(data_origin[data_origin['join_party'].isna()][data_origin[data_origin['join_party'].isna()]['political'] == 4].index, inplace=True)
對於 hukou_loc
即 目前的戶口登記地,檢視缺失記錄的 hukou
登記情況,發現取值都為 7
即 沒有戶口,所以缺失屬於正常。
data_origin[data_origin['hukou_loc'].isna()]['hukou'].to_frame()
hukou | |
---|---|
id | |
589 | 7 |
3657 | 7 |
3799 | 7 |
7811 | 7 |
對於 social_neighbor
和 social_friend
即與與其他朋友進行社交娛樂活動的頻繁程度和有多少個晚上是因為出去度假或者探訪親友而沒有在家過夜,首先檢視缺失記錄的 socialize
的分佈情況。
data_origin[data_origin['social_neighbor'].isna()]['socialize'].plot.hist()
pd.value_counts(data_origin[data_origin['social_neighbor'].isna()]['socialize'])
1 793
Name: socialize, dtype: int64
可以發現所有的 social_neighbor
和 social_friend
缺失記錄的 socialize
即 是否經常在空閒時間做社交的事情全部均為 1
即 從不社交,所以兩個特徵的缺失值可以使用 1
填充。
data_origin['social_neighbor'].fillna(1, inplace=True)
data_origin['social_friend'].fillna(1, inplace=True)
對於 s_edu
到 s_work_exper
的特徵,缺失值的記錄數都一樣,所以存在可能這幾項特徵的缺失記錄都來自同一批問卷物件。
首先檢視 s_edu
的缺失記錄的 marital
的分佈情況。
data_origin[data_origin['s_edu'].isna()]['marital'].plot.hist()
pd.value_counts(data_origin[data_origin['s_edu'].isna()]['marital'])
1 827
7 717
6 171
Name: marital, dtype: int64
可以發現 s_edu
缺失的記錄的婚姻情況全部均為未婚、離婚或喪偶,均屬於沒有配偶或同居伴侶的情況,所以屬於正常的缺失。
對於 s_political
到 s_work_exper
全部均屬於上述情況。
對於 s_work_status
即 配偶或同居伴侶目前的工作狀況,首先檢視調查問卷。
可以得知只有 s_work_exper
填寫了 1
的情況下才應該填寫 s_work_status
和 s_work_type
其它選項均需要跳過,所以屬於正常缺失值。
下面檢視 s_work_status
缺失記錄的 s_work_exper
的分佈情況。
data_origin[data_origin['s_work_status'].isna()]['s_work_exper'].plot.hist()
pd.value_counts(data_origin[data_origin['s_work_status'].isna()]['s_work_exper'])
5.0 1424
3.0 1017
4.0 823
6.0 221
2.0 217
1.0 1
Name: s_work_exper, dtype: int64
檢視得知 s_work_exper
選 1
的記錄只有1條,直接刪除即可。
data_origin.drop(data_origin[data_origin['s_work_status'].isna()][data_origin[data_origin['s_work_status'].isna()]['s_work_exper'] == 1].index, inplace=True)
在調查問卷中,每個選項通用含義,其 -1
表示不適用;-2
表示不知道;-3
表示拒絕回答;-8
表示無法回答。
在這裡將所有的特徵的負數使用每一個特徵的中位數進行填充。
data_origin.shape
(7978, 139)
no_ne_rows_index = (data_origin.drop(['survey_time', 'edu_other', 'property_other', 'invest_other'], axis=1) < 0).sum(axis=1)[(data_origin.drop(['survey_time', 'edu_other', 'property_other', 'invest_other'], axis=1) < 0).sum(axis=1) == 0].index
for column, content in data_origin.items():
if pd.api.types.is_numeric_dtype(content):
data_origin[column] = data_origin[column].apply(lambda x : pd.Series(data_origin.loc[no_ne_rows_index, :][column].unique()).median() if(x < 0 and x != np.nan) else x)
將所有的負數填充完成後,再將 NaN
數值全部使用統一的一個值 -1
填充。
data_origin.fillna(-1, inplace=True)
至此,所有特徵的缺失值已經全部處理完畢。
文字資料處理
在所有的特徵中,有3個特徵分別是 edu_other
、property_other
、invest_other
是字串資料,需要將其轉換成序號編碼(Ordinal Encoding)。
首先檢視 edu_other
的填寫情況。
data_origin[data_origin['edu_other'] != -1]['edu_other'].to_frame()
edu_other | |
---|---|
id | |
1170 | 夜校 |
2513 | 夜校 |
4926 | 夜校 |
可以看到 edu_other
的填寫情況全都是夜校,將字串轉換成序號編碼。
data_origin['edu_other'] = data_origin['edu_other'].astype('category').values.codes + 1
檢視 property_other
即 房子產權歸屬誰,首先檢查調查問卷的填寫情況。
data_origin[data_origin['property_other'] != -1]['property_other'].to_frame()
property_other | |
---|---|
id | |
76 | 無產權 |
92 | 已購買,但未過戶 |
99 | 家庭共同所有 |
132 | 待辦 |
455 | 沒有產權 |
... | ... |
7376 | 家人共有 |
7746 | 全家人共有 |
7776 | 兄弟共有 |
7821 | 未分家,全家所有 |
7917 | 家人共有 |
66 rows × 1 columns
根據填寫情況來看,其中有很多填寫資訊都是一個意思,例如 家庭共同所有
和 全家所有
是同一個意思,但是在python處理中只能一個個的手動處理。
#data_origin.loc[[8009, 9212, 9759, 10517], 'property_other'] = '多人擁有'
#data_origin.loc[[8014, 8056, 10264], 'property_other'] = '未過戶'
#data_origin.loc[[8471, 8825, 9597, 9810, 9842, 9967, 10069, 10166, 10203, 10469], 'property_other'] = '全家擁有'
#data_origin.loc[[8553, 8596, 9605, 10421, 10814], 'property_other'] = '無產權'
data_origin.loc[[76, 132, 455, 495, 1415, 2511, 2792, 2956, 3647, 4147, 4193, 4589, 5023, 5382, 5492, 6102, 6272, 6339,
6507, 7184, 7239], 'property_other'] = '無產權'
data_origin.loc[[92, 1888, 2703, 3381, 5654], 'property_other'] = '未過戶'
data_origin.loc[[99, 619, 2728, 3062, 3222, 3251, 3696, 5283, 6191, 7295, 7376, 7746, 7821, 7917], 'property_other'] = '全家擁有'
data_origin.loc[[1597, 4993, 5398, 5899, 7240, 7776], 'property_other'] = '多人擁有'
data_origin.loc[[6469, 6891], 'property_other'] = '小產權'
將字串編碼為整數型的序號(ordinal)型別。
data_origin['property_other'] = data_origin['property_other'].astype('category').values.codes + 1
檢視 invest_other
即 從事的投資活動的填寫情況。
pd.DataFrame(data_origin[data_origin['invest_other'] != -1]['invest_other'].unique())
0 | |
---|---|
0 | 理財產品 |
1 | 民間借貸 |
2 | 銀行理財 |
3 | 儲蓄存款 |
4 | 理財 |
5 | 銀行存款利息 |
6 | 活期儲蓄 |
7 | 投資服務業、傢俱業 |
8 | 銀行存款 |
9 | 個人融資 |
10 | 租房 |
11 | 老人家不清楚 |
12 | 家中有部分土地承包出去 |
13 | 沒有 |
14 | 高利貸 |
15 | 彩票 |
16 | 自己沒有,兒女不清楚 |
17 | 網上理財 |
18 | 統籌 |
19 | 福利車票 |
20 | 其他理財產品 |
21 | 商業萬能保險 |
22 | 投資開發區 |
23 | 字畫、茶壺 |
同樣地,將其轉換成整數型別的序號(ordinal)編碼。
data_origin['invest_other'] = data_origin['invest_other'].astype('category').values.codes + 1
離群值處理
data_nona = data_origin.copy()
畫出箱型圖分析特徵的異常值。
並刪除離群記錄。
sns.boxplot(x=data_nona['house'])
<AxesSubplot:xlabel='house'>
data_nona.drop(data_nona[data_nona['house'] > 25].index, inplace=True)
sns.boxplot(x=data_nona['family_m'])
<AxesSubplot:xlabel='family_m'>
data_nona.drop(data_nona[data_nona['family_m'] > 40].index, inplace=True)
sns.boxplot(x=data_nona['inc_exp'])
<AxesSubplot:xlabel='inc_exp'>
data_nona.drop(data_nona[data_nona['inc_exp'] > 0.6e8].index, inplace=True)
檢視調查時間的月份分佈情況,因為調查問卷都是在2015年填寫,只需要檢視月份的離群點。
由圖可知調查問卷是從6月開始的,記錄中2月的問卷屬於異常資料,應該刪除。
sns.boxplot(x=data_nona['survey_time'].dt.month)
<AxesSubplot:xlabel='survey_time'>
data_nona.drop(data_nona[data_nona['survey_time'].dt.month < 6].index, inplace=True)
特徵構造
特徵構造也可稱為特徵交叉、特徵組合、資料變換。
連續變數離散化
離散化除了一些計算方面等等好處,還可以引入非線性特性,也可以很方便的做cross-feature。離散特徵的增加和減少都很容易,易於模型的快速迭代。此外,噪聲很大的環境中,離散化可以降低特徵中包含的噪聲,提升特徵的表達能力。
pd.DataFrame(continuous_list)
0 | |
---|---|
0 | birth |
1 | edu_yr |
2 | income |
3 | floor_area |
4 | height_cm |
5 | weight_jin |
6 | work_yr |
7 | family_income |
8 | family_m |
9 | house |
10 | son |
11 | daughter |
12 | minor_child |
13 | marital_1st |
14 | s_birth |
15 | marital_now |
16 | s_income |
17 | f_birth |
18 | m_birth |
19 | inc_exp |
20 | public_service_1 |
21 | public_service_2 |
22 | public_service_3 |
23 | public_service_4 |
24 | public_service_5 |
25 | public_service_6 |
26 | public_service_7 |
27 | public_service_8 |
28 | public_service_9 |
將連續型變數全部進行分箱,然後對每個區間進行編碼,生成新的離散的特徵。
for column in continuous_list:
cut = pd.qcut(data_nona[column], q=5, duplicates='drop')
cat = cut.values
codes = cat.codes
data_nona[column + '_discrete'] = codes
for column, content in data_nona.items():
if pd.api.types.is_numeric_dtype(content):
data_nona[column] = content.astype('int')
特徵選擇
將連續變數離散化後,生成以字尾 _discrete
的新特徵,所以將原來的連續變數的特徵刪除掉。
data_nona.to_csv('./data/happiness_train_complete_analysis.csv')
data_nona.drop(continuous_list, axis=1, inplace=True)
data_nona.to_csv('./data/happiness_train_complete_nona.csv')
特徵分析
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
data = pd.read_csv('./data/happiness_train_complete_analysis.csv', index_col='id', parse_dates=['survey_time'])
data.head()
happiness | survey_type | province | city | county | survey_time | gender | birth | nationality | religion | ... | inc_exp_discrete | public_service_1_discrete | public_service_2_discrete | public_service_3_discrete | public_service_4_discrete | public_service_5_discrete | public_service_6_discrete | public_service_7_discrete | public_service_8_discrete | public_service_9_discrete | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||||||||||
1 | 4 | 1 | 12 | 32 | 59 | 2015-08-04 14:18:00 | 1 | 1959 | 1 | 1 | ... | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 4 | 2 | 18 | 52 | 85 | 2015-07-21 15:04:00 | 1 | 1992 | 1 | 1 | ... | 2 | 4 | 1 | 2 | 3 | 4 | 1 | 4 | 0 | 0 |
3 | 4 | 2 | 29 | 83 | 126 | 2015-07-21 13:24:00 | 2 | 1967 | 1 | 0 | ... | 3 | 4 | 2 | 3 | 3 | 3 | 4 | 4 | 4 | 2 |
4 | 5 | 2 | 10 | 28 | 51 | 2015-07-25 17:33:00 | 2 | 1943 | 1 | 1 | ... | 0 | 4 | 3 | 2 | 3 | 3 | 4 | 4 | 3 | 2 |
5 | 4 | 1 | 7 | 18 | 36 | 2015-08-10 09:50:00 | 2 | 1994 | 1 | 1 | ... | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 168 columns
data.describe()
happiness | survey_type | province | city | county | gender | birth | nationality | religion | religion_freq | ... | inc_exp_discrete | public_service_1_discrete | public_service_2_discrete | public_service_3_discrete | public_service_4_discrete | public_service_5_discrete | public_service_6_discrete | public_service_7_discrete | public_service_8_discrete | public_service_9_discrete | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | ... | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 | 7968.000000 |
mean | 3.866466 | 1.405120 | 15.158258 | 42.572164 | 70.631903 | 1.530748 | 1964.710216 | 1.399724 | 0.880271 | 1.452560 | ... | 1.725653 | 1.665537 | 1.272214 | 1.841365 | 1.613328 | 1.848519 | 1.643449 | 1.651732 | 1.654869 | 1.302962 |
std | 0.818844 | 0.490946 | 8.915876 | 27.183764 | 38.736751 | 0.499085 | 16.845155 | 1.466409 | 0.324665 | 1.358444 | ... | 1.338535 | 1.420309 | 1.108440 | 1.342524 | 1.499494 | 1.297290 | 1.533445 | 1.544477 | 1.511468 | 1.078601 |
min | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1921.000000 | 1.000000 | 0.000000 | 1.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 4.000000 | 1.000000 | 7.000000 | 18.000000 | 37.000000 | 1.000000 | 1952.000000 | 1.000000 | 1.000000 | 1.000000 | ... | 1.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
50% | 4.000000 | 1.000000 | 15.000000 | 42.000000 | 73.000000 | 2.000000 | 1965.000000 | 1.000000 | 1.000000 | 1.000000 | ... | 1.000000 | 2.000000 | 1.000000 | 2.000000 | 1.000000 | 2.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
75% | 4.000000 | 2.000000 | 22.000000 | 65.000000 | 104.000000 | 2.000000 | 1977.000000 | 1.000000 | 1.000000 | 1.000000 | ... | 3.000000 | 2.000000 | 2.000000 | 3.000000 | 3.000000 | 3.000000 | 3.000000 | 3.000000 | 3.000000 | 2.000000 |
max | 5.000000 | 2.000000 | 31.000000 | 89.000000 | 134.000000 | 2.000000 | 1997.000000 | 8.000000 | 1.000000 | 9.000000 | ... | 4.000000 | 4.000000 | 3.000000 | 4.000000 | 4.000000 | 4.000000 | 4.000000 | 4.000000 | 4.000000 | 3.000000 |
8 rows × 167 columns
data.info(verbose=True, null_counts=True)
<class 'pandas.core.frame.DataFrame'>
Int64Index: 7968 entries, 1 to 8000
Data columns (total 168 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 happiness 7968 non-null int64
1 survey_type 7968 non-null int64
2 province 7968 non-null int64
3 city 7968 non-null int64
4 county 7968 non-null int64
5 survey_time 7968 non-null datetime64[ns]
6 gender 7968 non-null int64
7 birth 7968 non-null int64
8 nationality 7968 non-null int64
9 religion 7968 non-null int64
10 religion_freq 7968 non-null int64
11 edu 7968 non-null int64
12 edu_other 7968 non-null int64
13 edu_status 7968 non-null int64
14 edu_yr 7968 non-null int64
15 income 7968 non-null int64
16 political 7968 non-null int64
17 join_party 7968 non-null int64
18 floor_area 7968 non-null int64
19 property_0 7968 non-null int64
20 property_1 7968 non-null int64
21 property_2 7968 non-null int64
22 property_3 7968 non-null int64
23 property_4 7968 non-null int64
24 property_5 7968 non-null int64
25 property_6 7968 non-null int64
26 property_7 7968 non-null int64
27 property_8 7968 non-null int64
28 property_other 7968 non-null int64
29 height_cm 7968 non-null int64
30 weight_jin 7968 non-null int64
31 health 7968 non-null int64
32 health_problem 7968 non-null int64
33 depression 7968 non-null int64
34 hukou 7968 non-null int64
35 hukou_loc 7968 non-null int64
36 media_1 7968 non-null int64
37 media_2 7968 non-null int64
38 media_3 7968 non-null int64
39 media_4 7968 non-null int64
40 media_5 7968 non-null int64
41 media_6 7968 non-null int64
42 leisure_1 7968 non-null int64
43 leisure_2 7968 non-null int64
44 leisure_3 7968 non-null int64
45 leisure_4 7968 non-null int64
46 leisure_5 7968 non-null int64
47 leisure_6 7968 non-null int64
48 leisure_7 7968 non-null int64
49 leisure_8 7968 non-null int64
50 leisure_9 7968 non-null int64
51 leisure_10 7968 non-null int64
52 leisure_11 7968 non-null int64
53 leisure_12 7968 non-null int64
54 socialize 7968 non-null int64
55 relax 7968 non-null int64
56 learn 7968 non-null int64
57 social_neighbor 7968 non-null int64
58 social_friend 7968 non-null int64
59 socia_outing 7968 non-null int64
60 equity 7968 non-null int64
61 class 7968 non-null int64
62 class_10_before 7968 non-null int64
63 class_10_after 7968 non-null int64
64 class_14 7968 non-null int64
65 work_exper 7968 non-null int64
66 work_status 7968 non-null int64
67 work_yr 7968 non-null int64
68 work_type 7968 non-null int64
69 work_manage 7968 non-null int64
70 insur_1 7968 non-null int64
71 insur_2 7968 non-null int64
72 insur_3 7968 non-null int64
73 insur_4 7968 non-null int64
74 family_income 7968 non-null int64
75 family_m 7968 non-null int64
76 family_status 7968 non-null int64
77 house 7968 non-null int64
78 car 7968 non-null int64
79 invest_0 7968 non-null int64
80 invest_1 7968 non-null int64
81 invest_2 7968 non-null int64
82 invest_3 7968 non-null int64
83 invest_4 7968 non-null int64
84 invest_5 7968 non-null int64
85 invest_6 7968 non-null int64
86 invest_7 7968 non-null int64
87 invest_8 7968 non-null int64
88 invest_other 7968 non-null int64
89 son 7968 non-null int64
90 daughter 7968 non-null int64
91 minor_child 7968 non-null int64
92 marital 7968 non-null int64
93 marital_1st 7968 non-null int64
94 s_birth 7968 non-null int64
95 marital_now 7968 non-null int64
96 s_edu 7968 non-null int64
97 s_political 7968 non-null int64
98 s_hukou 7968 non-null int64
99 s_income 7968 non-null int64
100 s_work_exper 7968 non-null int64
101 s_work_status 7968 non-null int64
102 s_work_type 7968 non-null int64
103 f_birth 7968 non-null int64
104 f_edu 7968 non-null int64
105 f_political 7968 non-null int64
106 f_work_14 7968 non-null int64
107 m_birth 7968 non-null int64
108 m_edu 7968 non-null int64
109 m_political 7968 non-null int64
110 m_work_14 7968 non-null int64
111 status_peer 7968 non-null int64
112 status_3_before 7968 non-null int64
113 view 7968 non-null int64
114 inc_ability 7968 non-null int64
115 inc_exp 7968 non-null int64
116 trust_1 7968 non-null int64
117 trust_2 7968 non-null int64
118 trust_3 7968 non-null int64
119 trust_4 7968 non-null int64
120 trust_5 7968 non-null int64
121 trust_6 7968 non-null int64
122 trust_7 7968 non-null int64
123 trust_8 7968 non-null int64
124 trust_9 7968 non-null int64
125 trust_10 7968 non-null int64
126 trust_11 7968 non-null int64
127 trust_12 7968 non-null int64
128 trust_13 7968 non-null int64
129 neighbor_familiarity 7968 non-null int64
130 public_service_1 7968 non-null int64
131 public_service_2 7968 non-null int64
132 public_service_3 7968 non-null int64
133 public_service_4 7968 non-null int64
134 public_service_5 7968 non-null int64
135 public_service_6 7968 non-null int64
136 public_service_7 7968 non-null int64
137 public_service_8 7968 non-null int64
138 public_service_9 7968 non-null int64
139 birth_discrete 7968 non-null int64
140 edu_yr_discrete 7968 non-null int64
141 income_discrete 7968 non-null int64
142 floor_area_discrete 7968 non-null int64
143 height_cm_discrete 7968 non-null int64
144 weight_jin_discrete 7968 non-null int64
145 work_yr_discrete 7968 non-null int64
146 family_income_discrete 7968 non-null int64
147 family_m_discrete 7968 non-null int64
148 house_discrete 7968 non-null int64
149 son_discrete 7968 non-null int64
150 daughter_discrete 7968 non-null int64
151 minor_child_discrete 7968 non-null int64
152 marital_1st_discrete 7968 non-null int64
153 s_birth_discrete 7968 non-null int64
154 marital_now_discrete 7968 non-null int64
155 s_income_discrete 7968 non-null int64
156 f_birth_discrete 7968 non-null int64
157 m_birth_discrete 7968 non-null int64
158 inc_exp_discrete 7968 non-null int64
159 public_service_1_discrete 7968 non-null int64
160 public_service_2_discrete 7968 non-null int64
161 public_service_3_discrete 7968 non-null int64
162 public_service_4_discrete 7968 non-null int64
163 public_service_5_discrete 7968 non-null int64
164 public_service_6_discrete 7968 non-null int64
165 public_service_7_discrete 7968 non-null int64
166 public_service_8_discrete 7968 non-null int64
167 public_service_9_discrete 7968 non-null int64
dtypes: datetime64[ns](1), int64(167)
memory usage: 10.3 MB
首先,檢視 happiness
幸福程度的分佈,可以發現多數人都屬於 比較幸福
的程度。
sns.set_theme(style="darkgrid")
sns.displot(data, x="happiness", facet_kws=dict(margin_titles=True))
<seaborn.axisgrid.FacetGrid at 0x25128009850>
檢視每個人的收入和幸福度的散點圖,通過散點圖可以看出隨著收入的提高,大多數點都落在了較高的幸福程度上;即使如此,也會發現存在一些收入非常高的人也處在一個說不上幸福不幸福的程度。
sns.set_theme(style="whitegrid")
f, ax = plt.subplots()
sns.despine(f, left=True, bottom=True)
sns.scatterplot(x="happiness", y="income",
size="income",
palette="ch:r=-.2,d=.3_r",
data=data, ax=ax)
<AxesSubplot:xlabel='happiness', ylabel='income'>
檢視性別男女的幸福程度的分佈直方圖,在性別特徵上沒有過多的類別不平衡情況。
sns.set_theme(style="darkgrid")
sns.displot(
data, x="happiness", col="gender",
facet_kws=dict(margin_titles=True)
)
<seaborn.axisgrid.FacetGrid at 0x25128b8e1f0>
通過直線圖,可以看出,隨著 edu
受到的教育的提高,幸福程度也隨之提升。
sns.set_theme(style="ticks")
palette = sns.color_palette("rocket_r")
sns.relplot(
data=data,
x="edu", y="happiness",
kind="line", size_order=["T1", "T2"], palette=palette,
facet_kws=dict(sharex=False)
)
<seaborn.axisgrid.FacetGrid at 0x251284bdeb0>
檢視每個幸福程度的出生日期,可以看出,不同幸福程度的年代的人分佈都是大同小異的。
sns.set_theme(style="ticks", palette="pastel")
sns.boxplot(x="happiness", y="birth",
data=data)
sns.despine(offset=10, trim=True)
將記錄分為是否信仰宗教信仰,檢視幸福度和健康狀況的分裂小提琴圖,也可以看出一個趨勢,幸福度高的人大多數都分佈在較高的健康狀況上,而且也可以看出一個現象,隨著健康狀況和幸福度的提高,信仰宗教信仰的人數也慢慢增加。
sns.set_theme(style="whitegrid")
sns.violinplot(data=data, x="happiness", y="health", hue="religion",
split=True, inner="quart", linewidth=1)
sns.despine(left=True)
繪製一個多變數分佈直方圖,可以看出大多數比較幸福的人,房產的數量也不會大幅增加。
import seaborn as sns
sns.set_theme(style="ticks")
g = sns.JointGrid(data=data, x="happiness", y="house", marginal_ticks=True)
# Set a log scaling on the y axis
g.ax_joint.set(yscale="linear")
# Create an inset legend for the histogram colorbar
cax = g.fig.add_axes([.15, .55, .02, .2])
# Add the joint and marginal histogram plots
g.plot_joint(
sns.histplot, discrete=(True, False),
cmap="light:#03012d", pmax=.8, cbar=True, cbar_ax=cax
)
g.plot_marginals(sns.histplot, element="step", color="#03012d")
<seaborn.axisgrid.JointGrid at 0x251288fee20>
繪製幸福度和住房建築面積的核密度估計圖,可以看出同樣的現象,多數比較幸福的人的房屋建築面積也不會集中在很高的一個水平,但是也會有一個隨著房屋建築面積的增加幸福度也增加的現象。
sns.set_theme(style="ticks")
g = sns.jointplot(
data=data[data['floor_area'] < 600],
x="happiness", y="floor_area",
kind="kde",
)
檢視各個特徵的熱力圖,可以根據圖中的顏色深度看出兩兩特徵之間的相關性的高低。
sns.set_theme(style="whitegrid")
corr_list = ['survey_type', 'province', 'city', 'county', 'survey_time', 'gender', 'birth', 'nationality', 'religion',
'religion_freq', 'edu', 'income', 'political', 'floor_area', 'height_cm', 'weight_jin', 'health', 'health_problem',
'depression', 'hukou', 'socialize', 'relax', 'learn', 'equity', 'class', 'work_exper', 'work_status', 'work_yr', 'work_type',
'work_manage', 'family_income', 'family_m', 'family_status', 'house', 'car', 'marital', 'status_peer', 'status_3_before',
'view', 'inc_ability']
df = data
corr_mat = data[corr_list].corr().stack().reset_index(name="correlation")
g = sns.relplot(
data=corr_mat,
x="level_0", y="level_1", hue="correlation", size="correlation",
palette="vlag", hue_norm=(-1, 1), edgecolor=".7",
height=10, sizes=(50, 250), size_norm=(-.2, .8),
)
g.set(xlabel="", ylabel="", aspect="equal")
g.despine(left=True, bottom=True)
g.ax.margins(.02)
for label in g.ax.get_xticklabels():
label.set_rotation(90)
for artist in g.legend.legendHandles:
artist.set_edgecolor(".7")
檢視全國省會城市的幸福人數的佔比條形圖,通過圖中可以看出,湖北省調查人數最多但幸福人數不算高;河南省和山東省的幸福人數的佔比非常之高;即使內蒙古自治區的調查人數最少,但是幸福人數的佔比卻是非常高的。
sns.set_theme(style="whitegrid")
province_total = data['province'].groupby(data['province']).count().sort_values(ascending=False).to_frame()
province_total.columns = ['total']
happiness_involved = []
for index in province_total.index:
happiness_involved.append((data[data['province'] == index][data[data['province'] == index]['happiness'] > 3].shape[0]))
happiness_involved = pd.DataFrame(happiness_involved, index=province_total.index)
happiness_involved.columns = ['involved']
province_total['province'] = province_total.index.map({
1 : 'Shanghai', 2 : 'Yunnan', 3 : 'Neimeng', 4 : 'Beijing', 5 : 'Jilin', 6 : 'Sichuan', 7 : 'Tianjin', 8 : 'Ningxia',
9 : 'Anhui', 10 : 'Shandong', 11 : 'Shanxi', 12 : 'Guangdong', 13 : 'Guangxi', 14 : 'Xinjiang', 15 : 'Jiangsu',
16 : 'Jiangxi', 17 : 'Hebei', 18 : 'Henan', 19 : 'Zhejiang', 20 : 'Hainan', 21 : 'Hubei', 22 : 'Hunan', 23 : 'Gansu',
24 : 'Fujian', 25 : 'XIzang', 26 : 'Guizhou', 27 : 'Liangning', 28 : 'Chongqing', 29 : 'Shaanxi', 30 : 'Qinghai', 31 : 'Heilongjiang'})
happiness_involved['province'] = province_total['province']
f, ax = plt.subplots(figsize=(6, 15))
sns.set_color_codes("pastel")
sns.barplot(x="total", y="province", data=province_total,
label="Total", color="b")
sns.set_color_codes("muted")
sns.barplot(x="involved", y="province", data=happiness_involved,
label="Alcohol-involved", color="b")
ax.legend(ncol=2, loc="lower right", frameon=True)
ax.set(ylabel="", xlabel="Happiness of every province")
sns.despine(left=True, bottom=True)
檢視調查物件認為的當今社會的公平度中的幸福人數佔比的直方圖,多數調查物件認為當今社會是出於一個比較公平的,但仍有近半數人認為不算太公平。
sns.set_theme(style="ticks")
f, ax = plt.subplots(figsize=(7, 5))
sns.despine(f)
sns.histplot(
data, hue='happiness',
x="equity",
multiple="stack",
palette="light:m_r",
edgecolor=".3",
linewidth=.5
)
<AxesSubplot:xlabel='equity', ylabel='Count'>
根據多變數的散點圖,幸福度高的人的都均勻地分佈在了不同身高、體重的地方;體形沒有太大地影響幸福度。
sns.set_theme(style="white")
sns.relplot(x="height_cm", y="weight_jin", hue="happiness", size="health",
alpha=.5, palette="muted", data=data)
<seaborn.axisgrid.FacetGrid at 0x2512b5a2ca0>
繪製一個帶有誤差帶的直線圖,橫軸表示幸福度的提升,縱軸表示期待的年收入的提升,可以看出,在幸福度比較低的人期待的年收入通常會很高並帶有非常大的誤差,隨著幸福度的提升每個人期待的年收入也沒有變得更高,並且隨之誤差帶也變小了。
sns.set_theme(style="ticks")
palette = sns.color_palette("rocket_r")
sns.relplot(
data=data,
x="happiness", y="inc_exp",
kind="line", palette=palette,
aspect=.75, facet_kws=dict(sharex=False)
)
<seaborn.axisgrid.FacetGrid at 0x2512b5e8d90>
模型建立
XGBoost 模型介紹
XGBoost 是一個具有高效、靈活和可移植性的經過優化的分散式 梯度提升 庫。它的實現是基於機器學習演算法梯度提升框架。XGBoost 提供了並行的提升樹(例如GBDT、GBM)以一個非常快速並且精準的方法解決了許多的資料科學問題。相同的程式碼可以執行在主流的分散式環境(如Hadoop、SGE、MPI)並且可以處理數十億的樣本。
XGBoost代表了極端梯度提升(Extreme Gradient Boosting)。
整合決策樹
首先了解XGBoost的模型選擇:整合決策樹。樹的整合模型是由CART(classification and regression trees)的集合組成。下面一張圖簡單說明了一個CART分出某個人是否喜歡玩電腦遊戲的例子。
將每個家庭成員分到不同的葉子結點上,並賦給他們一個分數,每一個葉結點對應了一個分數。CART與決策樹是略有不同的,決策樹中每個葉結點只包含了一個決策值。在CART上,真實的分數是與葉結點關聯的,可以給出比分類更豐富的解釋。這也允許了更具有原則、更一致性的優化方法。
通常,在實踐中一個單獨的樹是不夠強大的。實際上使用的是整合模型,將多個樹的預測結果彙總到一起。
上圖中是一個由兩棵樹整合在一起的例子。每一個樹的預測分數被加到一起得到最終的分數。一個重要的因素是兩棵樹努力補足彼此。可以寫出模型:
其中,\(K\) 是樹的數量,\(f\) 是一個在函式空間 \(\mathcal{F}\) 的函式,並且 \(\mathcal{F}\) 是一個所有可能的CART的集合。可被優化的目標函式為:
隨機森林和提升樹實際上都是相同的模型;不同之處是如何去訓練它們。如果需要一個用來預測的整合樹,只需要寫出一個並其可以工作在隨機森林和提升樹上。
提升樹
正如同所有的監督學習一樣,想要訓練樹就要先定義目標函式並優化它。
一個目標函式要總是包含訓練的損失度和正則化項。
加性訓練
樹需要訓練的引數有 \(f_i\) 每一個都包含了樹的結構和葉結點的得分。訓練樹的結構是比傳統的可以直接採用梯度的優化問題更難。一次性訓練並學習到所有的樹是非常棘手的。相反地,可以採取一個附加的策略,修正已經學習到的,同時增加一課新樹。可以寫出在第 \(t\) 步的預測值 \(\hat{y}_i^\left(t\right )\)
在每一步需要什麼的樹,增加一棵樹,優化目標函式。
如果考慮使用均方誤差(MSE)作為損失函式,目標函式將會變成:
MSE的形式是非常優雅的,其中有一個一階項(通常稱作殘差)和一個二階項。對於其它的損失函式(例如logistic的損失函式)而言,是沒有那麼輕易就可以得到如此優雅的形式。因此,通常會使用泰勒公式損失函式展開到二階項:
泰勒公式:函式 \(f(x)\) 在開區間 \((a,b)\) 上具有 \((n+1)\) 階導數,對於任一 \(x\in(a,b)\) 有
其中,\(g_i\) 和 \(h_i\) 被定義為:
移除所有的常量,在第 \(t\) 步的目標函式就成了:
這就成了對於一顆新樹的優化目標。一個非常重要的優勢就是這個定義的目標函式的值只依賴於 \(g_i\) 和 \(h_i\) 這正是XGBoost支援自定義損失函式。可以優化各種損失函式,包括邏輯迴歸和成對排名(pairwise ranking),使用 \(g_i\) 和 \(h_i\) 作為輸入的完全相同的求解器求解。
模型複雜度
定義樹的複雜度 \(\Omega(f)\) 。首先提煉出樹的定義 \(f(x)\) 為:
其中 \(w\) 是葉結點上的得分向量,\(q\) 是一個將每一個資料點分配到對應的葉結點上的函式,\(T\) 是葉結點的數量。在XGBoost中,定義複雜度為:
有不止一個方法定義複雜度,但是這種方式在實踐中可以表現的很好。正則化項是大多數樹包都會被忽略的一部分。這是因為傳統的樹學習的對待僅僅強調改善雜質,模型的複雜度的控制留給了啟發式。通過正式的定義它,可以更好的理解模型並使模型的表現更具有泛化能力。
樹的結構分數
通過對樹模型的目標函式的推導,可以得到在第 \(t\) 步的樹的目標值:
其中 \(I_j=\{i|q(x_i)=j\}\) 是第 \(i\) 個資料點被分配到第 \(j\) 個葉結點上的下標集合。改變了其累加的索引,因為被分配到相同的葉結點上的資料點得到的分數是統一的。進一步壓縮表達令 \(G_j=\sum_{i\in I_j}g_i\) 和 \(H_j=\sum_{i\in I_j}h_i\)
其中,\(w_j\) 是彼此獨立的,式子 \(G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2\) 是二次的,並且對於給定的結構 \(q(x)\) 最好的 \(w_j\) 和可以得到的最佳的目標規約為:
此公式衡量了一棵樹的結構 \(q(x)\) 有多好。
基本上,對於一顆給定的樹結構,將統計量 \(g_i\) 和 \(h_i\) 推到它們所屬的葉結點上,並將它們累加到一起,使用公式計算衡量這棵樹多好。這個分數類似於決策樹中的不純度度量(impurity measure),區別之處在於它還將模型複雜度考慮在內。
學習樹的結構
現在已經有了衡量一棵樹好壞的指標,一個典型的想法是列舉所有可能的樹並從中挑出最好的一個。實際上這是非常棘手的,所以應該嘗試一次優化樹的一個級別。具體來說,是將一個子結點分割成兩個葉結點,得分增益為:
這個公式可以被分解為幾個部分,一部分是在新左子結點的得分,第二部分是在新右子結點上的得分,第三部分是原先葉結點上的得分,第四部分是在新葉結點上的正則化項。可以看到非常重要的因素是,如果增益小於 \(\gamma\) 更好的選擇是不去分割出一個新分支。這就是基本的樹模型的剪枝(pruning)技術。
對於實際中的資料,通常想要搜尋一個最優的分割點。一個高效率的做法是,將所有的例項(記錄)排好序,如下圖示。
從左到右掃描計算所有分割方案的結構分數是非常高效的,並且可以快速地找出最優的分割點。
加性數訓練的限制
因為將所有可能的樹結構列舉出來是非常棘手的,所以每次增加一個分割點(split)。這個方法在大多數情況下執行的很好,但是有一些邊緣案例導致這個方法失效。對於退化模型的訓練結果,每次僅僅考慮一個特徵維度。參考Can Gradient Boosting Learn Simple Arithmetic?
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
train = pd.read_csv('./data/happiness_train_complete_nona.csv', index_col='id', parse_dates=['survey_time'])
test = pd.read_csv('./data/happiness_test_complete_nona.csv', index_col='id', parse_dates=['survey_time'])
submit = pd.read_csv('./data/happiness_submit.csv', index_col='id')
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
X = train.drop(['happiness', 'survey_time'], axis=1)
y = train['happiness']
X_train, X_test, y_train, y_test = train_test_split(X, y)
from xgboost import XGBRegressor
from xgboost import plot_importance
model = XGBRegressor(gamma=0.1, learning_rate=0.1)
model.fit(X_train, y_train)
mean_squared_error(y_test, model.predict(X_test))
0.4596381608913307
predict = pd.DataFrame({'happiness' : model.predict(test.drop('survey_time', axis=1))}, index=test.index)
submit.loc[predict.index, 'happiness'] = predict['happiness']
submit.to_csv('./data/predict.csv')