Python数据分析与数据化运营

3.10 离散化,对运营数据做逻辑分层

Author
宋天龙
发布于 2018-06-28
2749 次阅读
0 次赞
0 次分享
3.10 离散化,对运营数据做逻辑分层
AI 智能核心导读

数据离散化是将连续或复杂数据映射到有限分类区间的操作,旨在节约计算资源、满足算法需求、降低异常值影响并提升业务可解释性。常见场景涵盖时间、多值离散、连续数据的离散化及二值化处理。在Python实操中,主要借助Pandas(如cut/qcut分箱)与Scikit-learn(如KMeans聚类、Binarizer二值化)等工具,实现数据的逻辑分层与特征工程转换。

说明:本文是《Python 数据分析与数据化运营》中的“3.10 离散化,对运营数据做逻辑分层”。

-----------------------------下面是正文内容--------------------------

所谓离散化,就是把无限空间中有限的个体映射到有限的空间中。数据离散化操作大多是针对连续数据进行的,处理之后的数据值域分布将从连续属性变为离散属性,这种属性一般包含 2 个或 2 个以上的值域。

离散化处理的必要性:

  • 节约计算资源,提高计算效率。
  • 算法模型(尤其是分类模型)的计算需要。虽然很多模型,例如决策树可以支持输入连续型数据,但是决策树本身会先将连续型数据转化为离散型数据,因此离散化转换是一个必要步骤。
  • 增强模型的稳定性和准确度。数据离散化之后,处于异常状态的数据不会明显的突出异常特征,而是会被划分为一个子集中的一部分,因此异常数据对模型的影响会大大降低,尤其是基于距离计算的模型(例如 K 均值、协同过滤等)效果明显。
  • 特定数据处理和分析的必要步骤,尤其在图像处理方面应用广泛。大多数图像做特征检测(以及其他基于特征的分析)时,都需要先将图像做二值化处理,二值化也是离散化的一种。
  • 模型结果应用和部署的需要。如果原始数据的值域分布过多,或值域划分不符合业务逻辑,那么模型结果将很难被业务理解并应用。

注意 离散化通常针对连续数据进行处理,但是在很多情况下也可以针对已经是离散化的数据进行处理。这种场景一般是离散数据本身的划分过于复杂、琐碎甚至不符合业务逻辑,需要进一步做数据聚合或重新划分。

3.10.1 针对时间数据的离散化

针对时间数据的离散化主要用于以时间为主要特征的数据集中和粒度转换,离散化处理后将分散的时间特征转换为更高层次的时间特征。

在带有时间的数据集中,时间可能作为行记录的序列,也可能作为列(维度)记录数据特征。

常见的针对时间数据的离散化操作两类:

  • 针对一天中的时间离散化。一般是将时间戳转换为秒、分钟、小时或上下午。
  • 针对日粒度以上数据的离散化。一般是将日期转化为周数、周几、月、工作日或休息日、季度、年等。

针对时间数据的离散化可以将细粒度的时间序列数据离散化为粗粒度的三类数据:

  • 离散化为分类数据,例如上、下午
  • 离散化为顺序数据,例如周一、周二、周三
  • 离散化为数值型数据,例如一年有 52 个周,周数是数值型数据

3.10.2 针对多值离散数据的离散化

针对多值离散数据的离散化指的是要进行离散化处理的数据本身不是数值型数据,而是分类或顺序数据。

例如,用户收入变量的值原来可能划分为 10 个区间,根据新的建模需求,只需要划分为 4 个区间,那么就需要对原来的 10 个区间进行合并。

多值离散数据要进行离散化还有可能是划分的逻辑有问题,需要重新划分,这种问题通常都是由于业务逻辑的变更,导致在原始数据中存在不同历史数据下的不同值域定义。

例如,用户活跃度变量的值,原来分为 3 个类别:高价值、中价值和低价值。根据业务发展的需要,新的用户活跃度变量的值定义为:高价值、中价值、低价值和负价值。此时需要对不同类别的数据进行统一规则的离散化处理。

3.10.3 针对连续数据的离散化

针对连续数据的离散化是主要的离散化应用,在分类或关联分析中应用尤其广泛,这些算法的结果以类别或属性标识为基础,而非数值标记。例如,分类规则的典型结果逻辑是:

如果 变量1 = 值1 并且 变量2 = 值2 那么 目标变量(T)

连续数据的离散化结果可以分为两类:

一类是将连续数据划分为特定区间的集合,例如 {(0,10],(10,20],(20,50],(50,100]}

