基于多条件与GroupBy的Pandas列匹配值累计计数优化问询
更高效的Pandas实现方案
原始数据
| 商家 | 商品 | 排名 |
|---|---|---|
| Merchant 1 | apple | 1 |
| Merchant 1 | banana | 2 |
| Merchant 1 | mango | 3 |
| Merchant 1 | grapes | 4 |
| Merchant 1 | kiwi | 5 |
| Merchant 2 | orange | 1 |
| Merchant 2 | apple | 2 |
| Merchant 2 | banana | 3 |
| Merchant 2 | grapes | 4 |
| Merchant 3 | mango | 1 |
| Merchant 3 | grapes | 2 |
| Merchant 3 | orange | 3 |
需求回顾
按商家统计从排名1开始的第一组连续匹配商品的累计数量,计算调整后排名:
- 匹配规则:
apple|banana|orange - 调整后排名公式:
累计总数 + 1,若累计总数 ≤ 2则重置为1 - 最终输出每个商家对应排名1的调整后排名结果
理想输出
| 商家 | 排名 | 调整后排名 |
|---|---|---|
| Merchant 1 | 1 | 3 |
| Merchant 2 | 1 | 4 |
| Merchant 3 | 1 | 1 |
优化实现方案
方案一:向量化分组实现(最高效,适合大数据集)
该方案完全依赖Pandas向量化操作,避免循环,性能远优于原代码:
import pandas as pd import numpy as np # 读取数据,实际使用时替换为 pd.read_csv('data.csv') data = [ ["Merchant 1", "apple", 1], ["Merchant 1", "banana", 2], ["Merchant 1", "mango", 3], ["Merchant 1", "grapes", 4], ["Merchant 1", "kiwi", 5], ["Merchant 2", "orange", 1], ["Merchant 2", "apple", 2], ["Merchant 2", "banana", 3], ["Merchant 2", "grapes", 4], ["Merchant 3", "mango", 1], ["Merchant 3", "grapes", 2], ["Merchant 3", "orange", 3], ] df = pd.DataFrame(data, columns=["商家", "商品", "排名"]) pattern = r'apple|banana|orange' # 1. 标记商品是否匹配规则 df['match'] = df['商品'].str.contains(pattern) # 2. 分组标记首组连续匹配项:从排名1开始,连续匹配直到第一个不匹配 df['is_first_consecutive'] = df.groupby('商家')['match'].transform( lambda x: x & (x.cumsum() == np.arange(1, len(x)+1)) ) # 3. 按商家统计首组连续匹配的总数 consecutive_counts = df.groupby('商家')['is_first_consecutive'].sum() # 4. 计算调整后排名 adjusted_ranks = consecutive_counts.apply(lambda x: x + 1 if x > 2 else 1) # 5. 生成最终结果 result = pd.DataFrame({ '商家': adjusted_ranks.index, '排名': 1, '调整后排名': adjusted_ranks.values }).reset_index(drop=True) print(result)
方案二:分组Apply实现(逻辑直观,适合小数据集)
如果数据量不大,用apply的方式逻辑更直观,容易理解:
import pandas as pd df = pd.DataFrame(data, columns=["商家", "商品", "排名"]) pattern = r'apple|banana|orange' def compute_rank(group): # 确保按排名排序(原始数据已排序时可省略) group_sorted = group.sort_values('排名') # 生成匹配标记 matches = group_sorted['商品'].str.contains(pattern) if not matches.iloc[0]: # 第一个商品就不匹配,累计数为0 count = 0 else: # 找到第一个不匹配的位置,计算连续匹配数 first_non_match_idx = matches.idxmin() if not matches.all() else len(matches) count = matches.loc[:first_non_match_idx].sum() # 计算调整后排名 adjusted = count + 1 if count > 2 else 1 return pd.Series([group_sorted['商家'].iloc[0], 1, adjusted], index=['商家', '排名', '调整后排名']) # 分组计算并生成结果 result = df.groupby('商家', group_keys=False).apply(compute_rank).reset_index(drop=True) print(result)
优化点说明
- 减少冗余操作:原代码创建多个中间列、多次过滤合并,优化方案直接在分组内完成所有计算,避免不必要的数据复制和操作。
- 向量化优先:方案一使用
transform和cumsum实现全向量化操作,避免循环,在大数据集下性能提升显著。 - 逻辑更紧凑:直接聚焦“首组连续匹配”的核心需求,避免了原代码中复杂的分组累计和过滤逻辑。
内容的提问来源于stack exchange,提问作者Willll
相关产品推荐
相关产品推荐

