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


目录:

说明:本文是《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数据离散化处理

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

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

import pandas as pd
from sklearn.cluster import KMeans
from sklearn import preprocessing

# 读取数据
df = pd.read_table('data7.txt', names=['id', 'amount', 'income', 'datetime', 'age'])  # 读取数据文件
print (df.head(5))  # 打印输出前5条数据

# 针对时间数据的离散化
for i, signle_data in enumerate(df['datetime']):  # 循环得到索引和对应值
    single_data_tmp = pd.to_datetime(signle_data)  # 将时间转换为datetime格式
    df['datetime'][i] = single_data_tmp.weekday()  # 离散化为周几
print (df.head(5))  # 打印输出前5条数据

# 针对多值离散数据的离散化
map_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'])  # 定义一个要转换的新区间
df_tmp = df.merge(map_df, left_on='age', right_on='age', how='inner')  # 数据框关联匹配
df = df_tmp.drop('age', 1)  # 丢弃名为age的列
print (df.head(5))  # 打印输出前5条数据

# 针对连续数据的离散化
# 方法1:自定义分箱区间实现离散化
bins = [0, 200, 1000, 5000, 10000]  # 自定义区间边界
df['amount1'] = pd.cut(df['amount'], bins)  # 使用边界做离散化
print (df.head(5))  # 打印输出前5条数据
# 方法2 使用聚类法实现离散化
data = df['amount']  # 获取要聚类的数据,名为amount的列
data_reshape = data.reshape((data.shape[0], 1))  # 转换数据形状
model_kmeans = KMeans(n_clusters=4, random_state=0)  # 创建KMeans模型并指定要聚类数量
keames_result = model_kmeans.fit_predict(data_reshape)  # 建模聚类
df['amount2'] = keames_result  # 新离散化的数据合并到原数据框
print (df.head(5))  # 打印输出前5条数据
# 方法3:使用4分位数实现离散化
df['amount3'] = pd.qcut(df['amount'], 4, labels=['bad', 'medium', 'good', 'awesome'])  # 按四分位数进行分隔
df = df.drop('amount', 1)  # 丢弃名为amount的列
print (df.head(5))  # 打印输出前5条数据

# 针对连续数据的二值化
binarizer_scaler = preprocessing.Binarizer(threshold=df['income'].mean())  # 建立Binarizer模型对象
income_tmp = binarizer_scaler.fit_transform(df['income'])  # Binarizer标准化转换
income_tmp.resize(df['income'].shape)  # 转换数据形状
df['income'] = income_tmp  # Binarizer标准化转换
print (df.head(5))  # 打印输出前5条数据

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

第一部分导入库。

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

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

数据集为100行5列的数据框,包含id、amount、income、datetime和age5个字段。

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

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

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

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

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

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

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

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

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

      id  amount  income datetime  age2
0  15093    1390   10.40        6  0-40
1  15064    7952    4.40        0  0-40
2  15080     503    5.72        5  0-40
3  15068    1668    3.19        5  0-40
4  15019    6710    3.20        0  0-40

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

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

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

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

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

      id  amount  income datetime  age2        amount1
0  15093    1390   10.40        6  0-40   (1000, 5000]
1  15064    7952    4.40        0  0-40  (5000, 10000]
2  15080     503    5.72        5  0-40    (200, 1000]
3  15068    1668    3.19        5  0-40   (1000, 5000]
4  15019    6710    3.20        0  0-40  (5000, 10000]

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

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

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

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

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

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

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

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

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

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

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

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

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

      id  income datetime  age2        amount1  amount2  amount3
0  15093     1.0        6  0-40   (1000, 5000]        2      bad
1  15064     1.0        0  0-40  (5000, 10000]        1  awesome
2  15080     1.0        5  0-40    (200, 1000]        2      bad
3  15068     0.0        5  0-40   (1000, 5000]        2      bad
4  15019     0.0        0  0-40  (5000, 10000]        1  awesome

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

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

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

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

  • 通过Pandas的read_table方法读取文本数据文件,并指定列名
  • 通过Pandas的to_datetime方法将字符串转换为datetime格式,使用weekday提取周几数据
  • 使用Pandas的head方法只展示前n条数据
  • 通过Pandas的merge方法合并多个数据框,实现类似SQL的数据关联查询
  • 使用Pandas的drop方法丢弃特定数据列
  • 使用Pandas的cut和qcut方法实现基于自定义区间和分位数方法的数据离散化
  • 使用sklearn.cluster的KMeans方法实现聚类分析
  • 使用shape方法获取矩阵形状并使用resize方法对矩阵实现形状转换
  • 使用sklearn.preprocessing的Binarizer方法做二值化处理