一类是将连续数据划分为特定类,例如类 1、类 2、类 3

常见实现连续数据化离散化的方法包括:

  • 分位数法:使用四分位、五分位、十分位等分位数进行离散化处理,这种方法简单易行。
  • 距离区间法:可使用等距区间或自定义区间的方式进行离散化,这种操作更加灵活且能满足自定义需求,另外该方法(尤其是等距区间)可以较好的保持数据原有的分布。
  • 频率区间法:将数据按照不同数据的频率分布进行排序,然后按照等频率或指定频率离散化,这种方法会把数据变换成均匀分布,好处是各区间的观察值是相同的,不足是已经改变了原有数据的分布状态。
  • 聚类法:例如使用 K 均值将样本集分为多个离散化的簇。
  • 卡方:通过使用基于卡方的离散化方法,找出数据的最佳临近区间并合并,形成较大的区间。

3.10.4 针对连续数据的二值化

在很多场景下,我们可能需要将变量特征进行二值化操作:每个数据点跟阀值比较,大于阀值设置为固定值(例如 1),小于阀值设置为固定值(例如 0),然后得到一个只拥有两个值域的二值化数据集。

提示 二值化后的值的设置取决于场景,例如大部分数据的处理可以设置为 1 或 0;在图像处理中则会设置为 0 或 255。有关如何设置没有固定要求,只要满足后续数据和结果的识别、理解和应用即可。

二值化应用的前提是数据集中所有的属性值所代表的含义相同或类似,例如读取图像所获得数据集的是颜色值的集合(具体颜色模式取决于读取图像时的模式设置,例如灰度、RGB 等),因此每一个数据点都代表颜色,此时可对整体数据集做二值化处理。某些情况下,也可能只针对特定列做二值化,这样不同列的属性虽然不同,但同一列内产生的二值化结果却仍然具有比较和分类意义。

3.10.5 代码实操:Python 数据离散化处理

本示例中,将使用 Pandassklearn 进行离散化相关处理。

数据源文件 data7.txt 位于“附件-chapter3”中,默认工作目录为“附件-chapter3”(如果不是,请 cd 切换到该目录下,否则会报“IOError: File data7.txt does not exist”)。

python
1import pandas as pd 2from sklearn.cluster import KMeans 3from sklearn import preprocessing 4 5# 读取数据 6df = pd.read_table('data7.txt', names=['id', 'amount', 'income', 'datetime', 'age']) # 读取数据文件 7print (df.head(5)) # 打印输出前5条数据 8 9# 针对时间数据的离散化 10for i, signle_data in enumerate(df['datetime']): # 循环得到索引和对应值 11 single_data_tmp = pd.to_datetime(signle_data) # 将时间转换为datetime格式 12 df['datetime'][i] = single_data_tmp.weekday() # 离散化为周几 13print (df.head(5)) # 打印输出前5条数据 14 15# 针对多值离散数据的离散化 16map_df = pd.DataFrame([['0-10', '0-40'], ['10-20', '0-40'], ['20-30', '0-40'], ['30-40', '0-40'], ['40-50', '40-80'], ['50-60', '40-80'], ['60-70', '40-80'], ['70-80', '40-80'], ['80-90', '>80'], ['>90', '>80']], columns=['age', 'age2']) # 定义一个要转换的新区间 17df_tmp = df.merge(map_df, left_on='age', right_on='age', how='inner') # 数据框关联匹配 18df = df_tmp.drop('age', 1) # 丢弃名为age的列 19print (df.head(5)) # 打印输出前5条数据 20 21# 针对连续数据的离散化 22# 方法1:自定义分箱区间实现离散化 23bins = [0, 200, 1000, 5000, 10000] # 自定义区间边界 24df['amount1'] = pd.cut(df['amount'], bins) # 使用边界做离散化 25print (df.head(5)) # 打印输出前5条数据 26# 方法2 使用聚类法实现离散化 27data = df['amount'] # 获取要聚类的数据,名为amount的列 28data_reshape = data.reshape((data.shape[0], 1)) # 转换数据形状 29model_kmeans = KMeans(n_clusters=4, random_state=0) # 创建KMeans模型并指定要聚类数量 30keames_result = model_kmeans.fit_predict(data_reshape) # 建模聚类 31df['amount2'] = keames_result # 新离散化的数据合并到原数据框 32print (df.head(5)) # 打印输出前5条数据 33# 方法3:使用4分位数实现离散化 34df['amount3'] = pd.qcut(df['amount'], 4, labels=['bad', 'medium', 'good', 'awesome']) # 按四分位数进行分隔 35df = df.drop('amount', 1) # 丢弃名为amount的列 36print (df.head(5)) # 打印输出前5条数据 37 38# 针对连续数据的二值化 39binarizer_scaler = preprocessing.Binarizer(threshold=df['income'].mean()) # 建立Binarizer模型对象 40income_tmp = binarizer_scaler.fit_transform(df['income']) # Binarizer标准化转换 41income_tmp.resize(df['income'].shape) # 转换数据形状 42df['income'] = income_tmp # Binarizer标准化转换 43print (df.head(5)) # 打印输出前5条数据

