Loading

基于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

output_15_1

进一步查看取值为 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_exper1 的记录存在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

output_26_1

查看 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 特征,可以检查这个特征缺失的记录另外两项特征 sondaughter 分别表示儿子、女儿的数量,如果为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

output_35_2

可以看到输出结果表明对于 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

output_37_1

根据输出可以看到,marital 取值为 167 分别表示 未婚、离婚和丧偶,所以 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

output_41_1

根据输出可以得到 12 表示没有结婚的情况,所以缺失属于正常;

对于 367 分别表示 初婚有配偶、离婚、丧偶;只有 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

output_46_1

可以看到对于 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

可以看到 edu14 的记录中,有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

output_57_1

可以看到对于 edu_status 缺失的记录,其对应的 edu 教育程度为别为没有受过任何教育、私塾、扫盲班和小学;对于取值为 12 的情况,属于跳问选项,对应的 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

output_61_1

根据直方图看到,有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_neighborsocial_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

output_67_1

可以发现所有的 social_neighborsocial_friend 缺失记录的 socialize 即 是否经常在空闲时间做社交的事情全部均为 1 即 从不社交,所以两个特征的缺失值可以使用 1 填充。

data_origin['social_neighbor'].fillna(1, inplace=True)
data_origin['social_friend'].fillna(1, inplace=True)

对于 s_edus_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

output_71_1

可以发现 s_edu 缺失的记录的婚姻情况全部均为未婚、离婚或丧偶,均属于没有配偶或同居伴侣的情况,所以属于正常的缺失。

对于 s_politicals_work_exper 全部均属于上述情况。

对于 s_work_status 即 配偶或同居伴侣目前的工作状况,首先查看调查问卷。

图片

可以得知只有 s_work_exper 填写了 1 的情况下才应该填写 s_work_statuss_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

output_73_1

查看得知 s_work_exper1 的记录只有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_otherproperty_otherinvest_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'>

output_100_1

data_nona.drop(data_nona[data_nona['house'] > 25].index, inplace=True)
sns.boxplot(x=data_nona['family_m'])
<AxesSubplot:xlabel='family_m'>

output_102_1

data_nona.drop(data_nona[data_nona['family_m'] > 40].index, inplace=True)
sns.boxplot(x=data_nona['inc_exp'])
<AxesSubplot:xlabel='inc_exp'>

output_104_1

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'>

output_107_1

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>

output_129_1

查看每个人的收入和幸福度的散点图,通过散点图可以看出随着收入的提高,大多数点都落在了较高的幸福程度上;即使如此,也会发现存在一些收入非常高的人也处在一个说不上幸福不幸福的程度。

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'>

output_131_1

查看性别男女的幸福程度的分布直方图,在性别特征上没有过多的类别不平衡情况。

sns.set_theme(style="darkgrid")
sns.displot(
    data, x="happiness", col="gender",
    facet_kws=dict(margin_titles=True)
)
<seaborn.axisgrid.FacetGrid at 0x25128b8e1f0>

output_133_1

通过直线图,可以看出,随着 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>

output_135_0

查看每个幸福程度的出生日期,可以看出,不同幸福程度的年代的人分布都是大同小异的。

sns.set_theme(style="ticks", palette="pastel")
sns.boxplot(x="happiness", y="birth",
            data=data)
sns.despine(offset=10, trim=True)

output_135_0

将记录分为是否信仰宗教信仰,查看幸福度和健康状况的分裂小提琴图,也可以看出一个趋势,幸福度高的人大多数都分布在较高的健康状况上,而且也可以看出一个现象,随着健康状况和幸福度的提高,信仰宗教信仰的人数也慢慢增加。

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)

output_137_0

绘制一个多变量分布直方图,可以看出大多数比较幸福的人,房产的数量也不会大幅增加。

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>

output_139_1

绘制幸福度和住房建筑面积的核密度估计图,可以看出同样的现象,多数比较幸福的人的房屋建筑面积也不会集中在很高的一个水平,但是也会有一个随着房屋建筑面积的增加幸福度也增加的现象。

sns.set_theme(style="ticks")
g = sns.jointplot(
    data=data[data['floor_area'] < 600],
    x="happiness", y="floor_area",
    kind="kde",
)

output_141_0

查看各个特征的热力图,可以根据图中的颜色深度看出两两特征之间的相关性的高低。

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")

