思维导图:零基础入门数据挖掘的学习路径

1. 写在前面

零基础入门数据挖掘是记录自己在Datawhale举办的数据挖掘专题学习中的所学和所想, 该系列笔记使用理论结合实践的方式,整理数据挖掘相关知识,提升在实际场景中的数据分析、数据清洗,特征工程、建模调参和模型融合等技能。所以这个系列笔记共五篇重点内容, 也分别从上面五方面进行整理学习, 既是希望能对知识从实战的角度串联回忆,加强动手能力的锻炼,也希望这五篇笔记能够帮助到更多喜欢数据挖掘的小伙伴,我们一起学习,一起交流吧。

既然是理论结合实践的方式,那么我们是从天池的一个二手车交易价格预测比赛出发进行学习,既可以学习到知识,又可以学习如何入门一个数据竞赛, 下面我们开始吧。

今天是本系列的第三篇文章数据的清晰和转换技巧,依然是围绕着上面的比赛进行展开。数据清洗和转换在数据挖掘中也非常重要,毕竟数据探索我们发现了问题之后,下一步就是要解决问题,通过数据清洗和转换,可以让数据变得更加整洁和干净,才能进一步帮助我们做特征工程,也有利于模型更好的完成任务。如果看了零基础数据挖掘入门系列(二) - 数据的探索性(EDA)分析, 就会发现数据存在下面的一些问题(当然可能不全,欢迎补充交流)

  1. 有异常值, 可以用箱线图锁定异常, 但是有时候不建议直接把样本删除,截尾的方式可能会更好一些
  2. 有缺失值,尤其是类别的那些特征(bodyType, gearbox, fullType, notRepaired的那个)
  3. 类别倾斜的现象(seller, offtype), 考虑删除
  4. 类别型数据需要编码
  5. 数值型数据或许可以尝试归一化和标准化的操作
  6. 预测值需要对数转换
  7. power高度偏斜,这个处理异常之后对数转换试试
  8. 隐匿特征的相关性
  9. 存在高势集model, 及类别特征取值非常多, 可以考虑使用聚类的方式,然后在独热编码

所以今天的重点是在处理上面的问题上,整理数据清洗和转换的技巧,以方便日后查阅迁移。首先是处理异常数据,通过箱线图捕获异常点,然后截尾处理, 然后是整理一些处理缺失的技巧, 然后是数据分桶和数据转换的一些技巧, 下面我们开始。

大纲如下:

  • 异常值处理(箱线图分析删除,截尾,box-cox转换技术)
  • 缺失值处理(不处理, 删除,插值补全, 分箱)
  • 数据分桶(等频, 等距, Best-KS分桶,卡方分桶)
  • 数据转换(归一化标准化, 对数变换,转换数据类型,编码等)
  • 知识总结

Ok, let’s go!

准备工作:数据清洗的时候,我这里先把数据训练集和测试集放在一块进行处理,因为我后面的操作不做删除样本的处理, 如果后面有删除样本的处理,可别这么做。 数据合并处理也是一个trick, 一般是在特征构造的时候合并起来,而我发现这个问题中,数据清洗里面训练集和测试集的操作也基本一致,所以在这里先合起来, 然后分成数值型、类别型还有时间型数据,然后分别清洗。

"""把train_data的price先保存好"""
train_target = train_data['price']
del train_data['price']"""数据合并"""
data = pd.concat([train_data, test_data], axis=0)
data.set_index('SaleID', inplace=True)"""把数据分成数值型和类别型还有时间型,然后分开处理""""""人为设定"""
numeric_features = ['power', 'kilometer']
numeric_features.extend(['v_'+str(i) for i in range(15)]) categorical_features = ['name', 'model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage','regionCode', 'seller', 'offerType']time_features = ['regDate', 'creatDate']num_data = data[numeric_features]
cat_data = data[categorical_features]
time_data = data[time_features]