示例代码以空行分为 6 个部分。

第一部分导入库。

代码中用到了 Pandassklearn。前者主要用来做文件读取、切块、时间处理、关联合并和部分离散化操作;后者主要用来做二值化和聚类建模离散化。

第二部分使用 Pandas 的 read_table 方法读取数据文件,并指定列名。

数据集为 100 行 5 列的数据框,包含 idamountincomedatetimeage 5 个字段。

原始数据前 5 条数据如下:

code
1 id amount income datetime age 20 15093 1390 10.40 2017-04-30 19:24:13 0-10 31 15062 4024 4.68 2017-04-27 22:44:59 70-80 42 15028 6359 3.84 2017-04-27 10:07:55 40-50 53 15012 7759 3.70 2017-04-04 07:28:18 30-40 64 15021 331 4.25 2017-04-08 11:14:00 70-80

第三部分针对时间数据的离散化。

该过程中,首先通过 enumerate 方法获得要循环的日期索引和对应值,在每个循环中通过 pandasto_datetime 方法将字符串转换为 datetime 格式,并直接使用 weekday 方法获取周几,新获取的周几的数据直接替换原数据框的时间戳。最后打印输出离散化后的结果:

code
1 id amount income datetime age 20 15093 1390 10.40 6 0-10 31 15062 4024 4.68 3 70-80 42 15028 6359 3.84 3 40-50 53 15012 7759 3.70 1 30-40 64 15021 331 4.25 5 70-80

从结果中看到,datetime 列的值由原来的日期时间格式,转换为由 0-6 组成的周几值,0 代表周一,6 代表周日。

提示 该部分由于 to_datetime 无法针对整个数据框或 Series 做整体转换,因此需要写循环执行。另外,该方法执行效率非常低,笔者的环境下大概需要 5 秒左右,如果更大数据量其效率会更低。

第四部分针对多值离散数据的离散化。

该过程中先通过 dp.Dataframe 定义一个新的转换区间,用来将原数据映射到新区间来;然后通过 merge 方法将原数据框和新定义的数据框进行关联,关联的两个 key(left_onleft_on)分别是 ageage,关联模式为 inner(内关联);接着我们通过 drop 方法去除原始数据框中的 age 列,只保留新的转换后的区间。得到如下结果:

code
1 id amount income datetime age2 20 15093 1390 10.40 6 0-40 31 15064 7952 4.40 0 0-40 42 15080 503 5.72 5 0-40 53 15068 1668 3.19 5 0-40 64 15019 6710 3.20 0 0-40

上述返回结果中,age2 列是转换后新的列,原来的分类区间已经被映射到新的类别区间。

第五部分针对连续数据的离散化。

该部分包含 3 种常用方法。

方法 1:自定义分箱区间实现离散化。

首先定义一个自定义区间边界列表,用来将数据做划分;然后使用 pandascut 方法做离散化;并将结果生成一个新的名为 amount1 的列追加到原数据框,打印输出结果如下:

code
1 id amount income datetime age2 amount1 20 15093 1390 10.40 6 0-40 (1000, 5000] 31 15064 7952 4.40 0 0-40 (5000, 10000] 42 15080 503 5.72 5 0-40 (200, 1000] 53 15068 1668 3.19 5 0-40 (1000, 5000] 64 15019 6710 3.20 0 0-40 (5000, 10000]

上述返回结果中,amount1 列是转换后产生的列,每行对应的区间左侧是区间开始值(不包含),右侧是区间结束值(包含);

除了显示区间外,cut 方法还可以通过自定义 labels(值为列表的形式,用来表示不同分类区间的标签)用标签代替上述区间,例如 labels=['bad','medium','good','awesome'],那么显示在 amount1 里面的数据就是对应 lables 里面的字符串。这里没有将原始 amount 列删除,原因是下面的方法中还会用到该列原始数据。

方法 2:使用聚类法实现离散化。