output_143_0

查看全国省会城市的幸福人数的占比条形图,通过图中可以看出,湖北省调查人数最多但幸福人数不算高;河南省和山东省的幸福人数的占比非常之高;即使内蒙古自治区的调查人数最少,但是幸福人数的占比却是非常高的。

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)

output_145_0

查看调查对象认为的当今社会的公平度中的幸福人数占比的直方图,多数调查对象认为当今社会是出于一个比较公平的,但仍有近半数人认为不算太公平。

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'>

output_147_1

根据多变量的散点图,幸福度高的人的都均匀地分布在了不同身高、体重的地方;体形没有太大地影响幸福度。

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>

output_149_1

绘制一个带有误差带的直线图,横轴表示幸福度的提升,纵轴表示期待的年收入的提升,可以看出,在幸福度比较低的人期待的年收入通常会很高并带有非常大的误差,随着幸福度的提升每个人期待的年收入也没有变得更高,并且随之误差带也变小了。

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>

output_151_1


模型建立

XGBoost 模型介绍

XGBoost 是一个具有高效、灵活和可移植性的经过优化的分布式 梯度提升 库。它的实现是基于机器学习算法梯度提升框架。XGBoost 提供了并行的提升树(例如GBDT、GBM)以一个非常快速并且精准的方法解决了许多的数据科学问题。相同的代码可以运行在主流的分布式环境(如Hadoop、SGE、MPI)并且可以处理数十亿的样本。

XGBoost代表了极端梯度提升(Extreme Gradient Boosting)。

集成决策树

首先了解XGBoost的模型选择:集成决策树。树的集成模型是由CART(classification and regression trees)的集合组成。下面一张图简单说明了一个CART分出某个人是否喜欢玩电脑游戏的例子。

图片

将每个家庭成员分到不同的叶子结点上,并赋给他们一个分数,每一个叶结点对应了一个分数。CART与决策树是略有不同的,决策树中每个叶结点只包含了一个决策值。在CART上,真实的分数是与叶结点关联的,可以给出比分类更丰富的解释。这也允许了更具有原则、更一致性的优化方法。

通常,在实践中一个单独的树是不够强大的。实际上使用的是集成模型,将多个树的预测结果汇总到一起。

图片

上图中是一个由两棵树集成在一起的例子。每一个树的预测分数被加到一起得到最终的分数。一个重要的因素是两棵树努力补足彼此。可以写出模型:

\[\hat{y}_i=\sum_{k=1}^Kf_k\left (x_i\right ),f_k\in\mathcal{F} \]

其中,\(K\) 是树的数量,\(f\) 是一个在函数空间 \(\mathcal{F}\) 的函数,并且 \(\mathcal{F}\) 是一个所有可能的CART的集合。可被优化的目标函数为:

\[\mathit{obj}\left (\theta\right )=\sum_i^n\ell\left (y_i,\hat{y}_i\right )+\sum_{k=1}^K\Omega\left (f_k\right ) \]

随机森林和提升树实际上都是相同的模型;不同之处是如何去训练它们。如果需要一个用来预测的集成树,只需要写出一个并其可以工作在随机森林和提升树上。

提升树

正如同所有的监督学习一样,想要训练树就要先定义目标函数并优化它。

一个目标函数要总是包含训练的损失度和正则化项。

\[\mathit{obj}=\sum_i^n\ell\left (y_i,\hat{y}_i\right )+\sum_{k=1}^K\Omega\left (f_k\right ) \]

加性训练

树需要训练的参数有 \(f_i\) 每一个都包含了树的结构和叶结点的得分。训练树的结构是比传统的可以直接采用梯度的优化问题更难。一次性训练并学习到所有的树是非常棘手的。相反地,可以采取一个附加的策略,修正已经学习到的,同时增加一课新树。可以写出在第 \(t\) 步的预测值 \(\hat{y}_i^\left(t\right )\)

\[\begin{split} \hat{y}_i^{\left (0\right )}&=0\\ \hat{y}_i^{\left (1\right )}&=f_1\left (x_i\right )=\hat{y}_i^{\left (0\right )}+f_1\left (x_i\right )\\ \hat{y}_i^{\left (2\right )}&=f_1\left (x_i\right )+f_2\left (x_i\right )=\hat{y}_i^{\left (1\right )}+f_2\left (x_i\right )\\ &\dots\\ \hat{y}_i^{\left (t\right )}&=\sum_{k=1}^Kf_k\left (x_i\right )=\hat{y}_i^{\left (t-1\right )}+f_t\left (x_i\right )\\ \end{split} \]

