R与Python手牵手:特征工程(数值型变换)

欢迎关注天善智能,我们是专注于商业智能BI,人工智能AI,大数据分析与挖掘领域的垂直社区,学习,问答、求职一站式搞定!

对商业智能BI、大数据分析挖掘、机器学习,python,R等数据领域感兴趣的同学加微信:tstoutiao,邀请你进入数据爱好者交流群,数据爱好者们都在这儿。

作者黄天元,复旦大学博士在读,目前研究涉及文本挖掘、社交网络分析和机器学习等。希望与大家分享学习经验,推广并加深R语言在业界的应用。

邮箱:huang.tian-yuan@qq.com

经常玩数据竞赛的人几乎达成了一个共识,就是建模过程大家都大同小异,但是特征工程则至关重要。数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。这里将会介绍一些最基本的特征工程方法以及代码的实现,供大家参考。这次的数据可以在下面网站找到:

https://github.com/dipanjanS/practical-machine-learning-with-python/tree/master/notebooks/Ch04_Feature_Engineering_and_Selection/datasets


Python

基本设置

#加载包import pandas as pdimport matplotlib.pyplot as pltimport matplotlib as mplimport numpy as npimport scipy.stats as spstats#对图像输出的统一设置%matplotlib inlinempl.style.reload_library()mpl.style.use('classic')mpl.rcParams['figure.facecolor'] = (1, 1, 1, 0)mpl.rcParams['figure.figsize'] = [6.0, 4.0]mpl.rcParams['figure.dpi'] = 100file_path = "G:/Py/practical-machine-learning-with-python-master/notebooks/Ch04_Feature_Engineering_and_Selection/"

原始数据特征

看到这份数据介绍的时候我也是惊呆了,这是关于“口袋妖怪”游戏的一个数据集,上面是关于一些妖怪们的攻击力、防御力、生命值、速度之类的参数,这个动画在我那个年代叫做“宠物小精灵”...Anyway,我们这个例子要看的就是数值型变量的基本特征,我们选其中三个进行观察。

poke_df = pd.read_csv(file_path + 'datasets/Pokemon.csv',   encoding='utf-8')poke_df.head()


3901436-1a56e1bc1dc51aea

(点击放大)


poke_df[['HP', 'Attack', 'Defense']].head()


3901436-61cf01d67406d62f
poke_df[['HP', 'Attack', 'Defense']].describe()


3901436-c30bd560255fa7dd

另外,有的原始数值型变量是通过计数来表示的,比如下面例子是用户听歌的记录,其中listen_count记录的是用户听了一首歌多少次。

popsong_df = pd.read_csv(file_path + 'datasets/song_views.csv', encoding='utf-8')popsong_df.head(10)


3901436-2c397a6dd35840d2

二值化

关于听歌的案例,其实有一种粗略的理解,就是听过还是没听过,也就是不管听了多少次,只要不是没听过,就记录为1,其他是0.对于一些问题来说,听歌的绝对次数其实并不重要,比如一首歌的用户覆盖面,就不需要考虑哪些用户特别喜欢这首歌的问题。

#提取用户是否听过这首歌的特征watched = np.array(popsong_df['listen_count']) watched[watched >= 1] = 1popsong_df['watched'] = watchedpopsong_df.head(10)


3901436-64d214399b3d36c2


#sklearn用专门的函数来完成这个任务from sklearn.preprocessing import Binarizerbn = Binarizer(threshold=0.9)pd_watched = bn.transform([popsong_df['listen_count']])[0]popsong_df['pd_watched'] = pd_watchedpopsong_df.head(11)


3901436-cdd5d03ced7db49f

Binarizer函数的阈值设定含义为,小于等于阈值的值都视为0,大于阈值的则视为1.


取整

有时候数据真的不需要太高的精度,高精度的数据会占用更多的内存,因此可以取整处理。应该说这种操作肯定是会损失信息量的,但是如果在一些情况下5.9和6.3被认为没有差别的时候,取整也许更加合适。

items_popularity = pd.read_csv(file_path + 'datasets/item_popularity.csv', encoding='utf-8')items_popularity


3901436-64e4b988249e0740


这个例子中,pop_percent的数据是百分比,因此我们可以用几成或百分点为单位来表示。

items_popularity['popularity_scale_10'] = np.array(np.round((items_popularity['pop_percent'] * 10)), dtype='int')items_popularity['popularity_scale_100'] = np.array(np.round((items_popularity['pop_percent'] * 100)), dtype='int')items_popularity


3901436-3ba10a409991c548


交互项构造

