基于XGBoost模型的幸福度预测——阿里天池学习赛
本文根据阿里天池学习赛《快来一起挖掘幸福感!》撰写
加载数据
加载的是完整版的数据 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
from xgboost import plot_importance
fig, ax = plt.subplots(figsize=(15, 60))
plot_importance(model, ax=ax, height=0.8)`
训练完XGBoost模型,绘制XGBoost模型训练好的特征重要性( feature importance )条形图,查看哪些特征对幸福度(happiness)特征重要。
所有特征中,重要性最高的特征是province即采访地点:省/自治区/直辖市编码,也就是说明,生活在哪个城市对幸福程度是最重要的。这首先在于,由于快速的城市化进程,我国城市的数量和规模日益增加。在未来的20年中,中国将进一步加速城市化进程。这在最直观的角度说明了“城市”这一特殊的经济体(区域)的重要性。其次,在城市经济学理论中,城市既是各种活动的聚集地,更是有着一定内聚力(聚集经济、规模经济)的经济形态。城市的各种条件、活动无疑将对人们的幸福起到更举足轻重的作用。
其次,equity特征即填写问卷对象认为当今社会公不公平的程度,也对幸福度有着很大的重要性。国家治理质量的提高对人们的幸福感则具有决定性的意义。幸福感在很大程度上取决于横向的比较,所以不公平会成为人们不幸福的根本原因。这也印证了《论语》里的老话,“不患寡而患不均”。
class_10_after即调查对象认为他们10年后将会在哪个等级上,也就是对未来的定位和奋斗目标,对当前的幸福感也占了很大的比重。每个人每天生活的是否幸福也取决于人们每天与街坊邻居相处的是否融洽,所以neighbor_familiarity也占着很大的比重。
从直观来说,排除去主要的影响幸福感的因素之后,每个人的幸福感也可能源自于自己的身心健康和外貌,所以depression即感到心情抑郁或沮丧的频繁程度和health即身体健康状况也占有很大的重要性。
幸福度重要性中最少的是invest和property特征,即一个人的投资和房产的信息,也侧面说明了并不是富裕的人才会幸福贫穷的人不会感到幸福。
这也进一步说明了所有靠物质支撑的幸福感,都不能持久,都会随着物质的离去而离去。只有心灵的淡定宁静,继而产生的身心愉悦,才是幸福的真正源泉。
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')