Python大数据分析4:特征数据的处理(会员卡预测)

在上次课上,我们利用决策树进行了会员卡类型的预测。其中,我们发现不同的特征对于预测的准确度有着很大的影响。那么我们该如何选择特征呢?简单的凭借自己的认知和运气显然并不是通用的好方法。今天我们来了解一下专业的方法。

首先,我们在做数据分析前,应该对每个需要处理的数据列进行必要的了解。

常见的方法为:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\customer.csv’)

print(frame[‘yearly_income’].describe())

对于文本数据,从结果可以看出非空的数据总数(count)、数据的种类数(unique,即相同的数据算一种)、出现频次最高的数据种类(top)及其频次(freq)。

如果需要详细了解具体取值种类,可以使用unique方法:

print(frame[‘yearly_income’].unique())

而对于数值型数据,内容更为丰富。内容主要包括计数、平均值、标准差、最小值、百分位数、最大值。一般通过这些,可以大概看出数据分布是否均匀。

print(frame[‘total_children’].describe())

不过,有些数据虽然看起来是数值,但其实不能作为一般的数值来分析。比如顾客地区ID等,显然平均值等概念并无实际意义。

print(frame[‘customer_region_id’].describe())

对于此类数值,应该处理方式和文本数据一样,使用独热编码来进行转换,可以有效避免算法故作聪明的利用数据之间的大小关系来做出反而有害的计算。

其次,数据预处理。

使用原始数据并不意味着一定要原封不动的使用他们,有时进行必要的转换和创建新的数据反而更为有效,既可以消除原始数据存在问题的产生的不利影响,也可以扩展内容特征。

简单的预处理只需对数据本身做一些数值上的技术处理。

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np
from sklearn import preprocessing

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
frame['yearly_income_new'] = preprocessing.scale(frame['yearly_income'])
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income_new", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

这里,没有直接将年收入用于计算,而是首先调用preprocessing类型的scale方法,它可以进行均值移除,也就是说将各个特征的平均值调整为0,标准差调整为1。再利用这个新的年收入计算,可以发现效果略有提升。

数据预处理还可以进行了更为深入一些。

比如我们来看看年收入这个数据列。为方便处理,我们将其转换为整数,并测度方差:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\customer.csv’)

frame[‘yearly_income’] = frame[‘yearly_income’].str.split(” “).str[0].str.replace(‘[^0-9]’, ”)

frame[‘yearly_income_new’] = frame[‘yearly_income’].astype(int)

print(frame[‘yearly_income_new’].describe())

你会发现原始的年收入列方差非常大。方差大意味着数据分布非常分散,而对于会员卡等级预测而言,我们其实只有四种类别,显然过大的数据波动影响反而不利。

如何减少方差呢?我们不妨做个转换,将不同的年收入映射到几个区间中:

frame[‘yearly_income_new’] = frame[‘yearly_income_new’] // 30

print(frame[‘yearly_income_new’].describe())

这里的双斜杠表示整除,原始数据中最大最小数值差距为140,现在转换为5,方差极大的减少。

我们利用这个减少方差的新年收入来替换下过去的决策树方法:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
frame['yearly_income_new'] = frame['yearly_income'].astype(int)
frame['yearly_income_new'] = frame['yearly_income_new'] // 30
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income_new", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

你会发现提高了接近0.4个百分点。

我们再举一个增加数据列的例子。

关于时间列,该数据有两个,一个是顾客生日,一个开卡的时间,由此我们可以计算出一个开卡年龄:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\customer.csv’)

frame[‘age’] = pd.to_datetime(frame[‘date_accnt_opened’]).dt.year – pd.to_datetime(frame[‘birthdate’]).dt.year

frame[‘age’] = frame[‘age’] // 20

print(frame[‘age’].describe())

考虑到过细的年龄数据即导致很大的方差,实际意义也不大,因此我们通过整除20映射到了年龄段。

增加此数据列,观察预测结果:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['age'] = pd.to_datetime(frame['date_accnt_opened']).dt.year - pd.to_datetime(frame['birthdate']).dt.year
frame['age'] = frame['age'] // 20
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["age", "yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

实际效果并不好,当然这里我们只是演示一种创建列的可行方法。不过即使如此,我们还是可以改进。

比如我们认为中青年年龄段的消费能力最强,于是我们再次调整年龄数据列:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['age'] = pd.to_datetime(frame['date_accnt_opened']).dt.year - pd.to_datetime(frame['birthdate']).dt.year
frame['age'] = frame['age'] // 20
frame.loc[frame['age'] >= 3, 'age'] = 1
frame.loc[frame['age'] <= 1, 'age'] = 1
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["age", "yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True,  right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

这里的想法是认为过低的年龄(小于20岁)和过高的年龄(大于60岁)没有区别,消费能力都有限,于是调整为统一的年龄段特征。从结果来看,比刚才的做法要提高1个百分点。当然,这里只是演示如何通过新增列来扩展数据的方法,事实上,年龄列效果非常不好。后面会有介绍。真实的考虑往往还需要更多结合应用的特点来增加真正有价值的数据列,在一些基于时间序列的应用中还可以通过不同行之间的时序关系得到更多的特征信息,如增长率等。

第三,数据选择。一般而言,选择特征数量的增加会导致计算复杂度的指数级增长,因此比较常见的处理方法是首先尝试表现较好的单个特征,在根据情况酌情增加。

那么如何选择这些数据呢?

我们可以根据数据之间的相关性来进行选择。我们就以前面我们曾经使用过的几个数据列,比较下与会员卡列的相关性:

import pandas as pd
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.preprocessing import OneHotEncoder
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['age'] = pd.to_datetime(frame['date_accnt_opened']).dt.year - pd.to_datetime(frame['birthdate']).dt.year
frame['age'] = frame['age'] // 20
frame.loc[frame['age'] >= 3, 'age'] = 1
frame.loc[frame['age'] <= 1, 'age'] = 1
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["age", "yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
y = frame["member_card"]
transformer = SelectKBest(score_func=chi2, k='all')
Xt_chi2 = transformer.fit_transform(X, y)
print(transformer.scores_)

这里直到X的生成都是前面介绍过的方法,y为会员卡比较列,我们调用列SelectKBest类型返回最佳相关特征,chi2的参数值表示采用卡方检验,all参数值表示返回所有特征情况。通过运算后,结果可以看出,年龄字段的价值非常小,甚至起到相反的作用,而最大相关度的是年收入和汽车拥有数量。

我们还可以使用主成分分析算法来寻找最小的有效特征集合,对于相关的数据就可以无需全部利用,同时还能保证整体数据有效性不变。这里我们去除了年龄列。

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import numpy as np
from sklearn.decomposition import PCA

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
pca = PCA(n_components=2)
Xd = pca.fit_transform(X)
np.set_printoptions(precision=3, suppress=True)
print(pca.explained_variance_ratio_)
print(Xd)

我们定义了只生成两个2成分,并将原始含有6列的X特征最终转化为2列特征,其实还可以通过输出观察到2列各自的贡献度,我们以3位小数限制输出。当然,这里的2列绝不是简单的从原始6列中抽取2列,而是综合各列加权汇总而成的两个特征。

再以这两个特征,虽然特征只有2列,但是已经很取得很好的预测效果。

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import numpy as np
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
pca = PCA(n_components=2)
Xd = pca.fit_transform(X)
clf = RandomForestClassifier()
scores = cross_val_score(clf, Xd, y, scoring='accuracy')
print(np.mean(scores))

发表评论

电子邮件地址不会被公开。 必填项已用*标注