一些数据挖掘算法,特别是某些分类算法,如ID3算法、Apriori算法等,要求数据是分类属性形式。这样,常常需要将连续属性变换成分类属性,即连续属性离散化。
连续属性离散化就是在数据的取值范围内设定若干个离散的划分点,将取值范围划分为一些离散化的区间,最后用不同的符号或整数值代表落在每个子区间中的数据值。所以,离散化涉及两个子任务:确定分类数以及如何将连续属性值映射到这些分类值。
常用的离散化方法有等宽法、等频法和(一维)聚类。
(1)等宽法
将属性的值域分成具有相同宽度的区间,区间的个数由数据本身的特点决定或者用户指定,类似于制作频率分布表。
(2)等频法
将相同数量的记录放进每个区间。 这两种方法简单,易于操作,但都需要人为规定划分区间的个数。同时,等宽法的缺点在于它对离群点比较敏感,倾向于不均匀地把属性值分布到各个区间。有些区间包含许多数据,而另外一些区间的数据极少,这样会严重损坏建立的决策模型。等频法虽然避免了上述问题的产生,却可能将相同的数据值分到不同的区间,以满足每个区间中固定的数据个数。
(3)根据业务手动离散化,比如把年龄根据少年、青年、中年、老年进行手动区分离散化等
(4)基于聚类分析的方法 (我一般不会直接上来就用聚类离散化)
一维聚类方法包括两个步骤:首先将连续属性的值用聚类算法(如K-Means算法)进行聚类,然后再将聚类得到的簇进行处理,合并到一个簇的连续属性值做同一标记。聚类分析的离散化方法也需要用户指定簇的个数,从而决定产生的区间数。
在python中一说到离散化,所有人都能想到cut和qcut还有类似于sql中case when这样的手动离散化操作。可大部分人对cut和qcut的区别总是模棱两可说不清楚,我看到很多人说cut是用来做等宽离散化的,qcut是用来做等频离散化的,这样的理解显然是错误。其实直接查看官方cut和qcut的API既可以明了的知道两者的区别。
将数据分箱到指定的间隔中。
api:
pandas.cut(x, bins, right=True, labels=None, retbins=False, precision=3, include_lowest=False, duplicates='raise', ordered=True)[source]
常用参数讲解:
x :要被分箱的数据,必须是一维数组
bins:如果做等宽离散化,则写一个int数值,如果要手动指定分箱间隔则设定要给间隔列表
right:默认是True,即左开右闭(一般默认即可)
labels:设置分箱的标签,一般设为False,显示出来就是0、1、2…,也可以自己设定要给列表,写上对应箱子的名称
precision:精度,即数值保留几位小数
可以做手动设定间隔、等宽、等频离散化
(1)手动设定间隔
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]}) # 创建模拟年龄数据
bins = [18, 25, 35, 60, 100] # 手动设置分箱间隔
df['age_bins'] = pd.cut(df['age'],bins,labels=False) # 使用cut离散化
df
(2)等宽离散化
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]}) # 创建模拟年龄数据
k=4 # 设置箱子宽度
df['age_bins'] = pd.cut(df['age'],k,labels=False) # 使用cut进行等宽离散化
df
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]}) # 创建模拟年龄数据
df = df['age'].copy()
#等频率离散化
k=4
w = [1.0*i/k for i in range(k+1)]
w = df.describe(percentiles = w)[4:4+k+1] # 使用describe函数自动计算分位数
w[0] = w[0]*(1-1e-10)
df= pd.cut(df,k,labels=False) # 使用cut进行离散化
df
不少朋友看到这里一定很郁闷,这个cut做等频离散化咋就这么麻烦啊,是啊,连你都能知道这个非常麻烦,官网又岂能不知道呢?所以才有qcut出场的机会嘛。
将数据基于分位数进行离散化。(不要问我分位数是什么,不明白的自行百度)
api:
pandas.qcut(x, q, labels=None, retbins=False, precision=3, duplicates='raise')
q:分位数,比如分为为10、4等等,也可在列表中写上对应的百分数,如四分位数[0,0.25,0.5,0.75,1]
其他参数和cut含义基本一致。
其实qcut也可以实现和cut一样的功能如:手动设定间隔、等宽、等频离散化,只不过它在做等频离散化的时候更方便,其他的离散化一律不用考虑qcut
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]}) # 创建模拟年龄数据
k=4 #设置等频个数
df = pd.qcut(df['age'],k,labels=False) # qcut离散化
df
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]}) # 创建模拟年龄数据
q = [0,0.25,0.5,0.75,1]
df = pd.qcut(df['age'],q,labels=False) # qcut离散化
df
有很多种实现的方式
方式一:
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32,200,-100]}) # 创建模拟年龄数据
df['b_age'] = -1
df['b_age'][(df['age']<=25) & (df['age']>=18)] = 0
df['b_age'][(df['age']> 25) & (df['age']<= 35)] = 1
df['b_age'][(df['age']> 35) & (df['age']<= 60)] = 2
df['b_age'][(df['age']> 60) & (df['age']<= 100)] = 3
df
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32,200,-100]}) # 创建模拟年龄数据
df['b_age'] = -1
df.loc[df['age'].between(18,25,inclusive=True),'b_age'] = 0
df.loc[df['age'].between(25,35,inclusive=True),'b_age'] = 1
df.loc[df['age'].between(35,60,inclusive=True),'b_age'] = 2
df.loc[df['age'].between(60,100,inclusive=True),'b_age'] = 3
df
方式三:(个人比较喜欢,因为这种类似于直接写python函数)
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32,200,-100]}) # 创建模拟年龄数据
def age_cut(age):
if age >= 18 and age <= 25:
b_age = 0
elif age > 25 and age <= 35:
b_age = 1
elif age > 35 and age <= 60:
b_age = 2
elif age > 60 and age <= 100:
b_age = 3
else:
b_age = -1
return b_age
df['age'] = df['age'].apply(lambda x:age_cut(x))
df
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32,200,-100]}) # 创建模拟年龄数据
df['b_age'] = np.where(
df['age'].between(18,25,inclusive=True),
0,
np.where(df['age'].between(25,35,inclusive=True),1,
np.where(df['age'].between(35,60,inclusive=True),2,
np.where(df['age'].between(60,100,inclusive=True),3,-1)
)
)
)
df
df = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32,200,-100]}) # 创建模拟年龄数据
df['b_age'] = np.select(
[
df['age'].between(18, 25, inclusive=True),
df['age'].between(25, 36, inclusive=True),
df['age'].between(35, 60,inclusive=True),
df['age'].between(60, 100,inclusive=True)
],
[
0,
1,
2,
3
],
default=-1
)
df
data = pd.DataFrame({'age':[20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]}) # 创建模拟年龄数据
data = data['age'].copy()
k = 4
d1 = pd.cut(data, k, labels = range(k)) # 等宽离散化,各个类比依次命名为0,1,2,3
#等频率离散化
w = [1.0*i/k for i in range(k+1)]
w = data.describe(percentiles = w)[4:4+k+1] # 使用describe函数自动计算分位数
w[0] = w[0]*(1-1e-10)
d2 = pd.cut(data, w, labels = range(k))
from sklearn.cluster import KMeans # 引入KMeans
kmodel = KMeans(n_clusters = k, n_jobs = 4) # 建立模型,n_jobs是并行数,一般等于CPU数较好
kmodel.fit(np.array(data).reshape((len(data), 1))) # 训练模型
c = pd.DataFrame(kmodel.cluster_centers_).sort_values(0) # 输出聚类中心,并且排序(默认是随机序的)
w = c.rolling(2).mean() # 相邻两项求中点,作为边界点
w = w.dropna()
w = [0] + list(w[0]) + [data.max()] # 把首末边界点加上
d3 = pd.cut(data, w, labels = range(k))
def cluster_plot(d, k): # 自定义作图函数来显示聚类结果
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.figure(figsize = (8, 3))
for j in range(0, k):
plt.plot(data[d==j], [j for i in d[d==j]], 'o')
plt.ylim(-0.5, k-0.5)
return plt
cluster_plot(d1, k).show()
cluster_plot(d2, k).show()
cluster_plot(d3, k).show()
相信看完这篇博客,对于连续数据离散化的问题应该可以很容易的解决了。