案例说明
我们经常会遇到这种需求: —— 将数据按照某个维度的值进行分组(例如: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