如果认为一些变量的平方项更有可能与因变量成一定关系,就应该构造二次项甚至是更高次的交互项。比如,我们如果有草地的长宽,我们要知道什么对绿化面积造成影响,就应该把长宽相乘。这只是一个比喻,很多情况下我们不知道是否需要构造交互项,但是先构造然后再进行筛选也是一个不错的选择。 在实际应用中,交互项是两个变量的乘积或自身的平方项,这样我们就知道两个变量之间是否存在相互影响。

atk_def = poke_df[['Attack', 'Defense']]atk_def.head()
3901436-8ef95f312029bceb
from sklearn.preprocessing import PolynomialFeatures#构造二次交互项pf = PolynomialFeatures(degree=2, interaction_only=False, include_bias=False)res = pf.fit_transform(atk_def)res
3901436-67a16a20e5fd82c1
#加入到数据框中intr_features = pd.DataFrame(res, columns=['Attack', 'Defense', 'Attack^2', 'Attack x Defense', 'Defense^2'])intr_features.head(5)  
3901436-c2c8fe5cb4f0055c

有同学可能会问,我怎么知道列的名称呢?可以这么看:

pd.DataFrame(pf.powers_, columns=['Attack_degree', 'Defense_degree'])
3901436-92c889bb1f3bfbb1

也就是说哪个是一次项哪些是二次项哪些是交互项都可以看出来。pf自从用了fit_transform之后,就记录了这份数据构造二次项的模式,后面要从新使用就可以用transform函数了。

new_df = pd.DataFrame([[95, 75],[121, 120], [77, 60]],   columns=['Attack', 'Defense'])new_df
3901436-b62b779e1cac5e06
new_res = pf.transform(new_df)new_intr_features = pd.DataFrame(new_res,  columns=['Attack', 'Defense',   'Attack^2', 'Attack x Defense', 'Defense^2'])new_intr_features
3901436-9ce47f06e2afdf1e


分箱

本质上是根据数值,把观测进行等级分类,比如60分以下判定为不及格,90分以上为优秀,就是简单的分箱。

#读数据fcc_survey_df = pd.read_csv(file_path + 'datasets/fcc_2016_coder_survey_subset.csv', encoding='utf-8')fcc_survey_df[['ID.x', 'EmploymentField', 'Age', 'Income']].head()


3901436-ffda433594095c3e


fig, ax = plt.subplots()fcc_survey_df['Age'].hist(color='#A9C5D3')ax.set_title('Developer Age Histogram', fontsize=12)ax.set_xlabel('Age', fontsize=12)ax.set_ylabel('Frequency', fontsize=12)

得:

Text(0, 0.5, 'Frequency')

3901436-b563e741c49be5ae

根据上面的分布图,进行分箱,也就是说10到20岁统一标为1。

fcc_survey_df['Age_bin_round'] = np.array(np.floor(np.array(fcc_survey_df['Age']) / 10.))fcc_survey_df[['ID.x', 'Age', 'Age_bin_round']].iloc[1071:1076]


3901436-c75cdf0b7cd86798


或者我们可以自定义自己的分箱范围标准:

bin_ranges = [0, 15, 30, 45, 60, 75, 100]bin_names = [1, 2, 3, 4, 5, 6]#首先看每个记录落在哪一个我们定义好的范围fcc_survey_df['Age_bin_custom_range'] = pd.cut(np.array(fcc_survey_df['Age']),    bins=bin_ranges)#给我们定义好的范围进行数值编号fcc_survey_df['Age_bin_custom_label'] = pd.cut(np.array(fcc_survey_df['Age']),    bins=bin_ranges, labels=bin_names)#观察我们整理好的数据框fcc_survey_df[['ID.x', 'Age', 'Age_bin_round',    'Age_bin_custom_range', 'Age_bin_custom_label']].iloc[1071:1076]


3901436-c4cffcfe048b20b4

上面的意思是,0-15属于1级,30-45则属于3级,以此类推。

另一种分箱标准,就是采用分位数,比如四分位数。我们先看看数据,我们会对收入变量进行分箱。

fcc_survey_df[['ID.x', 'Age', 'Income']].iloc[4:9]


3901436-05396b6be94e4909


fig, ax = plt.subplots()fcc_survey_df['Income'].hist(bins=30, color='#A9C5D3')ax.set_title('Developer Income Histogram', fontsize=12)ax.set_xlabel('Developer Income', fontsize=12)ax.set_ylabel('Frequency', fontsize=12)

得:

Text(0, 0.5, 'Frequency')