在每一步需要什么的树,增加一棵树,优化目标函数。

\[\begin{split} \mathit{obj}^{(t)}&=\sum_{i=1}^n\ell(y_i,\hat{y}_i^{(t)})+\sum_{i=1}^t\Omega(f_i)\\ &=\sum_{i=1}^n\ell(y_i,\hat{y}^{(t-1)}_i+f_t(x_i))+\Omega(f_t)+C \end{split} \]

如果考虑使用均方误差(MSE)作为损失函数,目标函数将会变成:

\[\begin{split} \mathit{obj}^{(t)}&=\sum_{i=1}^n\ell(y_i,\hat{y}^{(t-1)}_i+f_t(x_i))+\Omega(f_t)+C\\ &=\sum_{i=1}^n(y_i-(\hat{y}_i^{(t-1)}+f_t(x_i)))^2+\Omega(f_t)+C\\ &=\sum_{i=1}^n((y_i-\hat{y}_i^{(t-1)})-f_t(x_i))^2+\Omega(f_t)+C\\ &=\sum_{i=1}^n((y_i-\hat{y}_i^{(t-1)})^2-2(y_i-\hat{y}_i^{(t-1)})f_t(x_i)+f_t(x_i)^2)+\Omega(f_t)+C\\ &=\sum_{i=1}^n(-2(y_i-\hat{y}_i^{(t-1)})f_t(x_i)+f_t(x_i)^2)+\Omega(f_t)+C\\ \end{split} \]

MSE的形式是非常优雅的,其中有一个一阶项(通常称作残差)和一个二阶项。对于其它的损失函数(例如logistic的损失函数)而言,是没有那么轻易就可以得到如此优雅的形式。因此,通常会使用泰勒公式损失函数展开到二阶项:

泰勒公式:函数 \(f(x)\) 在开区间 \((a,b)\) 上具有 \((n+1)\) 阶导数,对于任一 \(x\in(a,b)\)