该过程使用了 sklearn.clusterKMeans 算法实现。首先通过指定数据框的列名获得要建模的数据列;然后将数据的形状进行转换,否则算法会认为该数据只有 1 行(通过 pands 指定列名获得的数据默认都没有列值,例如示例中的 data 的形状是 (100,),因此大多数场景下作为输入变量都需要做形状转换);接着创建 KMeans 模型并指定要聚类数量为 4 表并设置初始化随机种子为固定值 0(否则每次得到的聚类结果很可能基本都不一样),并使用 fit_predict 方法直接建模输出结果;最后将新的结果追加到原始数据框中,最终打印输出前 5 条结果如下:

code
1 id amount income datetime age2 amount1 amount2 20 15093 1390 10.40 6 0-40 (1000, 5000] 2 31 15064 7952 4.40 0 0-40 (5000, 10000] 1 42 15080 503 5.72 5 0-40 (200, 1000] 2 53 15068 1668 3.19 5 0-40 (1000, 5000] 2 64 15019 6710 3.20 0 0-40 (5000, 10000] 1

上述返回结果中,amount2 列是转换后产生的列,列的值域是 0/1/2 代表三类数据。

方法 3:使用 4 分位数实现离散化。

该过程中,使用了 pandasqcut 方法指定做 4 分位数分隔,同时设置不同四分位得到的区间的标签分别为 ['bad','medium','good','awesome'];将得到的结果以列名为 amount3 追加到原始数据框中;然后通过 drop 方法丢弃名为 amount 的列,打印输出结果如下:

code
1 id income datetime age2 amount1 amount2 amount3 20 15093 10.40 6 0-40 (1000, 5000] 2 bad 31 15064 4.40 0 0-40 (5000, 10000] 1 awesome 42 15080 5.72 5 0-40 (200, 1000] 2 bad 53 15068 3.19 5 0-40 (1000, 5000] 2 bad 64 15019 3.20 0 0-40 (5000, 10000] 1 awesome

上述返回结果中,amount3 列是转换后产生的列,其结果构成与方法 1 完全相同,差异仅在于区间边界不同。

提示 使用方法 1 中的 cut 方法也能实现,二者的区别是 cut 方法通常是自定义分割区间,而 qcut 则是应用标准分位数方法。

第六部分做特征二值化处理。

先建立建立 Binarizer 模型对象,然后使用 fit_transform 方法进行二值化转换,阀值设置为该列的均值;转换后得到的数据的形状是 (1,100),通过 resize 更改为与原始数据框 income 列相同的尺寸,并将结果直接替换原始列的值,最后打印输出前 5 条数据结果如下:

code
1 id income datetime age2 amount1 amount2 amount3 20 15093 1.0 6 0-40 (1000, 5000] 2 bad 31 15064 1.0 0 0-40 (5000, 10000] 1 awesome 42 15080 1.0 5 0-40 (200, 1000] 2 bad 53 15068 0.0 5 0-40 (1000, 5000] 2 bad 64 15019 0.0 0 0-40 (5000, 10000] 1 awesome

上述返回结果中,income 列的值已经离散为由 0 和 1 组成的二值化数据。

上述过程中,主要需要考虑的关键点是:如何根据不同的数据特点和建模需求选择最合适的离散化方式,因为离散化方式的是否合理会直接影响后续数据建模和应用效果。除了本节介绍的相关类型的转换离散化制约外,不同模型对于离散化的约束如下:

  • 使用决策树时往往倾向于少量的离散化区间,原因是过多的离散化将使得规则过多受到碎片区间的影响。
  • 关联规则需要对所有特征一起离散化,原因是关联规则关注的是所有特征的关联关系,如果对每个列单独离散化将失去整体规则性。

代码实操小结:本小节示例中,主要用了几个知识点:

  • 通过 Pandasread_table 方法读取文本数据文件,并指定列名
  • 通过 Pandasto_datetime 方法将字符串转换为 datetime 格式,使用 weekday 提取周几数据
  • 使用 Pandashead 方法只展示前 n 条数据
  • 通过 Pandasmerge 方法合并多个数据框,实现类似 SQL 的数据关联查询
  • 使用 Pandasdrop 方法丢弃特定数据列
  • 使用 Pandascutqcut 方法实现基于自定义区间和分位数方法的数据离散化
  • 使用 sklearn.clusterKMeans 方法实现聚类分析
  • 使用 shape 方法获取矩阵形状并使用 resize 方法对矩阵实现形状转换
  • 使用 sklearn.preprocessingBinarizer 方法做二值化处理
分享
最后修订: 2018-06-28