3901436-4a3eed1458d523b3
#计算收入的分位数分别是多少quantile_list = [0, .25, .5, .75, 1.]quantiles = fcc_survey_df['Income'].quantile(quantile_list)quantiles


3901436-ab57a336bd6b637a
#在图中标出分位数的位置fig, ax = plt.subplots()fcc_survey_df['Income'].hist(bins=30, color='#A9C5D3')for quantile in quantiles:    qvl = plt.axvline(quantile, color='r')ax.legend([qvl], ['Quantiles'], fontsize=10)ax.set_title('Developer Income Histogram with Quantiles', fontsize=12)ax.set_xlabel('Developer Income', fontsize=12)ax.set_ylabel('Frequency', fontsize=12)

得:

Text(0, 0.5, 'Frequency')

3901436-a8f2b09e8d2bafc1
#根据分位数进行分箱特征提取quantile_labels = ['0-25Q', '25-50Q', '50-75Q', '75-100Q']fcc_survey_df['Income_quantile_range'] = pd.qcut(fcc_survey_df['Income'],                                                  q=quantile_list)fcc_survey_df['Income_quantile_label'] = pd.qcut(fcc_survey_df['Income'],                                                  q=quantile_list, labels=quantile_labels)fcc_survey_df[['ID.x', 'Age', 'Income',                'Income_quantile_range', 'Income_quantile_label']].iloc[4:9]


3901436-5b687daa90a953f9


数学变换

我们这里提到的数学变化,基本都是因为数据不服从正态性,通过变化让数据服从正态分布,这样我们才能够用一些模型进行拟合。比如线性回归就需要数据服从正态性分布,如果有偏的话结果就会不可信。这里我们介绍对数变换和Box-Cox变换。

对数变换

我们可以看到,上面一个例子中收入是不服从左右对称的正态分布的,我们用对数变换看看是否起到效果。

fcc_survey_df['Income_log'] = np.log((1+ fcc_survey_df['Income']))   #为什么加1?如果有人收入为0那么就会出问题了。fcc_survey_df[['ID.x', 'Age', 'Income', 'Income_log']].iloc[4:9]
3901436-b69898cf61fddc60


income_log_mean = np.round(np.mean(fcc_survey_df['Income_log']), 2)fig, ax = plt.subplots()fcc_survey_df['Income_log'].hist(bins=30, color='#A9C5D3')plt.axvline(income_log_mean, color='r')ax.set_title('Developer Income Histogram after Log Transform', fontsize=12)ax.set_xlabel('Developer Income (log scale)', fontsize=12)ax.set_ylabel('Frequency', fontsize=12)ax.text(11.5, 450, r'$\mu$='+str(income_log_mean), fontsize=10)

得:

Text(11.5, 450, '$\\mu$=10.43')

3901436-da0c357a2f187c51


Box-Cox变换

做这个变换之前,首先要确定一个lambda值,不过建议傻瓜式复制就好,我们的任务只是要得到能够服从正态分布的一列变量而已。

income = np.array(fcc_survey_df['Income'])income_clean = income[~np.isnan(income)]        l, opt_lambda = spstats.boxcox(income_clean)print('Optimal lambda value:', opt_lambda)

得:

Optimal lambda value: 0.11799123945557663


fcc_survey_df['Income_boxcox_lambda_0'] = spstats.boxcox((1+fcc_survey_df['Income']),                                                          lmbda=0)fcc_survey_df['Income_boxcox_lambda_opt'] = spstats.boxcox(fcc_survey_df['Income'],                                                            lmbda=opt_lambda)fcc_survey_df[['ID.x', 'Age', 'Income', 'Income_log',                'Income_boxcox_lambda_0', 'Income_boxcox_lambda_opt']].iloc[4:9]


3901436-f04a5e6c66455beb


income_boxcox_mean = np.round(np.mean(fcc_survey_df['Income_boxcox_lambda_opt']), 2)fig, ax = plt.subplots()fcc_survey_df['Income_boxcox_lambda_opt'].hist(bins=30, color='#A9C5D3')plt.axvline(income_boxcox_mean, color='r')ax.set_title('Developer Income Histogram after Box–Cox Transform', fontsize=12)ax.set_xlabel('Developer Income (Box–Cox transform)', fontsize=12)ax.set_ylabel('Frequency', fontsize=12)ax.text(24, 450, r'$\mu$='+str(income_boxcox_mean), fontsize=10)

得:

Text(24, 450, '$\\mu$=20.65')

3901436-9f64e03ce19b22ca

这里计算了lambda为0,以及为最优值的Box-Cox转换值。

3901436-2fcff65374ac53ff

R