trick1: 就是如果发现处理数据集的时候,需要训练集和测试集进行同样的处理,不放将它们合并到一块处理。

trick2: 如果发现特征字段中有数值型,类别型,时间型的数据等,也不妨试试将它们分开,因为数值型,类别型,时间型数据不管是在数据清洗还是后面的特征工程上, 都是会有不同的处理方式, 所以这里将训练集合测试集合并起来之后,根据特征类型把数据分开, 等做完特征工程之后再进行统一的整合(set_index把它们的索引弄成一样的,整合的时候就非常简单了)。

2. 异常值处理

常用的异常值处理操作包括箱线图分析删除异常值, BOX-COX转换(处理有偏分布), 长尾截断的方式, 当然这些操作一般都是处理数值型的数据。

关于box-cox转换,一般是用于连续的变量不满足正态的时候,在做线性回归的过程中,一般线性模型假定 Y = X β + ε Y=Xβ + ε Y=Xβ+ε, 其中ε满足正态分布,但是利用实际数据建立回归模型时,个别变量的系数通不过。例如往往不可观测的误差 ε 可能是和预测变量相关的,不服从正态分布,于是给线性回归的最小二乘估计系数的结果带来误差,为了使模型满足线性性、独立性、方差齐性以及正态性,需改变数据形式,故应用box-cox转换。具体详情这里不做过多介绍,当然还有很多转换非正态数据分布的方式:

  • 对数转换: y i = l n ( x i ) y_i = ln(x_i) yi​=ln(xi​)
  • 平方根转换: y i = x i y_i = \sqrt {x_i} yi​=xi​ ​
  • 倒数转换: y i = 1 x i y_i = \frac{1}{x_i} yi​=xi​1​
  • 平方根后取倒数: y i = 1 x i y_i = \frac{1}{\sqrt{x_i}} yi​=xi​ ​1​
  • 平方根后再取反正弦: y i = A r c s i n ( x i ) y_i = Arcsin(\sqrt{x_i}) yi​=Arcsin(xi​ ​)
  • 幂转换: y i = x i λ − 1 x ~ λ + 1 y_i = \frac{x^\lambda_i-1}{\tilde{x}^{\lambda+1}} yi​=x~λ+1xiλ​−1​, 其中 x ~ = ( ∏ i = 1 n x i ) 1 n \tilde{x}=\left(\prod_{i=1}^{n} x_{i}\right)^{\frac{1}{n}} x~=(∏i=1n​xi​)n1​, 参数 λ ∈ [ − 1.5 , 1 ] \lambda \in[-1.5,1] λ∈[−1.5,1]

在一些情况下(P值<0.003)上述方法很难实现正态化处理,所以优先使用Box-Cox转换,但是当P值>0.003时两种方法均可,优先考虑普通的平方变换。

