Python|数据透视表+cut切分+Kmeans聚类

目录

  • 1  背景
  • 2  数据预处理
    • 2.1  读入数据
    • 2.2  删去缺失值
  • 3  需求1:把娱乐/明星八卦单独拉出来
    • 3.1  检验一下人数
    • 3.2  针对score进行降序排列
    • 3.3  看分布
    • 3.4  分区间段进行统计人数
    • 3.5  直接分成三块
    • 3.6  等人数的分块
    • 3.7  给这部分进行打标签
  • 4  需求2:聚类
    • 4.1  先根据娱乐的id将所有含娱乐标签的用户均返回
    • 4.2  对反斜杠进行处理
    • 4.3  统计2469用户的兴趣点频数
    • 4.4  测试迭代器中的组合函数
    • 4.5  两两组合
  • 5  封装成函数
  • 6  单独测试
    • 6.1  根据上表进行透视表操作然后聚类
  • 7  进行kmeans聚类
    • 7.1  建模
    • 7.2  拟合模型
    • 7.3  查看拟合结果 即类别标签
    • 7.4  将标签放到原数据框
    • 7.5  查看聚类中心
    • 7.6  可视化展示
    • 7.7  算两个变量有一个为0的的个数
      • 7.7.1  算两个变量的相关系数

背景

现在原始数据是这样:
Python|数据透视表+cut切分+Kmeans聚类_第1张图片

  • 我们希望首先根据score的大小进行某一标签的人群的一个划分
  • 其次希望得到不同标签用户之间的相关性,即进行聚类,看含有哪些标签的人群更类似,或者说哪些标签之间会有强相关,进而去进行同步推送相关文章。

数据预处理

读入数据

import pandas as pd
df = pd.read_excel('./example_data.xlsx', sheet_name='Sheet1')
print(df.shape)
df.head()
(37142, 3)
device_uuid interests_news interests_score
0 00:08:22:2e:db:fb 体育 0.364912
1 00:08:22:2e:db:fb 体育/中国足球 0.659644
2 00:08:22:2e:db:fb 体育/乒乓球 0.368236
3 00:08:22:2e:db:fb 健康/养生 0.448471
4 00:08:22:2e:db:fb 娱乐 0.263637
df['interests_news'].value_counts()
娱乐/明星八卦      2469
社会           1785
娱乐           1582
娱乐/电视         989
娱乐/综艺         935
健康/养生         881
社会/国际         857
社会/民生         747
要闻/国外         705
军事/军情         702
娱乐/电影         653
军事/装备         615
社会/市井         591
情感/两性         523
健康/医疗         513
要闻/国内         491
财经/商业         487
历史/古代史        436
历史/近现代史       428
汽车/用车         417
房产/购房         406
体育/NBA        394
社会/天气         374
体育/中国足球       368
生活/生活常识技巧     357
历史/世界史        354
体育/国际足球       340
手机/安卓         330
军事            296
美食/美食趣闻       288
             ... 
数码/配件           3
小品              3
科技/新科技          3
教育/演讲           3
财经/创业           3
房产/政策           2
人工智能            2
财经/基金           2
艺术/歌舞           2
人文/风水命理         2
财经/贵金属          2
财经/债券           2
故事              2
汽车/试驾测评         2
动漫/国产动漫         1
艺术/建筑设计         1
社会/违法犯罪         1
搞笑/创意广告         1
互联网/干货          1
教育/外语           1
艺术/乐器           1
体育/极限运动         1
艺术/戏曲           1
体育/赛车           1
人文/美文           1
科技/人工智能         1
演出现场            1
房产/土地           1
游戏/单机游戏         1
财经/保险           1
Name: interests_news, Length: 224, dtype: int64

删去缺失值

df1 = df.dropna()
print(df1.shape)
df1.head()
(31878, 3)
device_uuid interests_news interests_score
0 00:08:22:2e:db:fb 体育 0.364912
1 00:08:22:2e:db:fb 体育/中国足球 0.659644
2 00:08:22:2e:db:fb 体育/乒乓球 0.368236
3 00:08:22:2e:db:fb 健康/养生 0.448471
4 00:08:22:2e:db:fb 娱乐 0.263637

需求1:把娱乐/明星八卦单独拉出来