\[f(x)=\frac{f(x_0)}{0!}+\frac{f'(x_0)}{1!}(x-x_0)+\frac{f''(x_0)}{2!}(x-x_0)^2+\dots+\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n+R_n(x) \]

\[\begin{split} \mathit{obj}^{(t)}&=\sum_{i=1}^n\ell(y_i,\hat{y}^{(t-1)}_i+f_t(x_i))+\Omega(f_t)+C\\ &=\sum_{i=1}^n[\frac{\ell(y_i,\hat{y}^{(t-1)}_i)}{0!}+\frac{\ell'(y_i,\hat{y}^{(t-1)}_i)}{1!}(\hat{y}^{(t)}_i-\hat{y}^{(t-1)}_i)+\frac{\ell''(y_i,\hat{y}^{(t-1)}_i)}{2!}(\hat{y}^{(t)}_i-\hat{y}^{(t-1)}_i)^2]+\Omega(f_t)+C\\ &=\sum_{i=1}^n[\ell(y_i,\hat{y}^{(t-1)}_i)+\ell'(y_i,\hat{y}^{(t-1)}_i)f_t(x_i)+\frac{1}{2}\ell''(y_i,\hat{y}^{(t-1)}_i)f_t(x_i)^2]+\Omega(f_t)+C\\ &=\sum_{i=1}^n[\ell(y_i,\hat{y}^{(t-1)}_i)+g_if_t(x_i)+\frac{1}{2}h_if_t(x_i)^2]+\Omega(f_t)+C\\ \end{split} \]

其中,\(g_i\)\(h_i\) 被定义为:

\[\begin{split} g_i&=\partial_{\hat{y}_i^{(t-1)}}\ell(y_i,\hat{y}^{(t-1)}_i)\\ h_i&=\partial^2_{\hat{y}^{(t-1)}_i}\ell(y_i,\hat{y}^{(t-1)}_i) \end{split} \]

移除所有的常量,在第 \(t\) 步的目标函数就成了:

\[\sum_{i=1}^n[g_if_t(x_i)+\frac{1}{2}h_if_t(x_i)^2]+\Omega(f_t) \]

这就成了对于一颗新树的优化目标。一个非常重要的优势就是这个定义的目标函数的值只依赖于 \(g_i\)\(h_i\) 这正是XGBoost支持自定义损失函数。可以优化各种损失函数,包括逻辑回归和成对排名(pairwise ranking),使用 \(g_i\)\(h_i\) 作为输入的完全相同的求解器求解。

模型复杂度

定义树的复杂度 \(\Omega(f)\) 。首先提炼出树的定义 \(f(x)\) 为:

\[f_t(x)=w_{q(x)},w\in\mathbb{R}^T,q:\mathbb{R}^d\rightarrow \{1,2,\dots,T\}. \]

其中 \(w\) 是叶结点上的得分向量,\(q\) 是一个将每一个数据点分配到对应的叶结点上的函数,\(T\) 是叶结点的数量。在XGBoost中,定义复杂度为:

\[\Omega(f)=\gamma T+\frac{1}{2}\lambda\sum_{j=1}^Tw_j^2 \]

有不止一个方法定义复杂度,但是这种方式在实践中可以表现的很好。正则化项是大多数树包都会被忽略的一部分。这是因为传统的树学习的对待仅仅强调改善杂质,模型的复杂度的控制留给了启发式。通过正式的定义它,可以更好的理解模型并使模型的表现更具有泛化能力。

树的结构分数

通过对树模型的目标函数的推导,可以得到在第 \(t\) 步的树的目标值:

\[\begin{split} \mathit{obj}^{(t)}&\approx\sum_{i=1}^n[g_iw_{q(x_i)}+\frac{1}{2}h_iw^2_{q(x_i)}]+\gamma T+\frac{1}{2}\lambda\sum_{j=1}^Tw_j^2\\ &=[g_1w_{q(x_1)}+\frac{1}{2}h_1w^2_{q(x_1)}+g_2w_{q(x_2)}+\frac{1}{2}h_2w^2_{q(x_2)}+\dots+g_nw_{q(x_n)}+\frac{1}{2}h_nw^2_{q(x_n)}]+\gamma T+\frac{1}{2}\lambda\sum_{j=1}^Tw_j^2\\ &=\sum_{j=1}^T[(\sum_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum_{i\in I_j}h_i)w^2_j]+\gamma T+\frac{1}{2}\lambda\sum_{j=1}^Tw_j^2\\ &=\sum_{j=1}^T[(\sum_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum_{i\in I_j}h_i)w^2_j+\frac{1}{2}\lambda w_j^2]+\gamma T\\ &=\sum_{j=1}^T[(\sum_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum_{i\in I_j}h_i+\lambda)w^2_j]+\gamma T\\ \end{split} \]

其中 \(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\)

\[\mathit{obj}^{(t)}=\sum_{j=1}^T[G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2]+\gamma T \]

其中,\(w_j\) 是彼此独立的,式子 \(G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2\) 是二次的,并且对于给定的结构 \(q(x)\) 最好的 \(w_j\) 和可以得到的最佳的目标规约为:

\[\begin{split} w_j\ast&=-\frac{G_j}{H_j+\lambda}\\ \mathit{obj}\ast&=-\frac{1}{2}\sum_{j=1}^T\frac{G_j^2}{H_j+\lambda}+\gamma T \end{split} \]

此公式衡量了一棵树的结构 \(q(x)\) 有多好。

图片

基本上,对于一颗给定的树结构,将统计量 \(g_i\)\(h_i\) 推到它们所属的叶结点上,并将它们累加到一起,使用公式计算衡量这棵树多好。这个分数类似于决策树中的不纯度度量(impurity measure),区别之处在于它还将模型复杂度考虑在内。

学习树的结构

现在已经有了衡量一棵树好坏的指标,一个典型的想法是枚举所有可能的树并从中挑出最好的一个。实际上这是非常棘手的,所以应该尝试一次优化树的一个级别。具体来说,是将一个子结点分割成两个叶结点,得分增益为:

\[\mathit{Gain}=\frac{1}{2}\left [\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right]-\gamma \]

这个公式可以被分解为几个部分,一部分是在新左子结点的得分,第二部分是在新右子结点上的得分,第三部分是原先叶结点上的得分,第四部分是在新叶结点上的正则化项。可以看到非常重要的因素是,如果增益小于 \(\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')
posted @ 2020-12-20 18:59  极客锋行  阅读(1095)  评论(1编辑  收藏  举报