尽量简洁地解决上面提到的问题,在R中。

#载入包pacman::p_load(tidyverse,forecast)file_path = "G:/Py/practical-machine-learning-with-python-master/notebooks/Ch04_Feature_Engineering_and_Selection/"

原始数据特征

read_csv(paste0(file_path,"datasets/Pokemon.csv")) -> poke_dfpoke_df %>% head


3901436-d5dfcb25a51a272f


poke_df %>% select(HP,Attack,Defense) %>% head


3901436-10cecd0d14159479
poke_df %>% select(HP,Attack,Defense) %>% summary


3901436-2961408bb6f03a35
read_csv(paste0(file_path,"datasets/song_views.csv")) -> popsong_dfpopsong_df %>% head(10)


3901436-d043d664274b75eb


二值化

popsong_df %>% mutate(watched = ifelse(listen_count > 0,1,0)) %>% head(10)


3901436-57f15d50262dd1a0


取整

read_csv(paste0(file_path,"datasets/item_popularity.csv")) -> items_popularityitems_popularity


3901436-07869e3a2efb1dae
items_popularity %>% mutate(popularity_scale_10 = round(pop_percent * 10)) %>%mutate(popularity_scale_100 = round(pop_percent * 100))


3901436-3126e55375874733


交互项构造

poke_df %>% select(Attack,Defense) -> atk_defatk_def %>% head


3901436-7e9b458d4840e483
#需要平方项的同学请自行添加as_tibble(model.matrix(~ .^2-1,atk_def)) %>% head


3901436-c03fce44c88c8d90



分箱


#读数据read_csv(paste0(file_path,"datasets/fcc_2016_coder_survey_subset.csv")) -> fcc_survey_dffcc_survey_df %>% select('ID.x', 'EmploymentField', 'Age', 'Income') %>% head


3901436-6f86efa1f2d547a4


#根据年龄的十位数分箱fcc_survey_df %>%  mutate(Age_bin_round = floor(Age/10)) %>%  select('ID.x', 'Age', 'Age_bin_round') %>% slice(1071:1076)


3901436-ee875bfe709f9cd4


#自定义标准fcc_survey_df %>% mutate(Age_bin_custom_range = cut(Age,breaks = c(0, 15, 30, 45, 60, 75, 100))) %>% mutate(Age_bin_custom_label = cut(Age,                                  breaks = c(0, 15, 30, 45, 60, 75, 100),                                 labels = 1:6)) %>% slice(1071:1076) %>% select('ID.x', 'Age', 'Age_bin_custom_range', 'Age_bin_custom_label')


3901436-bb933eaa50e6f30c

如果你发现跟python数据对不上,那就对了,因为python的[1071:1076]其实是1072到1076列,因为python是从0开始的,怎么样,够坑吧?


#四分位数划分fcc_survey_df %>% mutate(income_quantile_labels = ntile(Income,4)) %>% select('ID.x', 'Age', 'Income', 'income_quantile_labels') %>%  slice(4:9)


3901436-02314dc2e96d2a33


数学转换

对数变换
fcc_survey_df %>%  mutate(Income_log = log(Income +1)) %>%  slice(4:9) %>% select('ID.x', 'Age', 'Income', 'Income_log')


3901436-cb5feb88d391166b

Box-Cox变换
fcc_survey_df %>%  mutate(Income_boxcox = BoxCox(Income,BoxCox.lambda(Income))) %>% select('ID.x', 'Age', 'Income', 'Income_boxcox') %>% slice(4:9)


3901436-6769f5c3dfc3802d

这里用的Box-Cox用的是forcast包,先计算最优参数,然后代入求BoxCox转化后的数值。

3901436-9ac604dbd9f6fffa

大家都在看

R语言二分类问题案例分析:以泰坦尼克号沉船为例


无缝对接Spark与R:Sparklyr系列—探讨属于数据科学家的Spark


R与Python手牵手:数据的分组排序


R与Python手牵手:数据框的构建、读取与基本描述


R与Python手牵手:数据科学导论系列(包的载入)


R与Python手牵手:数据探索性分析案例展示


R与Python手牵手:多格式文件导入与爬虫



3901436-af7b7b8f84cb356d

公众号后台回复关键字即可学习

回复 爬虫             爬虫三大案例实战  
回复 Python        1小时破冰入门

回复 数据挖掘      R语言入门及数据挖掘
回复 人工智能      三个月入门人工智能
回复 数据分析师   数据分析师成长之路 
回复 机器学习      机器学习的商业应用
回复 数据科学      数据科学实战
回复 常用算法      常用数据挖掘算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值