df_yl = df1[df1['interests_news'] == '娱乐/明星八卦']
print(df_yl.shape)
df_yl.head()
(2469, 3)
device_uuid interests_news interests_score
5 00:08:22:2e:db:fb 娱乐/明星八卦 0.320401
14 00:08:22:4a:bf:fb 娱乐/明星八卦 0.110526
36 283fd380ea20bdb9 娱乐/明星八卦 0.546335
56 359168070332202 娱乐/明星八卦 0.335987
62 38:bc:1a:2f:3d:1c 娱乐/明星八卦 0.244456

检验一下人数

len(df_yl['device_uuid'].unique())
2469

针对score进行降序排列

df_yl_sort = df_yl.sort_values(by = 'interests_score', ascending=0)
df_yl_sort.head()
device_uuid interests_news interests_score
16873 CQk2NThjZWRjNzUwNzE2NDI3CTc0MUFFQ1FIMjJKVlQ%3D 娱乐/明星八卦 0.994818
14027 CQk1MjQyMjdkYTAyOWJjMjBhCUEwMkFBQ1BTTkJMREo%3D 娱乐/明星八卦 0.980142
34036 CQliMTgyYmYxMzVmNzJjNGM5CTc0MUFFQ1FTMkdWREw%3D 娱乐/明星八卦 0.978786
8773 869322020959563 娱乐/明星八卦 0.974777
2967 867246026306168 娱乐/明星八卦 0.973836

看分布

import matplotlib
from matplotlib import pyplot as plt
plt.hist(df_yl['interests_score'])
plt.show()

Python|数据透视表+cut切分+Kmeans聚类_第2张图片

分区间段进行统计人数

pd.cut(df_yl['interests_score'], bins = 5).value_counts()
(0.199, 0.398]        670
(0.398, 0.597]        606
(-0.000971, 0.199]    603
(0.597, 0.796]        403
(0.796, 0.995]        187
Name: interests_score, dtype: int64

直接分成三块

pd.cut(df_yl['interests_score'], bins = 3).value_counts()
(-0.000971, 0.332]    1061
(0.332, 0.663]         968
(0.663, 0.995]         440
Name: interests_score, dtype: int64

等人数的分块

pd.qcut(df_yl['interests_score'], 3).value_counts()
(0.517, 0.995]        823
(0.263, 0.517]        823
(-0.000976, 0.263]    823
Name: interests_score, dtype: int64

总结:

  • cut是基于箱子的间隔 区间长度一样 即等区间
  • qcut是基于箱子里面的样本量个数分 区间里面样本量一致 即等频数

给这部分进行打标签

df_yl['yl_label'] = pd.cut(df_yl['interests_score'], bins = 3, labels=['低', '中', '高'])
df_yl.head()
device_uuid interests_news interests_score yl_label
5 00:08:22:2e:db:fb 娱乐/明星八卦 0.320401
14 00:08:22:4a:bf:fb 娱乐/明星八卦 0.110526
36 283fd380ea20bdb9 娱乐/明星八卦 0.546335
56 359168070332202 娱乐/明星八卦 0.335987
62 38:bc:1a:2f:3d:1c 娱乐/明星八卦 0.244456

需求2:聚类

先根据娱乐的id将所有含娱乐标签的用户均返回

yl_list = df1[df1['interests_news'] == '娱乐/明星八卦']['device_uuid'].tolist()
print(len(yl_list))
2469
# 这个就是最终的含有娱乐标签的用户情况
df1_all = df1[df1['device_uuid'].isin(yl_list)]
print(df1_all.shape)
df1_all.head()
(22399, 3)
device_uuid interests_news interests_score
0 00:08:22:2e:db:fb 体育 0.364912
1 00:08:22:2e:db:fb 体育/中国足球 0.659644
2 00:08:22:2e:db:fb 体育/乒乓球 0.368236
3 00:08:22:2e:db:fb 健康/养生 0.448471
4 00:08:22:2e:db:fb 娱乐 0.263637

对反斜杠进行处理

遇到的坑:

  • 输出文件的路径名称里不能有 | / \
  • 控制格式输出的时候 前面必须得是%d %s 啥的 后面是具体的数 不要搞懵逼了
  • DataFrame对某一列进行修改的时候 等式左边就把那一列给写上即可 不要就没有保留这个DataFrame
