特征工程概述
在做数据挖掘项目或是kaggle等机器学习比赛的时候,都会开始对数据进行的探索和可视化分析然后是一堆的预处理将数据转为需要的数字表格,再最后传入模型训练。这个其实是很多机器学习的大致过程,大部分就会将前面的都称为特征工程,特征工程其实是非常重要也是最花时间的,一般会占到整个项目的70%道80%,也几乎决定了模型的上限。
当然特征工程主要是从机器学习和模式识别等的角度来看,对于统计建模或是数据挖掘也可能是叫做数据获取,清洗,转换 ,规整再到数据集成和规约,变化以及转换降维等,对于统计或是计量经济里也可能是因变量和自变量,各自变量就是各个特征,做的是因子分析和因素分析,对应目标变量的就是因变量也有称为决策变量,也有称为解释变量和被解释变量,比如y是被解释变量,也就是问题目标,解释变量就是解释这个结果的因素x。
当然也是借鉴了各个领域的方法结合,这里就按特征工程来说,也就是kaggle里一般都是这么组成。
广义上讲特征工程还包括使用方案,特征的获取方案,采样和特征的处理和监控等
特征使用就是根据我们的业务目标,比如离网分析,去找需要哪些相关的数据,会对分析里的因变量有影响;同时还要对这些变量做可用性评估,一个是获取这些变量难度大么,一个是覆盖率,比如是不是只是少数人才有这个信息,还要看这个变量的准确率是否可靠。
特征获取就是如何得到数据,比如数据仓库,系统,用户收集调查等,还有如何存储这些特征数据。
采样其实更多是因为数据不均衡对整体的特征空间的影响,有的从狭义上不算是特征工程主要问题,确实特征选择的方式也可以处理不平衡数据,这里狭义上就不包含,CDA里按独立的样本不均衡问题。
重点来了就是我们这里说的特征工程,主要包括单特征的处理,多特征的处理,特征选择等。
特征监控其实就是对特征变量数据的质量控制。
有人形象的将数据工作和餐馆对比,那么算法建模其实就类似厨师做菜,
对应关系如下:
研发工程师 - 准备食材;
数据仓库工程师 - 食材筛选、归类 & 切菜;
算法工程师 - 炒菜烹饪;
运维工程师 - 洗碗 / 餐具归类管理;
产品设计师 / 产品经理 (PD/PM)- 设计菜单新品;
产品运营 - 设计菜品的优惠活动和套餐等;
数据分析师(BI)- 服务顾客;
美术设计师 - 设计餐厅和菜品的形态;
业务发展经理(BD)- 餐厅推广;
可以看到厨师也需要很多参与一些切菜和搭配等工作,同时也要很理解这些素材,就像我们做特征工程一样,就像练刀工,你总得学会,不是看着感觉都一样,但是其实你切的方向,厚薄,大小以及后期的搭配和火候都会影响最后的做出来的菜的好坏。
单特征处理
对于单个特征也就是某一列的数据进行的处理,主要有缺失值,异常值,归一化,离散化,独热编码,平滑,方差等,
缺失值
数据缺失分为两种:一是行记录的缺失,这种情况也定义为数据记录丢失,一般是取样过程处理;二是数据列值的缺失,指由于各种原因导致的数据记录中某些列的值空缺,这里主要考虑的是这类变量,不同的数据存储和环境中对于缺失值的表示结果不同,例如数据库中是Null、Python返回对象是None、Pandas或Numpy中是NaN。当然也有少数特殊的,比如会用空字符填充,或是用?或是0等,在编程时候最好都转为nan 这样可以通过pandas的方法统一处理。
对于丢失的数据记录通常无法找回,这里重点讨论数据列类型缺失值的处理,通常有四种思路:
1、丢弃
这种方法简单明了,直接删除带有缺失值的行记录(整行删除)或者列字段(整列删除),减少缺失数据记录对总体数据的影响。但丢弃意味着会消减数据特征,以下任意一种场景都不宜采用该方法:
数据集总体中存在大量的数据记录不完整情况且比例较大,例如超过10%,删除这些带有缺失值的记录意味着将会损失过多有用信息。
带有缺失值的数据记录大量存在着明显的数据分布规律或特征,例如带有缺失值的数据记录的目标标签(即分类中的Label变量)主要集中于某一类或几类,如果删除这些数据记录将使对应分类的数据样本丢失大量特征信息,导致模型过拟合或分类不准确。
2、补全
相对丢弃而言,补全是更加常用的缺失值处理方式,通过一定的方法将缺失的数据补上,从而形成完整的数据记录对于后续的数据处理、分析和建模至关重要。常用的补全方法包括:
统计法:对于数值型的数据,使用均值、加权均值、中位数等方法补足;对于分类型数据,使用类别众数最多的值补足。
模型法:更多时候我们会基于已有的其他字段,将缺失字段作为目标变量进行预测,从而得到较为可能的补全值。如果带有缺失值的列是数值变量,采用回归模型补全;如果是分类变量,则采用分类模型补全。
专家补全:对于少量且具有重要意义的数据记录,专家补足也是非常重要的一种途径。
其他方法:例如随机法、特殊值法、多重填补等。
3、真值转换法
某些情况下,我们可能无法得知缺失值的分布规律,并且无法对于缺失值采用上述任何一种更浓方法做处理;或者我们认为数据缺失也是一种规律,不应该轻易对缺失值随意处理,那么还有一种缺失值处理思路——真值转换。
该思路的根本观点是,我们承认缺失值的存在,并且把数据缺失也作为数据分布规律的一部分,这将变量的实际值和缺失值都作为输入维度参与后续数据处理和模型计算中。但是变量的实际值可以作为变量值参与模型计算,而缺失值通常无法参与运算,因此需要将缺失值进行真值转换。
以用户性别字段为例,很多数据库集都无法对会员的性别进行补足,但又舍不得将其丢弃掉,那么我们将选择将其中的值,包括男、女、未知从一个变量的多个值分布状态转换为多个变量的真值分布状态。
转换前:性别(值域:男、女、未知)
转换后:性别男(值域1或0)、性别女(值域1或0)、性别_未知(值域1或0)
然后将这3列新的字段作为输入维度来替换原来的1个字段参与后续模型计算。
4、不处理
这种思路主要看后期的数据分析和建模应用,很多模型对于缺失值有容忍度或灵活的处理方法,因此在预处理阶段可以不做处理。常见的能够自动处理缺失值的模型包括:KNN、决策树和随机森林、神经网络和朴素贝叶斯、DBSCAN(基于密度的带有噪声的空间聚类)等。这些模型对于缺失值的处理思路是:
忽略,缺失值不参与距离计算,例如KNN。
将缺失值作为分布的一种状态,并参与到建模过程,例如各种决策树及其变体。
不基于距离做计算,因此基于值的距离计算本身的影响就消除了,例如DBSCAN。
另外注意不能就一个个看,也有可能需要考虑另一个变量的数据来分析,比如kaggle的房价预测里,有两种情况,比如PoolQC是泳池质量,那要看一个相关的PoolArea也就是有没泳池,如果为0 ,那就是没有,那当然PoolQC 是空值,那可以用统一标识比如‘None’或是0来填充;但如果有泳池,那么这个缺失值就是第二种情况,属于数据缺失了,可能去掉,或者用众数填充。
import pandas as pd
import numpy as np
import sklearn
from sklearn.impute import SimpleImputer
# 生成缺失数据
df = pd.DataFrame(np.random.randn(6, 4),
columns=['col1', 'col2', 'col3', 'col4']) # 生成一份数据
df.iloc[1:2, 1] = np.nan # 增加缺失值
df.iloc[4, 3] = np.nan # 增加缺失值
# 查看哪些值缺失
nan_all = df.isnull() # 获得所有数据框中的N值
def null_count(data): # 定义 null 值查找函数,函数名 null_count
null_data = data.isnull().sum() # 查找各个特征 null 值并计算数量
null_data = null_data.drop(null_data[null_data == 0].index).sort_values(
ascending=False) # 删除数目为零的特征,降序排列
return null_data # 返回结果
# 查看哪些列缺失
nan_col1 = df.isnull().any() # 获得含有NA的列
nan_col2 = df.isnull().all() # 获得全部为NA的列
# 丢弃缺失值
df2 = df.dropna() # 直接丢弃含有NA的行记录
# 使用sklearn将缺失值替换为特定值
nan_model = SimpleImputer(missing_values='NaN', strategy='mean') # 建立替换规则:将值为NaN的缺失值以均值做替换
#nan_result = nan_model.fit(df) # 应用模型规则
# 使用pandas将缺失值替换为特定值
nan_result_pd1 = df.fillna(method='backfill') # 用后面的值替换缺失值
nan_result_pd2 = df.fillna(method='bfill', limit=1) # 用后面的值替代缺失值,限制每列只能替代一个缺失值
nan_result_pd3 = df.fillna(method='pad') # 用前面的值替换缺失值
nan_result_pd4 = df.fillna(0) # 用0替换缺失值
nan_result_pd5 = df.fillna({'col2': 1.1, 'col4': 1.2}) # 用不同值替换不同列的缺失值
nan_result_pd6 = df.fillna(df.mean()['col2':'col4']) # 用平均数代替,选择各自列的均值替换缺失值
异常值
异常数据是数据分布的常态,处于特定分布区域或范围之外的数据数据通常会被定义为异常或“噪音”。产生数据“噪音”的原因很多,例如业务运营操作、数据采集问题、数据同步问题等。对异常数据进行处理前,需要先辨别出到底哪些是真正的数据异常。从数据异常的状态看分为两种:
一种是“伪异常”,这些异常是由于业务特定运营动作产生,其实是正常反映业务状态,而不是数据本身的异常规律。
一种是“真异常”,这些异常并不是由于特定的业务动作引起,而是客观的反映了数据本身分布异常的分布个案。
大多数数据挖掘或数据工作中,异常值都会在数据的预处理过程中被认为是噪音而剔除,以避免其对总体数据评估和分析挖掘的影响。但在以下几种情况下,我们无需对异常值做抛弃处理。
异常正常反映了业务运营结果
该场景是由业务部门的特定动作导致的数据分布异常,如果抛弃异常值将导致无法正确反馈业务结果。
例如:公司的A商品正常情况下日销量为1000台左右。由于昨日举行优惠促销活动导致总销量达到10000台,由于后端库存备货不足导致今日销量又下降到100台。在这种情况下,10000台和100台都是正确反映了业务运营的结果,而非数据异常案例。
异常模型监测
异常检测模型是针对整体样本中的异常数据进行分析和挖掘以便找到其中的异常个案和规律,这种数据应用围绕异常值展开,因此异常值不能做抛弃处理。
异常检测模型常用于客户异常识别、信用卡欺诈、贷款审批识别、药物变异识别、恶劣气象预测、网络入侵检测、流量作弊检测等。在这种情况下,异常数据本身是目标数据,如果被处理掉将损失关键信息。
包容异常值的数据建模
如果数据算法和模型对异常值不敏感,那么即使不处理异常值也不会对模型本身造成负面影响。例如在决策树中,异常值本身就可以作为一种分裂节点。
对于分类变量的 异常需要考虑实际数据多少和可解释性,比如有的数据是不属于特有的中文字符,就一般认为是需要转为新的异常列,比如是否充值,可能就是异常的,还比如一些电话的通信记录,对于地址或是身份信息等数据可能就有是故意缺失或异常的,但是确实有用的特征值,类似异常检测的情况,这里一般可以使用另外特定的类别,比如工程里使用-9999等填充替换。
而对于一般的数值变量,一般采用箱线图,3σ准则和Z-score方法。
3σ准则是认为数据一般符合正态分布,那么概率上看,在正态分布 中σ代表 标准差 ,μ代表 均值 。x=μ即为图像的对称轴
3σ原则为
数值分布在(μ-σ,μ+σ)中的概率为0.6826
数值分布在(μ-2σ,μ+2σ)中的概率为0.9544
数值分布在(μ-3σ,μ+3σ)中的概率为0.9974
可以认为,Y 的取值几乎全部集中在(μ-3σ,μ+3σ)] 区间
内,超出这个范围的可能性仅占不到0.3%.也就是出现的小概率事件的数当做异常值。
def OutlierDetection(df,ks_res):
# 计算均值
u = df['value'].mean()
# 计算标准差
std = df['value'].std()
if ks_res==1:
# 定义3σ法则识别异常值
# 识别异常值
error = df[np.abs(df['value'] - u) > 3 * std]
# 剔除异常值,保留正常的数据
data_c = df[np.abs(df['value'] - u) <= 3 * std]
# 输出异常数据
# print(error)
return error
else:
print('请先检测数据是否服从正态分布-----------')
return None
z-score是标准化的方法,将数据投射到统一的范围,然后比较和均值的距离,距离超过一定阈值就是异常,
# 通过Z-Score方法判断异常值
df_zscore = df.copy() # 复制一个用来存储Z-score得分的数据框
cols = df.columns # 获得数据框的列名
for col in cols: # 循环读取每列
df_col = df[col] # 得到每列的值
z_score = (df_col - df_col.mean()) / df_col.std() # 计算每列的Z-score得分
df_zscore[
col] = z_score.abs() > 2.2 # 判断Z-score得分是否大于2.2,如果是则是True,否则为False
print(df_zscore) # 打印输出
相比于3sigma和z-score需要数据接近正态分布,但实际数据往往并不严格服从正态分布。箱线图就么有要求,所以比较常用;
箱线图判断异常值的标准以四分位数和四分位距为基础。
四分位距(QR, Quartile range):上四分位数与下四分位数之间的间距,即上四分位数减去下四分位数。
F代表中位数,QR代表四分位距。
在Q3+1.5QR(四分位距)和Q1-1.5QR处画两条与中位线一样的线段,这两条线段为异常值截断点,称其为内限。
在F(中位数)+3QR和F-3QR处画两条线段,称其为外限。
箱线图为我们提供了识别异常值的一个标准 ,虽然这种标准有点任意性,但它来源于经验判断,经验表明它在处理需要特别注意的数据方面表现不错。这与识别异常值的经典方法有些不同。众所周知,基于正态分布的3σ法则或z分数方法是以假定数据服从正态分布为前提的,它们判断异常值的标准是以计算数据批的均值和标准差为基础的,而均值和标准差的耐抗性极小,异常值本身会对它们产生较大影响,这样产生的异常值个数不会多于总数0.7%。显然,应用这种方法于非正态分布数据中判断异常值,其有效性是有限的。箱线图的绘制依靠实际数据,不需要事先假定数据服从特定的分布形式,没有对数据作任何限制性要求,它只是真实直观地表现数据形状的本来面貌;另一方面,箱线图判断异常值的标准以四分位数和四分位距为基础,四分位数具有一定的耐抗性,多达25%的数据可以变得任意远而不会很大地扰动四分位数,所以异常值不能对这个标准施加影响,箱线图识别异常值的结果比较客观。
另外也有基于树模型比如孤立森林的检测还有基于密度的聚类算法可以做检测。
import seaborn as sns
def outliers_proc(data, col_name, scale=3):
"""
用于清洗异常值,默认用 box_plot(scale=3)进行清洗
:param data: 接收 pandas 数据格式
:param col_name: pandas 列名
:param scale: 尺度
:return:
"""
def box_plot_outliers(data_ser, box_scale):
"""
利用箱线图去除异常值
:param data_ser: 接收 pandas.Series 数据格式
:param box_scale: 箱线图尺度,
:return:
"""
iqr = box_scale * (data_ser.quantile(0.75) - data_ser.quantile(0.25))
val_low = data_ser.quantile(0.25) - iqr
val_up = data_ser.quantile(0.75) + iqr
rule_low = (data_ser < val_low)
rule_up = (data_ser > val_up)
return (rule_low, rule_up), (val_low, val_up)
data_n = data.copy()
data_series = data_n[col_name]
rule, value = box_plot_outliers(data_series, box_scale=scale)
index = np.arange(data_series.shape[0])[rule[0] | rule[1]]
print("Delete number is: {}".format(len(index)))
data_n = data_n.drop(index)
data_n.reset_index(drop=True, inplace=True)
print("Now column number is: {}".format(data_n.shape[0]))
index_low = np.arange(data_series.shape[0])[rule[0]]
outliers = data_series.iloc[index_low]
print("Description of data less than the lower bound is:")
print(pd.Series(outliers).describe())
index_up = np.arange(data_series.shape[0])[rule[1]]
outliers = data_series.iloc[index_up]
print("Description of data larger than the upper bound is:")
print(pd.Series(outliers).describe())
fig, ax = plt.subplots(1, 2, figsize=(10, 7))
sns.boxplot(y=data[col_name], data=data, palette="Set1", ax=ax[0])
sns.boxplot(y=data_n[col_name], data=data_n, palette="Set1", ax=ax[1])
return data_n
标准化或归一化
这可以借鉴量化分析的思想,对于特别是基于距离的模型,比如一个变量是年龄,而一个变量是工资,值的范围就有很大差距,那对于做的模型自然变量值大的影响大,所以需要将不同规模和量纲的数据经过处理,缩放到相同的数据区间和范围,以减少规模、特征、分布差异等对模型的影响。而且除了用作模型计算,标准化后的数据还具有了直接计算并生成复合指标的意义,是加权指标的必要步骤。
实现中心化和正态分布的Z-Score
Z-Score标准化是基于原始数据的均值和标准差进行的标准化,假设原转换的数据为x,新数据为x’,那么x’=(x-mean)/std,其中mean和std为x所在列的均值和标准差。
这种方法适合大多数类型的数据,也是很多工具的默认标准化方法。标准化之后的数据是以0为均值,方差为1的正态分布。但是Z-Score方法是一种中心化方法,会改变原有数据的分布结构,不适合用于对稀疏数据做处理。
在很多时候,数据集会存在稀疏性特征,表现为标准差小、并有很多元素的值为0,最常见的稀疏数据集是用来做协同过滤的数据集,绝大部分的数据都是0,仅有少部分数据为1。对稀疏数据做标准化,不能采用中心化的方式,否则会破坏稀疏数据的结构。
实现归一化的Max-Min
Max-Min标准化方法是对原始数据进行线性变换,假设原转换的数据为x,新数据为x’,那么x’=(x-min)/(max-min),其中min和max为x所在列的最小值和最大值。
这种标准化方法的适应性非常广泛,得到的数据会完全落入[0,1]区间内(Z-Score则没有类似区间),这种方法能使数据归一化而落到一定的区间内,同时还能较好的保持原有数据结构。
用于稀疏数据的MaxAbs
最大值绝对值标准化(MaxAbs)根据最大值的绝对值进行标准化,假设原转换的数据为x,新数据为x’,那么x’=x/|max|,其中max为x所在列的最大值。
MaxAbs方法跟Max-Min用法类似,也是将数据落入一定区间,但该方法的数据区间为[-1,1]。MaxAbs也具有不破坏原有数据分布结构的特点,因此也可以用于稀疏数据,或者稀疏的CSR或CSC矩阵。
CSR(Compressed Sparse Row,行压缩)和CSC(Compressed Sparse Column,列压缩)是稀疏矩阵的两种存储格式,这两种稀疏矩阵在scipy.sparse包中应用广泛。除了这两种格式之外,用于存储稀疏矩阵的格式还有COO、CSR、DIA、ELL、HYB等。
针对离群点的RobustScaler
某些情况下,假如数据集中有离群点,我们可以使用Z-Score进行标准化,但是标准化后的数据并不理想,因为异常点的特征往往在标准化之后便容易失去离群特征。此时,可以使用RobustScaler针对离群点做标准化处理,该方法对数据中心化和数据的缩放鲁棒性有更强的参数控制。
import numpy as np
from sklearn import preprocessing
import matplotlib.pyplot as plt
#data = np.loadtxt('data6.txt', delimiter='\t') # 读取数据# Z-Score标准化
zscore_scaler = preprocessing.StandardScaler() # 建立StandardScaler对象
data_scale_1 = zscore_scaler.fit_transform(data) # StandardScaler标准化处理
# Max-Min标准化
minmax_scaler = preprocessing.MinMaxScaler() # 建立MinMaxScaler模型对象
data_scale_2 = minmax_scaler.fit_transform(data) # MinMaxScaler标准化处理
# MaxAbsScaler标准化
maxabsscaler_scaler = preprocessing.MaxAbsScaler() # 建立MaxAbsScaler对象
data_scale_3 = maxabsscaler_scaler.fit_transform(data) # MaxAbsScaler标准化处理
# RobustScaler标准化
robustscalerr_scaler = preprocessing.RobustScaler() # 建立RobustScaler标准化对象
data_scale_4 = robustscalerr_scaler.fit_transform(data) # RobustScaler标准化标准化处理
数据平滑
对于大多数变量都是来自自然的符合正态分布,但是有时候数据不那么好,称为偏态,所以就要使得数据改的接近正态分布,这就是做平滑,注意到其实目标变量也是一样需要,比如一些回归问题预测的是连续值,许多机器学习算法都是基于数据是高斯分布即正态分布的条件下推导出来的,因此,这里先把值处理成为高斯分布的形式。
比如房价
from scipy.stats import norm, skew
sns.distplot(train['SalePrice'], fit=norm)
# 获得均值和方差
(mu, sigma) = norm.fit(train['SalePrice'])
print('\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))
# 画出数据分布图
plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],
loc='best')
plt.ylabel('Frequency')
# 设置标题
plt.title('SalePrice distribution')
这里就是右偏了,对于各个特征也是可以类似看是否偏态,然后做调整,一般方法有 log,指数,boxcox变换
boxcox其实就是log(1+x)
变换后预测后的值还要转回来,对应expm1
# 平滑数据
#train["SalePrice"] = np.log(train["SalePrice"]) #log(x)
train["SalePrice"] = np.log1p(train["SalePrice"]) #log(1+x)
# 重新画出数据分布图
sns.distplot(train['SalePrice'], fit=norm)
# 重新计算平滑后的均值和方差
(mu, sigma) = norm.fit(train['SalePrice'])
print('\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))
plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],
loc='best')
plt.ylabel('Frequency')
plt.title('SalePrice distribution')
# 画出 Q-Q 图
fig = plt.figure()
res = stats.probplot(train['SalePrice'], plot=plt)
plt.show()
#批量查询检测
skew = {} # 建立空字典
for feature in features:
if train[feature].dtype != 'O': # 对非字符串类型的数据计算偏态系数
a = float('%.2f' % train[feature].skew()) # 计算偏态系数
if a > 1: # 如果偏态系数大于1,储存在字典中
skew[feature] = a
skew # 显示
#批量变换
from scipy.special import boxcox1p # 导入 box-cox transformation 模块
lam = 0.1
for feature in skew.keys():
train[feature] = boxcox1p(train[feature], lam) # box-cox transformation
离散化
这个很多见,就像我们决策树时候都是对某个特征进行一个切分,大于或小于某个值就进行下一步选择,最多见的就是年龄, 把年龄层分成5-18岁(中小学生),19-23岁(大学生),24-29岁(工作前几年),30-40岁(成家立业),40-60岁(中年人)从某些层面来说比连续的年龄数据(比如说某人年龄是20岁1月3日之类的)更容易理解不同年龄层人的特性。同样的对应很多连续数值特征进行离散化,叫分箱或分桶
为什么要做数据分桶呢
离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;
离散后的特征对异常值更具鲁棒性,如 age>30 为 1 否则为 0,对于年龄为 200 的也不会对模型造成很大的干扰;
如果是 LR 等属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,这相当于引入了非线性,能够提升模型的表达能力,加大拟合;
离散后特征可以进行特征交叉,提升表达能力,由 M+N 个变量变成 M*N 个变量,进一步引入非线形,提升了表达能力;
特征离散后模型更稳定,如用户年龄区间,不会因为用户年龄长了一岁就变化
还有很多好处,对于很多树模型也很有效果,最近比较好用的LightGBM ,除了速度快 ,就是改进 XGBoost 时增加了数据分桶,增强了模型的泛化性。
#年龄数据
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
# 分箱的边界
bins = [18, 25, 35, 60, 100]
cats = pd.cut(ages, bins)
print(type(cats))
# Categorical对象
cats
#获取分箱编码
cats.codes
#返回分箱便捷索引
cats.categories
#统计箱中元素的个数
pd.value_counts(cats)
#带标签的分箱
group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
cats = pd.cut(ages, bins, labels = group_names)
cats
cats.get_values()
非数值变量转换
这里主要考虑将非数值型数据分为分类和顺序的
分类数据指某些数据属性只能归于某一类别的非数值型数据,例如性别中的男、女就是分类数据。分类数据中的值没有明显的高、低、大、小等包含等级、顺序、排序、好坏等逻辑的划分,只是用来区分两个或多个具有相同或相当价值的属性。例如:性别中的男和女,颜色中的红、黄和蓝,他们都是相同衡量维度上不同的属性分类而已。
顺序数据是只能归于某一有序类别的非数值型数据,例如用户的价值度分为高、中、低,学历分为博士、研究生、学士,这些都属于顺序数据。在顺序数据中,有明显的排序规律和逻辑层次的划分。例如:高价值的用户就是比低价值的用户价值高(业务定义该分类时的已经赋予了这样的价值含义)。
比如男女就分为1和2 或是0或1只要不一样都行,而教育水平就可能是0,1,2大小排序,因为可能对于目标有影响,当然一般当做分类做独热编码也是可行的。
通用来说都可以用哑编码,比如女 :01,男:1 0
可以用sklearn的OneHotEncoder库
也可以使用 pandas的get_dummy得到独热编码
# 3.2 将分类数据和顺序数据转换为标志变量
import pandas as pd # 导入pandas库
from sklearn.preprocessing importOneHotEncoder # 导入OneHotEncoder库
# 生成数据
df = pd.DataFrame({'id': [3566841, 6541227,3512441],'sex': ['male', 'Female','Female'], 'level': ['high', 'low','middle']})
print (df) # 打印输出原始数据框
# 自定义转换主过程
df_new = df.copy() # 复制一份新的数据框用来存储转换结果
for col_num, col_name in enumerate(df): # 循环读出每个列的索引值和列名
col_data = df[col_name] # 获得每列数据
col_dtype = col_data.dtype # 获得每列dtype类型
if col_dtype == 'object': # 如果dtype类型是object(非数值型),执行条件
df_new = df_new.drop(col_name, 1) # 删除df数据框中要进行标志转换的列
value_sets = col_data.unique() # 获取分类和顺序变量的唯一值域
for value_unique in value_sets: # 读取分类和顺序变量中的每个值
col_name_new = col_name + '_' + value_unique # 创建新的列名,使用原标题+值的方式命名
col_tmp = df.iloc[:, col_num] # 获取原始数据列
new_col = (col_tmp == value_unique) # 将原始数据列与每个值进行比较,相同为True,否则为False
df_new[col_name_new] = new_col # 为最终结果集增加新列值
print (df_new) # 打印输出转换后的数据框
# 使用sklearn进行标志转换
df2 = pd.DataFrame({'id': [3566841,6541227, 3512441],'sex': [1, 2, 2],'level': [3, 1, 2]})id_data = df2.values[:, :1] # 获得ID列
transform_data = df2.values[:, 1:] # 指定要转换的列
enc = OneHotEncoder() # 建立模型对象
df2_new = enc.fit_transform(transform_data).toarray() # 标志转换
df2_all = pd.concat((pd.DataFrame(id_data),pd.DataFrame(df2_new)), axis=1) # 组合为数据框
print (df2_all) # 打印输出转换后的数据框
df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
'data1': range(6)})
df
pd.get_dummies(df['key'])