【问题标题】:Python - Group(Cluster/Sort) arrays based on ranking informationPython - 基于排名信息的分组(集群/排序)数组
【发布时间】:2022-01-12 23:28:11
【问题描述】:

我有一个如下所示的数据框:

      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,但我找不到合适的。我该怎么办?

【问题讨论】:

  • 是什么让2DArray3 从第 5 行开始?
  • @user17242583 其实我不关心组中的顺序只要他们有相同的排名顺序,所以对于 2DArrays3 我只需要C > A > B >D 但不关心里面的顺序组。
  • 注意pandas有groupby,纯python有groupby,而numpy没有groupby。您从数据框开始,然后转换为 numpy 数组。你宁愿转换成 python 列表,还是继续使用 pandas 数据框?

标签: python arrays numpy sorting grouping


【解决方案1】:

Numpy 没有groupby 函数,因为 groupby 会返回不同大小的列表列表;而 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 不保持ex-aequo;相反,它会按出现顺序打断关系(在这种情况下,这意味着两个项目之间的关系是按字母顺序打断的)。

按排列的索引索引

'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

【讨论】:

    【解决方案2】:

    你可以

    (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]]}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-25
      • 1970-01-01
      • 2015-04-27
      • 2012-01-13
      • 2021-07-17
      • 2013-07-11
      • 1970-01-01
      • 2015-04-13
      相关资源
      最近更新 更多