# 定义一个转换字符的函数 后面直接用map
def TransChar(x):
    return x.replace('/', '-')
import copy
df1_test = copy.deepcopy(df1_all)
df1_test['interests_news'] = df1_test['interests_news'].map(str).map(TransChar)
df1_all = df1_test
df1_all.head()
device_uuid interests_news interests_score
0 00:08:22:2e:db:fb 体育 0.364912
1 00:08:22:2e:db:fb 体育-中国足球 0.659644
2 00:08:22:2e:db:fb 体育-乒乓球 0.368236
3 00:08:22:2e:db:fb 健康-养生 0.448471
4 00:08:22:2e:db:fb 娱乐 0.263637

统计2469用户的兴趣点频数

df1_all['interests_news'].value_counts()
娱乐-明星八卦    2469
娱乐         1494
社会         1257
娱乐-综艺       805
娱乐-电视       800
社会-国际       663
娱乐-电影       562
社会-民生       560
社会-市井       485
健康-养生       427
财经-商业       392
情感-两性       351
要闻-国外       335
健康-医疗       332
历史-古代史      315
汽车-用车       307
历史-近现代史     307
军事-装备       274
军事-军情       269
手机-安卓       256
体育-NBA      246
房产-购房       242
美食-美食趣闻     234
体育-国际足球     232
体育-中国足球     231
历史-世界史      230
要闻-国内       220
社会-天气       206
搞笑          189
社会-交通出行     187
           ... 
数码-配件         3
科技-新科技        3
音乐-舞蹈         3
娱乐-娱乐资讯       3
小品            3
房产-房企         3
游戏-联机游戏       3
曲艺-杂技         2
舞蹈-广场舞        2
汽车-试驾测评       2
故事            2
财经-基金         2
艺术-歌舞         2
人文-风水命理       2
房产-政策         1
互联网-干货        1
财经-创业         1
搞笑-创意广告       1
艺术-戏曲         1
财经-债券         1
教育-演讲         1
社会-违法犯罪       1
艺术-乐器         1
体育-赛车         1
体育-极限运动       1
人工智能          1
游戏-单机游戏       1
动漫-国产动漫       1
教育-外语         1
演出现场          1
Name: interests_news, Length: 217, dtype: int64
# 筛选频数大于100的
yl_label_all = df1_all['interests_news'].value_counts().reset_index()
# print(yl_label_all)
yl_label_all = yl_label_all[yl_label_all['interests_news'] > 100]
# print(yl_label_all)
# 保留反斜杠的
yl_label_all_list = yl_label_all['index'].tolist()
# print(yl_label_all_list)
# 筛选出二级标签的 只留下这么多
yl_label_need_list = [x for x in yl_label_all_list if '-' in x]
print(len(yl_label_need_list))
yl_label_need_list
45





['娱乐-明星八卦',
 '娱乐-综艺',
 '娱乐-电视',
 '社会-国际',
 '娱乐-电影',
 '社会-民生',
 '社会-市井',
 '健康-养生',
 '财经-商业',
 '情感-两性',
 '要闻-国外',
 '健康-医疗',
 '历史-古代史',
 '汽车-用车',
 '历史-近现代史',
 '军事-装备',
 '军事-军情',
 '手机-安卓',
 '体育-NBA',
 '房产-购房',
 '美食-美食趣闻',
 '体育-国际足球',
 '体育-中国足球',
 '历史-世界史',
 '要闻-国内',
 '社会-天气',
 '社会-交通出行',
 '时尚-时装',
 '生活-生活常识技巧',
 '汽车-行业',
 '美食-食谱',
 '旅游-美景',
 '育儿-亲子',
 '情感-心理',
 '家居-家装',
 '人文-人文科普',
 '娱乐-音乐',
 '教育-高校',
 '互联网-电商',
 '军事-军人',
 '社会-法制',
 '汽车-新车',
 '体育-CBA',
 '社会-市政基建',
 '财经-股票']

测试迭代器中的组合函数

from itertools import combinations
com_all = list(combinations([1,2,3,4,5], 2))
com_all[0][1]
2

两两组合

