Pandas 案例一)进行组内排序,并添加序号


案例说明

我们经常会遇到这种需求: —— 将数据按照某个维度的值进行分组(例如:USER_ID),然后针对某个值进行组内排序(例如:SCORE),并标注序号

比如: 在推荐系统的召回阶段,我们会为每个用户推荐数个产品,并赋予其不同的得分,有时我们就需要对同一用户,不同推荐产品的得分进行排序,并标注序号。 以便在后续执行其他操作。

实现方式

示例数据:
# 构建示例数据
data={
    'USER_ID': ['张三', '张三', '张三', '李四', '李四', '李四', '李四', '王五', '王五'],
    'SKU_CODE': [1,2,3, 4,5,6,7, 8,9],
    'SCORE': [3,1,2, 9,5,5,6, 1,3]
}
df = pd.DataFrame(data)
# 数据示例:
#   USER_ID  SKU_CODE  SCORE
# 0      张三         1      3
# 1      张三         2      1
# 2      张三         3      2
# 3      李四         4      9
# 4      李四         5      5
# 5      李四         6      5
# 6      李四         7      6
# 7      王五         8      1
# 8      王五         9      3
实现方法:
df['rank'] = df['SCORE'].groupby(df['USER_ID']).rank(ascending=False)
# 输出结果:
#   USER_ID  SKU_CODE  SCORE  rank
# 0      张三         1      3   1.0
# 1      张三         2      1   3.0
# 2      张三         3      2   2.0
# 3      李四         4      9   1.0
# 4      李四         5      5   3.5
# 5      李四         6      5   3.5
# 6      李四         7      6   2.0
# 7      王五         8      1   2.0
# 8      王五         9      3   1.0

# 实现2 使用 method 为 first
df['rank2'] = df['SCORE'].groupby(df['USER_ID']).rank(method='first',ascending=False)
#   USER_ID  SKU_CODE  SCORE  rank  rank2
# 0      张三         1      3   1.0    1.0
# 1      张三         2      1   3.0    3.0
# 2      张三         3      2   2.0    2.0
# 3      李四         4      9   1.0    1.0
# 4      李四         5      5   3.5    3.0
# 5      李四         6      5   3.5    4.0
# 6      李四         7      6   2.0    2.0
# 7      王五         8      1   2.0    2.0
# 8      王五         9      3   1.0    1.0
# 可以看到上述的结果更贴近有序的 row_number() 结果
方法说明

这里面主要用到了两个关键的函数,.rank() & .groupby()

1) Series.rank()

Series.rank() 计算延轴的数值型数据的排序(从1到n),默认情况下为相等的值,分配同一个排序(如上述实现方法-输出结果中的 3.5)。

Series.rank() 中比较常用的参数有:

  • method: 排序方法,可选值有 average / min / max / first / dense
  • na_option: 如何对 NaN 值进行排序
  • ascending: 是否采用升序的排列方式,默认为是(可以看到,由于是对得分进行排序,所以上面的示例中,使用了 False,即不采用升序的方式)

更多详细可以点击链接查看官方文档

2) .groupby()

Series.groupby() 将 Series 按照一定规则进行分组。

关于 Group By,可以参考此篇官方文档

Group By 可以简单理解为对数据,进行了如下步骤的操作:

  • 拆分:将数据按照某些特定规则进行分组,拆分的方式有很多,例如:
    • 通过 Python 函数进行区分,它会对相应轴上的标签,依次贯入函数,并根据结果进行区分;
    • 与选定轴长度相同的列表或 Numpy 数组;
    • 一个关于 {值:分组} 的映射关系列表;
    • 等等
  • 应用规则:对每个分组分别应用某种规则,例如:
    • 聚合(Aggregation):比如我们常用的 sum / count / max / min 等等...
    • 转换(Transformation):执行一些特定于组的操作并返回索引相同的对象...
    • 过滤(Filtraction):根据相应条件过滤或丢弃一部分组内数据...
  • 将结果合并。

将上述案例中,我们采用了 df['rank'] = df['SCORE'].groupby(df['USER_ID']).rank(ascending=False) 的方法,该方法a在效果上等同于 df['rank'] = df.groupby['USER_ID']['SCORE'].rank(ascending=False)

即可以理解为对 df 针对键 'USER_ID' 列,做 Group By,然后对得分计算其在组内的排序值(降序排序),并将值赋予到 df 的 rank 列上。

方法延伸

上述方法虽然完成了排序,但是结果不甚美观,不符合我们的常规预期,我们可以将代码修改为如下形式:

df['rank'] = df['SCORE'].groupby(df['USER_ID']).rank(ascending=False).astype(int)
df = df.sort_values(['USER_ID', 'SCORE'], ascending=False).reset_index(drop=True)
#   USER_ID  SKU_CODE  SCORE  rank
# 0      王五         9      3     1
# 1      王五         8      1     2
# 2      李四         4      9     1
# 3      李四         7      6     2
# 4      李四         5      5     3
# 5      李四         6      5     3
# 6      张三         1      3     1
# 7      张三         3      2     2
# 8      张三         2      1     3

这里,分别使用 .astype(int) 将原先 float64 格式的 rank 数据,转换为 Int 形式; 使用 .sort_values(['USER_ID', 'SCORE'], ascending=False) 对数据进行重新排序(基于 USER_ID & SCORE); 使用 .reset_index(drop=True) 来重新生成索引。

注意

上述方法中,使用的是 .rank() 进行组内排序,那么针对 SQL 中的 row_number() 其对应的方法应该是那种呢?

pandas.core.groupby.GroupBy.cumcount

另外,十分需要注意的是,pandas 中专门罗列了,pandas 方法和 SQL 的对比,可参考: https://pandas.pydata.org/docs/getting_started/comparison/comparison_with_sql.html