Python - 根据排名信息分组(Cluster/Sort)数组

Python - Group(Cluster/Sort) arrays based on ranking information

我的数据框如下所示:

      A         B          C          D
0    5         4           3         2
1    4         5           3         2
2    3         5           2         1
3    4         2           5         1
4    4         5           2         1
5    4         3           5         1
...

我将数据帧转换为二维数组,如下所示:

[[5 4 3 2]
 [4 5 3 2]
 [3 5 2 1]
 [4 2 5 1]
 [4 5 2 1]
 [4 3 5 1]
 ...]

每一行的分数1-5其实就是人们给项目A, B, C, D的分数。我想确定具有相同排名的人,例如人们认为 A > B > C > D。我想根据这样的排名信息重新组合这些数组:

2DArray1: [[5 4 3 2]]
2DArray2: [[4 5 3 2]
           [3 5 2 1]
           [4 5 2 1]]
2DArray3: [[4 2 5 1]
           [4 3 5 1]]

例如2DArray2表示认为B > A > C > D的人,2DArray3是认为C > A > B > D的人。我在 numpy 中尝试了不同的 sort functions 但我找不到合适的。我该怎么办?

Numpy 没有 groupby 函数,因为 groupby 会 return 不同大小列表的列表;而 numpy 大多只处理“矩形”数组。

解决方法是对行进行排序,使相似的行相邻,然后生成每组开头索引的数组。

由于我懒得这样做,这里有一个没有 numpy 的解决方案:

直接按排列索引

对于每一行,我们计算 'ABCD' 的相应排列。然后,我们将该行添加到行列表的字典中,其中字典键是相应的排列。

from collections import defaultdict

a = [[5, 4, 3, 2], [4, 5, 3, 2], [3, 5, 2, 1], [4, 2, 5, 1], [4, 5, 2, 1], [4, 3, 5, 1]]

groups = defaultdict(list)
for row in a:
    groups[tuple(sorted(range(len(row)), key=lambda i: row[i], reverse=True))].append(row)

print(groups)

输出:

defaultdict(<class 'list'>, {
    (0, 1, 2, 3): [[5, 4, 3, 2]],
    (1, 0, 2, 3): [[4, 5, 3, 2], [3, 5, 2, 1], [4, 5, 2, 1]],
    (2, 0, 1, 3): [[4, 2, 5, 1], [4, 3, 5, 1]]
})

请注意,使用此解决方案,如果某些用户对两个不同的项目给出相同的分数,结果可能不是您所期望的,因为 sorted 不保持原样;相反,它按出现顺序打破联系(在这种情况下,这意味着两个项目之间的关系按字母顺序打破)。

按排列的索引索引

'ABCD'的排列可以按字典顺序排列:'ABCD'排在第一位,然后'ABDC'排在第二位,然后'ACBD'排在第三位...

事实证明,有一种算法可以计算给定排列出现在该序列中的索引!该算法在 python 模块 more_itertools:

中实现

因此,我们可以用简单的数字键 permutation_index(row, sorted(row, reverse=True)).

替换我们的元组键 tuple(sorted(range(len(row)), key=lambda i: row[i], reverse=True))
from collections import defaultdict
from more_itertools import permutation_index

a = [[5, 4, 3, 2], [4, 5, 3, 2], [3, 5, 2, 1], [4, 2, 5, 1], [4, 5, 2, 1], [4, 3, 5, 1]]

groups = defaultdict(list)
for row in a:
    groups[permutation_index(row, sorted(row, reverse=True))].append(row)

print(groups)

输出:

defaultdict(<class 'list'>, {
    0: [[5, 4, 3, 2]],
    6: [[4, 5, 3, 2], [3, 5, 2, 1], [4, 5, 2, 1]],
    8: [[4, 2, 5, 1], [4, 3, 5, 1]]
})

混合 permutation_index 和 pandas

由于 permutation_index 的输出是一个简单的数字,我们可以轻松地将其作为新列包含在 numpy 数组或 pandas 数据帧中:

import pandas as pd
from more_itertools import permutation_index

df = pd.DataFrame({'A': [5,4,3,4,4,4], 'B': [4,5,5,2,5,3], 'C': [3,2,2,5,2,5], 'D': [2,2,1,1,1,1]})

df['perm_idx'] = df.apply(lambda row: permutation_index(row, sorted(row, reverse=True)), axis=1)

print(df)

   A  B  C  D  perm_idx
0  5  4  3  2         0
1  4  5  2  2         6
2  3  5  2  1         6
3  4  2  5  1         8
4  4  5  2  1         6
5  4  3  5  1         8

for idx, sub_df in df.groupby('perm_idx'):
    print(idx)
    print(sub_df)

0
   A  B  C  D  perm_idx
0  5  4  3  2         0
6
   A  B  C  D  perm_idx
1  4  5  2  2         6
2  3  5  2  1         6
4  4  5  2  1         6
8
   A  B  C  D  perm_idx
3  4  2  5  1         8
5  4  3  5  1         8

你可以

(i) 转置 df 并将其转换为字典,

(ii) 按值对字典进行排序并获取键,

(iii) 加入每个“人”的排序键并将此字典分配给 df['ranks'],

(iv) 汇总排名积分并将其分配给 df['pref'],

(v) groupby(['ranks']) 并从 pref

创建列表
df = pd.DataFrame({'A': {0: 5, 1: 4, 2: 3, 3: 4, 4: 4, 5: 4},
                   'B': {0: 4, 1: 5, 2: 5, 3: 2, 4: 5, 5: 3},
                   'C': {0: 3, 1: 3, 2: 2, 3: 5, 4: 2, 5: 5},
                   'D': {0: 2, 1: 2, 2: 1, 3: 1, 4: 1, 5: 1}})

df['ranks'] = pd.Series({k : ''.join(list(zip(*sorted(v.items(), key=lambda d:d[1], 
                                                      reverse=True)))[0]) 
                         for k,v in df.T.to_dict().items()})
df['pref'] = df.loc[:,'A':'D'].values.tolist()
out = df[['ranks','pref']].groupby('ranks').agg(list).to_dict()['pref']

输出:

{'ABCD': [[5, 4, 3, 2]],
 'BACD': [[4, 5, 3, 2], [3, 5, 2, 1], [4, 5, 2, 1]],
 'CABD': [[4, 2, 5, 1], [4, 3, 5, 1]]}