from itertools import combinations
com_all = list(combinations(yl_label_need_list, 2)) # 990种组合 45*44/2
print(len(com_all))
com_all[0:5]
990





[('娱乐-明星八卦', '娱乐-综艺'),
 ('娱乐-明星八卦', '娱乐-电视'),
 ('娱乐-明星八卦', '社会-国际'),
 ('娱乐-明星八卦', '娱乐-电影'),
 ('娱乐-明星八卦', '社会-民生')]

总结:

  • 如果想要实现从一个list中获得所有的两两组合 可以使用combinations函数

封装成函数

df1_all.head()
device_uuid interests_news interests_score
0 00:08:22:2e:db:fb 体育 0.632
1 00:08:22:2e:db:fb 体育-中国足球 0.940
2 00:08:22:2e:db:fb 体育-乒乓球 0.975
3 00:08:22:2e:db:fb 健康-养生 0.695
4 00:08:22:2e:db:fb 娱乐 0.655

总结遇到的坑:

  • 输出文件路径不能有一些特殊的字符 这个得注意 前面也提到过
  • 如果老是输出有问题 不要急躁 拿一个小demo出来跑 看看到底是哪一步出了问题 一层一层的去寻找
  • 控制格式输出的时候 前面肯定是 %s %d 后面是具体的字母
from sklearn.cluster import KMeans
import pandas as pd
import numpy as np
import matplotlib
from matplotlib import pyplot as plt
import time

# 目的是为了去掉两个标签中有任意一个为0的行 会影响到聚类结果
def rule(x,y):
    if x == 0 or y == 0:
        return 1
    else:
        return 0
    
def Cluster2(label1, label2):
    try:
        # 放在输出文件前面
        # 1 选取娱乐 社会作为合并标签并筛选出相关用户
        clus_1 = [label1, label2]
        df1_clus_1 = df1_all[df1_all['interests_news'].isin(clus_1)]
        print('%s,%s,共有 %s 样本量'% (label1, label2, df1_clus_1.shape[0]))
        df1_clus_1.head()

        # 2 根据上表进行透视表操作然后聚类
        df1_clus_1_ = pd.pivot_table(df1_clus_1,index=["device_uuid"],values=["interests_score"],
                   columns=["interests_news"],aggfunc=[sum])
        df1_clus_1_.head()
        # 数据规整
        # 修整
        df1_clus_1_ = df1_clus_1_.fillna(0)
        df1_clus_1_.columns = df1_clus_1_.columns.droplevel()
        df1_clus_1_.columns = df1_clus_1_.columns.droplevel()
        # 把列名去掉
        del df1_clus_1_.columns.name
        df1_clus_1_.head()
        # 把索引变成列名
        df1_clus_1_['device_uuid'] = df1_clus_1_.index
        # df1_clus_1_.reset_index(drop=True)
        df1_clus_1_.head()
        # 重命名索引
        df1_clus_1_.index = range(len(df1_clus_1_))
        print('数据规整ok之后的样子为')
        print(df1_clus_1_.shape)
        print(df1_clus_1_.head())

        # 3 把任一标签的score为0的删去
        df1_clus_1_['Zero_num'] = df1_clus_1_.apply(lambda x : rule(x[label1], x[label2]), axis=1)
        print('两标签任一为0的总人数有: ', df1_clus_1_['Zero_num'].sum())
        # 过滤掉为1的
        df1_clus_1_ = df1_clus_1_[df1_clus_1_['Zero_num']!=1]
        # 再次重命名索引
        df1_clus_1_.index = range(len(df1_clus_1_))
        print('过滤掉任一为0之后的数据长这样:')
        print(df1_clus_1_.shape)
        print(df1_clus_1_.head())

        # 4 进行聚类
        # 建模
        estimator = KMeans(n_clusters=2, random_state=23)
        # 拟合
        estimator.fit(df1_clus_1_.iloc[:,:2])
        # 打标签
        df1_clus_1_['clu_label'] = estimator.labels_
        # 查看聚类中心
        centroid = estimator.cluster_centers_
        print('聚类中心为: ', centroid)

        # 5 绘图
        mark = ['or', 'ob']
        #画出所有样例点 属于同一分类的绘制同样的颜色
        for i in range(len(df1_clus_1_)):
            plt.plot(df1_clus_1_.iloc[i,0], df1_clus_1_.iloc[i,1], mark[estimator.labels_[i]]) #mark[markIndex])
        plt.show()    
        plt.close() # 关闭窗口
        return df1_clus_1_
    
    except Exception as e:
        print('报错信息为:  ', e)
    

