数据可视化
import pandas as pd
import matplotlib.pyplot as plt
#显示所有列
pd.set_option('display.max_columns', None)
#显示所有行
pd.set_option('display.max_rows', None)
1
2
3
4
5
6
complete文件为变量完整版数据,abbr文件为变量精简版数据。
index文件中包含每个变量对应的问卷题目,以及变量取值的含义。
survey文件是数据源的原版问卷,作为补充以方便理解问题背景。
train = pd.read_csv('happiness_train_complete.csv', encoding = 'ansi')
test = pd.read_csv('happiness_test_complete.csv', encoding = 'ansi')
train_abbr = pd.read_csv('happiness_train_abbr.csv', encoding = 'ansi')
test_sub=pd.read_csv('happiness_submit.csv', encoding = 'ansi')
1
2
3
4
我们这里读取详细的train和test以及简略版的train和提交成绩的文件
train_abbr.shape, train.shape
1
精简版提供的feature有42个,而详细的有140个,当然了,精简版只是为了更好地理解问卷中主要涉及的问题方面,这里我就直接从详细版下手,一步步分析
train.head()
1
数据有很多,一眼看去就有不重要的id(可删),非数字类型的time等一些待处理的数据
test.head()
1
测试数据就是缺少了happiness的label,这也是我们需要预测的目标
import seaborn as sns
sns.countplot(x = 'happiness', data = train)
1
2
我们这里将happiness的分布情况画出来
幸福指数高的还是比较多啊,1、2、3、4、5是幸福等级,-8则是填了无法回答,那我们默认其为3类,尽可能不影响大局
label = train['happiness']
label = label.map(lambda x:3 if x==-8 else x)
train.drop(['happiness'], axis = 1, inplace = True)
data = pd.concat((train, test), axis = 0)
1
2
3
4
我们这里将-8替换为3,并且将label单独提取出来,方便之后输入模型作为y,同时将train和test整合到一起,方便数据的共同处理
sns.countplot(x = 'nationality', data = data)
1
观察民族的分布,极为不均匀,这应该也是正常现象,毕竟少数民族占比不大,我们就不再处理
sns.countplot(x = 'religion', data = data)
1
宗教分布,不信仰宗教的占大多数
sns.countplot(x = 'political', data = data)
1
政治面貌群众最多,其次就是共产党员
sns.countplot(x='property_1', data = data)
1
房子是否属于自己,5000左右的人的房子还是属于自己的,剩下一半多房子属于父母、配偶等其他人
sns.countplot(x = 'invest_0', data = data)
1
家庭投资情况,绝大部分的人都没有投资行为,所以这里决定没有参考价值
数据处理
这一部分针对缺失数据,或者是-8类型的数据进行处理
isnull = data.isnull().sum().sort_values(ascending = False)#统计data中每个feature缺失值的总数,并以此排序
percent = (data.isnull().sum()/data.isnull().count()).sort_values(ascending=False)#计算缺失数目占总数的百分比
mis_data = pd.concat([isnull, percent], axis = 1, keys = ['isnull', 'percent'])#将两项结合组成dataframe
mis_data[mis_data['percent']>0]#只有缺失占比大于零才是缺失
1
2
3
4
统计缺省值
可以看到有很多feature都存在缺失,其中不乏有几乎全部缺失的,我们应当舍弃掉那些缺失严重已经不具备太大意义的值
data.drop(['edu_other','invest_other','property_other','join_party',
's_work_type','s_work_status','work_status',
'work_yr','work_manage','work_type'], axis = 1, inplace = True)
1
2
3
同时我们应当发现,上面缺失的数据存在着分界,从work_type开始,往上都是过半缺失,往下都是四分之一或以下,所以我们选择直接剔除掉大于0.5的feature
data["edu_yr"] = data["edu_yr"].fillna(-2)#最高学历的年份,查看数据,只有具体,-2和nan,所以nan填充为-2
data["edu_status"] = data["edu_status"].fillna(0)#填充0表示未受过教育
data["marital_1st"] = data["marital_1st"].fillna(-2)#第一次结婚时间
data["marital_now"] = data["marital_now"].fillna(-2)#目前结婚时间填充-2
data['s_political'] = data['s_political'].fillna(0)
data['s_hukou'] = data['s_hukou'].fillna(0)
data['s_income'] = data['s_income'].fillna(-2)
data['s_birth'] = data['s_birth'].fillna(0)
data['s_edu'] = data['s_edu'].fillna(0)
data['s_work_exper'] = data['s_work_exper'].fillna(0)#上述因结婚原因造成的数据缺失,要么使用-2,要么使用0
data['social_friend'] = data['social_friend'].fillna(7)
data['social_neighbor'] = data['social_neighbor'].fillna(7)#社交不积极填充7,越多表示越不频繁
data['minor_child'] = data['minor_child'].fillna(0)#子女个数,缺失认为没有
data["family_income"] = data["family_income"].fillna(50000.0)#家庭收入填充众数
data["hukou_loc"] = data["hukou_loc"].fillna(1)#户口所在地默认为本乡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对存在少量缺省值的feature进行填充
#查看非数字类型的feature
for col in data.columns:
if data[col][1].dtype == 'object':
print(col)
1
2
3
4
时间应分割转化成年月日等类型,可表示为数字
#将问卷当前时间分成各个属性
from datetime import datetime
data['survey_time'] = pd.to_datetime(data['survey_time'], format = '%Y-%m-%d %H:%M:%S')
data["weekday"] = data["survey_time"].dt.weekday
data["year"] = data["survey_time"].dt.year
data["quarter"] = data["survey_time"].dt.quarter
data["hour"] = data["survey_time"].dt.hour
data["month"] = data["survey_time"].dt.month
1
2
3
4
5
6
7
8
#把一天的时间分段
def hour_cut(x):
if 0<=x<6:
return 0
elif 6<=x<8:
return 1
elif 8<=x<12:
return 2
elif 12<=x<14:
return 3
elif 14<=x<18:
return 4
elif 18<=x<21:
return 5
elif 21<=x<24:
return 6
data["hour_cut"] = data["hour"].map(hour_cut)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#出生的年代
def birth_split(x):
if 1920<=x<=1930:
return 0
elif 1930<x<=1940:
return 1
elif 1940<x<=1950:
return 2
elif 1950<x<=1960:
return 3
elif 1960<x<=1970:
return 4
elif 1970<x<=1980:
return 5
elif 1980<x<=1990:
return 6
elif 1990<x<=2000:
return 7
data["birth_s"]=data["birth"].map(birth_split)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#收入分组
def income_cut(x):
if x<0:
return 0
elif 0<=x<1200:
return 1
elif 1200<x<=10000:
return 2
elif 10000<x<24000:
return 3
elif 24000<x<40000:
return 4
elif 40000<=x:
return 5
data["income_cut"] = data["income"].map(income_cut)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#做问卷时候的年龄
data["survey_age"] = data["year"] - data["birth"]
1
2
#去除无关因素
data = data.drop(["id", "hour", "birth", "income", 'survey_time'], axis = 1)
1
2
这一部分还可以进行上面提到的-8替换,可以选择对缺省值大的数据但是参考价值大的进行保留和填充,另外还可以对一些范围较大的feature进行缩略分段,如小学初中及以下归类到低等教育,这里举出一个例子
data['edu'] = data['edu'].map(lambda x:0 if x==-8 else x)
def edu_split(x):
if x in [1,2,14]:
return 0
elif x in [3]:
return 1
elif x in [4]:
return 2
elif x in [5,7,8]:
return 3
elif x in [6]:
return 4
elif x in [9,10]:
return 5
elif x in [11,12]:
return 6
elif x in[13]:
return 7
data["edu"] = data["edu"].map(edu_split)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
模型+训练
这一部分参考 天池上某位大佬的代码
X_train = data[:train.shape[0]]
X_test = data[train.shape[0]:]
1
2
首先划分训练集和测试集
#让label从0开始
label = label.map(lambda x:x-1)
1
2
import numpy as np
X_train = np.array(X_train)
y_train = np.array(label)
X_test = np.array(X_test)
1
2
3
4
数据类型转化
#自定义评价函数
def myFeval(preds, xgbtrain):
label = xgbtrain.get_label()
score = mean_squared_error(label,preds)
return 'myFeval',score
1
2
3
4
5
xgb_params = {"booster":'gbtree','eta': 0.005, 'max_depth': 5, 'subsample': 0.7,
'colsample_bytree': 0.8, 'objective': 'reg:linear', 'eval_metric': 'rmse', 'silent': True, 'nthread': 8}
1
2
参数设置,这里提供xgb模型简略介绍
folds = KFold(n_splits = 5, shuffle = True, random_state = 2021)
1
使用KFold做五次交叉验证
最终结果展示,评价指数使用rmse和mse,提交到天池上成绩为0.48502,虽然并不能排上名次,但是能够对这一阶段所学到的知识进行实际使用,还是感觉收获颇丰