Box-Cox的变换公式:
y ( λ ) = { ( y + c ) λ − 1 λ , if  λ ≠ 0 log ⁡ ( y + c ) , if  λ = 0 y^{(\lambda)}=\left\{\begin{array}{c} \frac{(y+c)^{\lambda}-1}{\lambda}, \text { if } \lambda \neq 0 \\ \log (y+c), \text { if } \lambda=0 \end{array}\right. y(λ)={λ(y+c)λ−1​, if λ​=0log(y+c), if λ=0​

具体实现:

from scipy.stats import boxcox
boxcox_transformed_data = boxcox(original_data)

当然,也给出一个使用案例:使用scipy.stats.boxcox完成BoxCox变换

好了,BOX-COX就介绍这些吧, 因为这里处理数据先不涉及这个变换,我们回到这个比赛中来,通过这次的数据介绍一下箱线图筛选异常并进行截尾:
从上面的探索中发现,某些数值型字段有异常点,可以看一下power这个字段:

# power属性是有异常点的
num_data.boxplot(['power'])

结果如下:

所以,我们下面用箱线图去捕获异常,然后进行截尾, 这里不想用删除,一个原因是我已经合并了训练集和测试集,如果删除的话肯定会删除测试集的数据,这个是不行的, 另一个原因就是删除有时候会改变数据的分布等,所以这里考虑使用截尾的方式:

"""这里包装了一个异常值处理的代码,可以随便调用"""
def outliers_proc(data, col_name, scale=3):"""用于截尾异常值, 默认用box_plot(scale=3)进行清洗param:data: 接收pandas数据格式col_name: pandas列名scale: 尺度"""data_col = data[col_name]Q1 = data_col.quantile(0.25) # 0.25分位数Q3 = data_col.quantile(0.75)  # 0,75分位数IQR = Q3 - Q1data_col[data_col < Q1 - (scale * IQR)] = Q1 - (scale * IQR)data_col[data_col > Q3 + (scale * IQR)] = Q3 + (scale * IQR)return data[col_name]num_data['power'] = outliers_proc(num_data, 'power')

我们再看一下数据:

是不是比上面的效果好多了?当然,如果想删除这些异常点,这里是来自Datawhale团队的分享代码,后面会给出链接,也是一个模板:

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) - iqrval_up = data_ser.quantile(0.75) + iqrrule_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

这个代码是直接删除数据,这个如果要使用,不要对测试集用哈。下面看看power这个特征的分布:

也不错了,所以就没再进一步处理power,至于其他的数值型是不是需要截尾,这个看自己吧。

3. 缺失值处理

关于缺失值处理的方式,这里也是先上方法, 有几种情况:不处理(这是针对xgboost等树模型), 有些模型有处理缺失的机制,所以可以不处理,如果缺失的太多,可以考虑删除该列, 另外还有插值补全(均值,中位数,众数,建模预测,多重插补等), 还可以分箱处理,缺失值一个箱。

下面整理几种填充值的方式:

# 删除重复值
data.drop_duplicates()
# dropna()可以直接删除缺失样本,但是有点不太好# 填充固定值
train_data.fillna(0, inplace=True) # 填充 0
data.fillna({0:1000, 1:100, 2:0, 4:5})   # 可以使用字典的形式为不用列设定不同的填充值train_data.fillna(train_data.mean(),inplace=True) # 填充均值
train_data.fillna(train_data.median(),inplace=True) # 填充中位数
train_data.fillna(train_data.mode(),inplace=True) # 填充众数train_data.fillna(method='pad', inplace=True) # 填充前一条数据的值,但是前一条也不一定有值
train_data.fillna(method='bfill', inplace=True) # 填充后一条数据的值,但是后一条也不一定有值"""插值法:用插值法拟合出缺失的数据,然后进行填充。"""
for f in features: train_data[f] = train_data[f].interpolate()train_data.dropna(inplace=True)"""填充KNN数据:先利用knn计算临近的k个数据,然后填充他们的均值"""
from fancyimpute import KNN
train_data_x = pd.DataFrame(KNN(k=6).fit_transform(train_data_x), columns=features)# 还可以填充模型预测的值, 这一个在我正在写的数据竞赛修炼笔记的第三篇里面可以看到,并且超级精彩,还在写

再回到这个比赛中,我们在数据探索中已经看到了缺失值的情况:

上图可以看到缺失情况, 都是类别特征的缺失,notRepaired这个特征的缺失比较严重, 可以尝试填充, 但目前关于类别缺失,感觉上面的方式都不太好,所以这个也是一个比较困难的地方,感觉用模型预测填充比较不错,后期再说吧,因为后面的树模型可以自行处理缺失。 当然OneHot的时候,会把空值处理成全0的一种表示,类似于一种新类型了。

4. 数据分桶

连续值经常离散化或者分离成“箱子”进行分析, 为什么要做数据分桶呢?

  1. 离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;
  2. 离散后的特征对异常值更具鲁棒性,如 age>30 为 1 否则为 0,对于年龄为 200 的也不会对模型造成很大的干扰;
  3. LR 属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,这相当于引入了非线性,能够提升模型的表达能力,加大拟合;
  4. 离散后特征可以进行特征交叉,提升表达能力,由 M+N 个变量编程 M*N 个变量,进一步引入非线形,提升了表达能力;
  5. 特征离散后模型更稳定,如用户年龄区间,不会因为用户年龄长了一岁就变化

当然还有很多原因,LightGBM 在改进 XGBoost 时就增加了数据分桶,增强了模型的泛化性

数据分桶的方式:

  • 等频分桶
  • 等距分桶
  • Best-KS分桶(类似利用基尼指数进行二分类)
  • 卡方分桶

最好是数据分桶的特征作为新一列的特征,不要把原来的数据给替换掉, 所以在这里通过分桶的方式做一个特征出来看看,以power为例

"""下面以power为例进行分桶, 当然构造一列新特征了"""
bin = [i*10 for i in range(31)]
num_data['power_bin'] = pd.cut(num_data['power'], bin, labels=False)

当然这里的新特征会有缺失。

这里也放一个数据分桶的其他例子(迁移之用)

# 连续值经常离散化或者分离成“箱子”进行分析。
# 假设某项研究中一组人群的数据,想将他们进行分组,放入离散的年龄框中
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
# 如果按年龄分成18-25, 26-35, 36-60, 61以上的若干组,可以使用pandas中的cut
bins = [18, 25, 35, 60, 100]         # 定义箱子的边
cats = pd.cut(ages, bins)
print(cats)   # 这是个categories对象    通过bin分成了四个区间, 然后返回每个年龄属于哪个区间
# codes属性
print(cats.codes)    #  这里返回一个数组,指明每一个年龄属于哪个区间
print(cats.categories)
print(pd.value_counts(cats))   # 返回结果是每个区间年龄的个数# 与区间的数学符号一致, 小括号表示开放,中括号表示封闭, 可以通过right参数改变
print(pd.cut(ages, bins, right=False))# 可以通过labels自定义箱名或者区间名
group_names = ['Youth', 'YonngAdult', 'MiddleAged', 'Senior']
data = pd.cut(ages, bins, labels=group_names)
print(data)
print(pd.value_counts(data))# 如果将箱子的边替代为箱子的个数,pandas将根据数据中的最小值和最大值计算出等长的箱子
data2 = np.random.rand(20)
print(pd.cut(data2, 4, precision=2))   # precision=2 将十进制精度限制在2位# qcut是另一个分箱相关的函数, 基于样本分位数进行分箱。 取决于数据的分布,使用cut不会使每个箱子具有相同数据数量的数据点,而qcut,使用
# 样本的分位数,可以获得等长的箱
data3 = np.random.randn(1000)   # 正太分布
cats = pd.qcut(data3, 4)
print(pd.value_counts(cats))

结果如下:

5. 数据转换

数据转换的方式, 数据归一化(MinMaxScaler), 标准化(StandardScaler), 对数变换(log1p), 转换数据类型(astype), 独热编码(OneHotEncoder),标签编码(LabelEncoder), 修复偏斜特征(boxcox1p)等。关于这里面的一些操作,我有几篇博客已经描述过了,后面会给出链接, 这里只针对这个问题进行阐述:

  1. 数值特征这里归一化一下, 因为我发现数值的取值范围相差很大

    minmax = MinMaxScaler()
    num_data_minmax = minmax.fit_transform(num_data)
    num_data_minmax = pd.DataFrame(num_data_minmax, columns=num_data.columns, index=num_data.index)
    
  2. 类别特征独热一下

    """类别特征某些需要独热编码一下"""
    hot_features = ['bodyType', 'fuelType', 'gearbox', 'notRepairedDamage']
    cat_data_hot = pd.get_dummies(cat_data, columns=hot_features)
    
  3. 关于高势集特征model,也就是类别中取值个数非常多的, 一般可以使用聚类的方式,然后独热,这里就采用了这种方式:

    from scipy.cluster.hierarchy import linkage, dendrogram
    #from sklearn.cluster import AgglomerativeClustering
    from sklearn.cluster import KMeansac = KMeans(n_clusters=3)
    ac.fit(model_price_data)model_fea = ac.predict(model_price_data)
    plt.scatter(model_price_data[:,0], model_price_data[:,1], c=model_fea)cat_data_hot['model_fea'] = model_fea
    cat_data_hot = pd.get_dummies(cat_data_hot, columns=['model_fea'])
    

    效果如下:

    但是我发现KMeans聚类不好,可以尝试层次聚类试试,并且这个聚类数量啥的应该也会有影响,这里只是提供一个思路,我觉得这个特征做的并不是太好,还需改进。

数据清洗和转换的技巧就描述到这里,但是不能说是结束,因为这一块的知识没有什么固定的套路,我们得学会发散思维,然后不断的试错探索,这里只整理的部分方法。

trick3: 通过上面的方式处理完了数据之后,我们要记得保存一份数据到文件,这样下次再用的时候,就不用再花功夫处理,直接导入清洗后的数据,进行后面的特征工程部分即可。一定要养成保存数据到文件的习惯。

6. 总结

今天主要是整理一些数据清洗和转换的技巧,包括异常处理,缺失处理,数据分箱和数据转换操作, 这些技巧也同样不仅适用于这个比赛,还可以做迁移。依然是利用思维导图把知识进行串联:

关于经验的话,数据清洗和转换这一块只能整理一些方法,然后需要针对具体的数据不断的尝试, 只有亲自尝试才能获得更多的成长,没有什么固定的套路或者说方式,没有什么循规蹈矩的规定,当然也希望不要把思维限定在上面的这些方法中,因为毕竟目前我也是小白,这些只是我目前接触到的一些,所以肯定不会包括所有的方式,希望有大佬继续补充和交流。

另外,我觉得分享本身就是一种成长,因为分享知识在帮助自己加深记忆的同时,也是和别人进行思维碰撞的机会,这个过程中会遇到很多志同道合的人一起努力,一起进步,这样比一个人要好的多。 一个人或许会走的很快,但是一群人才能走的更远,所以希望这个系列能帮助更多的伙伴,也希望学习路上可以遇到更多的伙伴, 你看,天上太阳正晴,我们一起吧

零基础数据挖掘入门系列(三) - 数据清洗和转换技巧相关推荐

  1. 零基础数据挖掘入门系列(四) - 特征工程

    思维导图:零基础入门数据挖掘的学习路径 1. 写在前面 零基础入门数据挖掘是记录自己在Datawhale举办的数据挖掘专题学习中的所学和所想, 该系列笔记使用理论结合实践的方式,整理数据挖掘相关知识, ...

  2. 零基础数据挖掘入门系列(二) - 数据的探索性(EDA)分析

    思维导图:零基础入门数据挖掘的学习路径 1. 写在前面 零基础入门数据挖掘是记录自己在Datawhale举办的数据挖掘专题学习中的所学和所想, 该系列笔记使用理论结合实践的方式,整理数据挖掘相关知识, ...

  3. 零基础数据挖掘入门系列(一) - 赛题理解

    思维导图:零基础入门数据挖掘的学习路径 1. 写在前面 零基础入门数据挖掘系列是记录自己在Datawhale举办的数据挖掘专题学习中的所学和所想, 该系列笔记使用理论结合实践的方式,整理数据挖掘相关知 ...

  4. 零基础数据挖掘入门系列(五) - 模型建立与调参

    思维导图:零基础入门数据挖掘的学习路径 1. 写在前面 零基础入门数据挖掘是记录自己在Datawhale举办的数据挖掘专题学习中的所学和所想, 该系列笔记使用理论结合实践的方式,整理数据挖掘相关知识, ...

  5. 【Python零基础快速入门系列 | 03】AI数据容器底层核心之Python列表

    • 这是机器未来的第7篇文章 原文首发地址:https://blog.csdn.net/RobotFutures/article/details/124957520 <Python零基础快速入门 ...

  6. 视频教程-零基础JS入门系列课程(2)之JS语法基础精讲-JavaScript

    零基础JS入门系列课程(2)之JS语法基础精讲 螺钉课堂讲师,擅长Vue.React.ReactNative.NodeJS等前端框架及技术 邓老师 ¥59.00 立即订阅 扫码下载「CSDN程序员学院 ...

  7. 【Python零基础快速入门系列 | 07】浪漫的数据容器:成双成对之字典

    这是机器未来的第11篇文章 原文首发链接:https://blog.csdn.net/RobotFutures/article/details/125038890 <Python零基础快速入门系 ...

  8. 零基础前端入门系列(八)

    CSS精讲(二) CSS体系知识介绍 选择器优先级 为什么关注优先级 优先级处理原则 !important 和 内联样式 样式继承 一个继承的例子 继承属性和非继承属性 范例 选择器权重计算 范例1 ...

  9. Python编程学习第一篇——Python零基础快速入门(三)——10行代码画朵花

    上一节讲了一些Python编程的一些基础知识,从这节开始,我们将跟随一些实际的小程序示例,进入正式的编程学习.       下面我们就来介绍一下今天这个只有10行代码的小程序,先来看一下它的运行效果, ...

最新文章

  1. apache 2.4 配置php,Apache2.4 PHP 配置
  2. jquery条件选择多个元素(与、或者)
  3. 如何用python画数据图-python怎么对动态数据在同一张图上画出来
  4. Spring+SpringMVC+MyBatis深入学习及搭建(十四)——SpringMVC和MyBatis整合
  5. python 如何理解 numpy 数组操作中的 axis 参数?
  6. @EnableAspectAutoJAutoProxy_exposeProxy属性
  7. 小叮咚切分词方法加入sourceforge.net中WebLucene分词模块
  8. bootstrap -- 一个标签中,同时有 col-xs , col-sm , col-md , col-lg
  9. stm32 led屏控制卡_室内LED显示屏如何安装?
  10. 信息学奥赛一本通 1066:满足条件的数累加 | OpenJudge NOI 1.5 10
  11. 树莓派教程 - 1.1 树莓派GPIO库wiringPi 硬件PWM可调频率
  12. mysql基准测试总结 一
  13. 修改鼠标手形 闪烁 在填写文字内容后也一直在闪烁
  14. log4j使用和配置详解
  15. 局域网内ip冲突引起的怪异现象
  16. html ul做成表格,HTML+CSS入门 ul打造表格样式解析
  17. 微信域名防封的3种方案
  18. 谁说技术男没有人文情怀:当旅途遇到生命赞歌
  19. thinkpadt410接口介绍_独家:ThinkPad T410与T400之细节对比
  20. ThinkPhp6+Vue+ElementUI前后端分离在线教程

热门文章

  1. 黑苹果传统BIOS引导安装
  2. 基于 Prometheus 的边缘计算监控实践
  3. C语言常见字符串处理string.h库函数strstr、strchr、strcat、strcmp、strcpy、strlen的介绍
  4. vue-seamless-scroll 从入坑到放弃
  5. Vue3.2——vue-seamless-scroll的使用
  6. 我学编程全靠B站了,真香(第一期)
  7. 软件开发模型、软件设计模式、软件架构风格
  8. [激光原理与应用-65]:激光器-器件 - 多模光纤(宽频光纤)、单模光纤的原理与区别
  9. 亥姆霍兹线圈、Helmholtz线圈、磁场线圈
  10. 【磁盘调度算法】磁盘调度模拟的设计与分析