t0 = time.time()
for i in range(len(com_all)):
    print('正在输出第 %d 种组合的聚类结果' % (i+1))
    print('△'*30)
    try:
        df1_clus_1_ = Cluster2(com_all[i][0], com_all[i][1])
    # 6 导出数据
#     print(com_all[i][0])
#     print(com_all[i][1])
        df1_clus_1_.to_csv('./娱乐大类/[%d]-[%s]_[%s]聚类结果.csv' % (i+1, str(com_all[i][0]), str(com_all[i][1])),
                           index = False, encoding = 'gbk')
    except Exception as e:
         print('第 %d 组合的报错信息为: %s' % (i+1, e))
    break
        
t1 = time.time()
print('组合所需时间为:%.2f s' % (t1-t0))
正在输出第 1 种组合的聚类结果
△△△△△△△△△△△△△△△△△△△△△△△△△△△△△△
娱乐-明星八卦,娱乐-综艺,共有 3274 样本量
数据规整ok之后的样子为
(2469, 3)
    娱乐-明星八卦     娱乐-综艺     device_uuid
0  0.875306  0.509272  86164703743646
1  0.469882  0.148727  86164703912396
2  0.077557  0.237983  86168103831042
3  0.203994  0.000000  86168103957564
4  0.001791  0.000000  86185103101184
两标签任一为0的总人数有:  1664
过滤掉任一为0之后的数据长这样:
(805, 4)
    娱乐-明星八卦     娱乐-综艺     device_uuid  Zero_num
0  0.875306  0.509272  86164703743646         0
1  0.469882  0.148727  86164703912396         0
2  0.077557  0.237983  86168103831042         0
3  0.632685  0.092836  86185103874437         0
4  0.158385  0.186337  86208603366335         0
聚类中心为:  [[0.66513518 0.32434565]
 [0.22094246 0.38035482]]

Python|数据透视表+cut切分+Kmeans聚类_第3张图片
组合所需时间为:2.16 s

单独测试

clus_1 = ['娱乐', '社会']
df1_clus_1 = df1_all[df1_all['interests_news'].isin(clus_1)]
print(df1_clus_1.shape)
df1_clus_1.head()
(2751, 3)
device_uuid interests_news interests_score
4 00:08:22:2e:db:fb 娱乐 0.263637
10 00:08:22:2e:db:fb 社会 0.444324
13 00:08:22:4a:bf:fb 娱乐 0.007945
35 283fd380ea20bdb9 娱乐 0.104760
44 283fd380ea20bdb9 社会 0.220708

根据上表进行透视表操作然后聚类

df1_clus_1_ = pd.pivot_table(df1_clus_1,index=["device_uuid"],values=["interests_score"],
               columns=["interests_news"],aggfunc=[sum])
df1_clus_1_.head()
sum
interests_score
interests_news 娱乐 社会
device_uuid
86164703743646 0.331710 NaN
86164703912396 0.034218 0.147317
86168103831042 0.197933 0.622465
86185103101184 NaN 0.161907
86185103527445 NaN 0.546773

总结1:

  • 使用pandas中的pivot_table函数可以起到和excel透视表一样的功能
  • index为根据这个进行分组
  • values则是要填充的值为这个
  • columns表示列名
  • aggfunc为values放进去是怎么放?是sum还是mean还是len

去掉pivot_table之后产生的多重索引的方法:

  • 使用columns.droplevel()函数
  • 删去列名 columns.name
