以余弦相似度将学生分组

cluster students into groups with cosine similarity

我有一个针对学生的 DataFrame,每个学生都由 6 门不同课程的二进制向量表示。即如果学生已经注册了这门课程,则1会被放在相应的位置,否则就是0。

import pandas as pd
import numpy as np

df_student = pd.DataFrame({'Name':['tom', 'Howard', 'Monty', 'Sean', 'mat', 
                                   'john', 'peter', 'lina', 'rory', 'joe'],
                           'math':[1,0,0,1,0,1,1,1,1,1],
                           'physics':[1,0,0,1,0,0,1,0,1,0], 
                           'chemistry':[0,1,1,1,0,1,0,1,1,1],
                           'biology':[1,0,0,1,0,1,1,1,0,1],
                           'history':[0,0,0,0,1,1,0,1,0,1],
                           'geography':[0,1,1,1,0,1,0,1,0,1]})

看起来像:

     Name  math  physics  chemistry  biology  history  geography
0     tom     1        1          0        1        0          0
1  Howard     0        0          1        0        0          1
2   Monty     0        0          1        0        0          1
3    Sean     1        1          1        1        0          1
4     mat     0        0          0        0        1          0
5    john     1        0          1        1        1          1
6   peter     1        1          0        1        0          0
7    lina     1        0          1        1        1          1
8    rory     1        1          1        0        0          0
9     joe     1        0          1        1        1          1

我想通过应用一些具有余弦相似度而不是欧氏距离的聚类算法将学生分组

因此,学生将被分组,例如在 k 个集群中,当我们有 10 个学生时,预期输出如下所示:

cluster_0:{tom, peter}
cluster_1:{Howard, Monty}
cluster_2:{Sean}
cluster_3:{mat}
cluster_4:{john, lina, joe}
cluster_5:{rory}

我们可以根据 cosine_similarity 的结果创建一个 DataFrame;然后 mask 小于 1 的值(由于存在一些舍入误差,我们 select 一个非常接近 1 的数字)和 stack 其余值。然后堆叠系列的索引包含我们想要的集群。要获得它们,我们使用 groupby + agg(set) + drop_duplicates。然后我们从集群中创建一个字典:

from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
df_student = df_student.set_index('Name')
df = pd.DataFrame(cosine_similarity(df_student), 
                  index=df_student.index, columns=df_student.index)
clusters= (df.mask(df<1-np.finfo(float).eps)
           .stack()
           .index.to_frame()
           .groupby(level=0)['Name'].agg(set)
           .drop_duplicates())
clusters = (clusters.set_axis([f'cluster_{i}' for i in range(len(clusters))])
            .to_dict())

输出:

{'cluster_0': {'Howard', 'Monty'},
 'cluster_1': {'Sean'},
 'cluster_2': {'joe', 'john', 'lina'},
 'cluster_3': {'mat'},
 'cluster_4': {'peter', 'tom'},
 'cluster_5': {'rory'}}