# 修整
df1_clus_1_ = df1_clus_1_.fillna(0)
df1_clus_1_.columns = df1_clus_1_.columns.droplevel()
df1_clus_1_.columns = df1_clus_1_.columns.droplevel()
# 把列名去掉
del df1_clus_1_.columns.name
df1_clus_1_.head()
娱乐 社会
device_uuid
86164703743646 0.331710 0.000000
86164703912396 0.034218 0.147317
86168103831042 0.197933 0.622465
86185103101184 0.000000 0.161907
86185103527445 0.000000 0.546773
# 把索引变成列名
df1_clus_1_['device_uuid'] = df1_clus_1_.index
# df1_clus_1_.reset_index(drop=True)
df1_clus_1_.head()
娱乐 社会 device_uuid
device_uuid
86164703743646 0.331710 0.000000 86164703743646
86164703912396 0.034218 0.147317 86164703912396
86168103831042 0.197933 0.622465 86168103831042
86185103101184 0.000000 0.161907 86185103101184
86185103527445 0.000000 0.546773 86185103527445
# 重命名索引
df1_clus_1_.index = range(len(df1_clus_1_))
print(df1_clus_1_.shape)
df1_clus_1_.head()
(1821, 3)
娱乐 社会 device_uuid
0 0.331710 0.000000 86164703743646
1 0.034218 0.147317 86164703912396
2 0.197933 0.622465 86168103831042
3 0.000000 0.161907 86185103101184
4 0.000000 0.546773 86185103527445

进行kmeans聚类

建模

from sklearn.cluster import KMeans
estimator = KMeans(n_clusters=2, random_state=23)
estimator
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
    n_clusters=2, n_init=10, n_jobs=None, precompute_distances='auto',
    random_state=23, tol=0.0001, verbose=0)

拟合模型

df1_clus_1_.iloc[:,:2].head()
娱乐 社会
0 0.331710 0.000000
1 0.034218 0.147317
2 0.197933 0.622465
3 0.000000 0.161907
4 0.000000 0.546773
estimator.fit(df1_clus_1_.iloc[:,:2])
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
    n_clusters=2, n_init=10, n_jobs=None, precompute_distances='auto',
    random_state=23, tol=0.0001, verbose=0)

查看拟合结果 即类别标签

estimator.labels_
array([1, 1, 0, ..., 1, 1, 1], dtype=int32)

将标签放到原数据框

df1_clus_1_['clu_label'] = estimator.labels_
df1_clus_1_.head()
娱乐 社会 device_uuid clu_label
0 0.331710 0.000000 86164703743646 1
1 0.034218 0.147317 86164703912396 1
2 0.197933 0.622465 86168103831042 0
3 0.000000 0.161907 86185103101184 1
4 0.000000 0.546773 86185103527445 0
df1_clus_1_.iloc[0,0]
0.33171015445836183

查看聚类中心

centroid = estimator.cluster_centers_
centroid
array([[0.25107961, 0.53625566],
       [0.33770399, 0.07484127]])

可视化展示

mark = ['or', 'ob']
#画出所有样例点 属于同一分类的绘制同样的颜色
for i in range(len(df1_clus_1_)):
    plt.plot(df1_clus_1_.iloc[i,0], df1_clus_1_.iloc[i,1], mark[estimator.labels_[i]]) #mark[markIndex])

Python|数据透视表+cut切分+Kmeans聚类_第4张图片

算两个变量有一个为0的的个数

思路:新增一列 为0的则记为1

def rule(x,y):
    if x == 0 or y == 0:
        return 1
    else:
        return 0
df1_clus_1_['Zero_num'] = df1_clus_1_.apply(lambda x : rule(x['娱乐'], x['社会']), axis=1)
print('两标签任一为0的总人数有: ', df1_clus_1_['Zero_num'].sum())
# 过滤掉为1的
df1_clus_1_ = df1_clus_1_[df1_clus_1_['Zero_num']!=1]
df1_clus_1_.head()
两标签任一为0的总人数有:  891
娱乐 社会 device_uuid clu_label Zero_num
1 0.034218 0.147317 86164703912396 1 0
2 0.197933 0.622465 86168103831042 0 0
7 0.077339 0.388035 86208603366335 0 0
10 0.402624 0.212100 86247803073494 1 0
11 0.237287 0.268475 86249303752541 1 0
891 / 2751
0.3238822246455834

算两个变量的相关系数

import numpy as np
np.corrcoef(df1_clus_1_['娱乐'], df1_clus_1_['社会'])
array([[ 1.        , -0.02175822],
       [-0.02175822,  1.        ]])

数据

example_data

你可能感兴趣的:(Python,机器学习,数据透视表,cut,函数,Kmeans聚类,组合combinations)