如何仅用DataFrame操作生成多玩家唯一组合的得分DataFrame?
问题
现有数据记录了两名玩家对战后,对应不同胜负结果的共享得分(数值仅为示例,与实际游戏逻辑无关)。需求如下:
- 基于给定的DataFrame,生成包含4名玩家所有唯一对战组合的新DataFrame
- 计算每个组合的总得分:比如P1&P2全胜得30分、P3&P4全胜得42分,二者相加就是这4人全胜组合的总得分72分
目前已能通过生成组合的方式实现,但数据量较大时代码冗余,询问是否仅通过merge、groupby、join、agg等DataFrame原生操作完成该需求。
附示例代码及数据:
import pandas as pd data = { "Player_1": ["P1", "P1", "P1", "P1", "P2", "P2", "P2", "P2", "P1", "P1", "P1", "P1", "P3", "P3", "P3", "P3"], "Player_2": ["P2", "P2", "P2", "P2", "P3", "P3", "P3", "P3", "P4", "P4", "P4", "P4", "P4", "P4", "P4", "P4"], "Outcome_1": ["win", "win", "lose", "lose", "win", "win", "lose", "lose", "win", "win", "lose", "lose", "win", "win", "lose", "lose"], "Outcome_2": ["win", "lose", "win", "lose", "win", "lose", "win", "lose", "win", "lose", "win", "lose", "win", "lose", "win", "lose"], "Score": [30, 45, 12, 78, 56, 21, 67, 90, 15, 32, 68, 88, 42, 74, 8, 93] } df = pd.DataFrame(data) print(df)
示例输出:
Player_1 Player_2 Outcome_1 Outcome_2 Score 0 P1 P2 win win 30 1 P1 P2 win lose 45 2 P1 P2 lose win 12 3 P1 P2 lose lose 78 4 P2 P3 win win 56 5 P2 P3 win lose 21 6 P2 P3 lose win 67 7 P2 P3 lose lose 90 8 P1 P4 win win 15 9 P1 P4 win lose 32 10 P1 P4 lose win 68 11 P1 P4 lose lose 88 12 P3 P4 win win 42 13 P3 P4 win lose 74 14 P3 P4 lose win 8 15 P3 P4 lose lose 93
解决方案
完全可以通过DataFrame原生操作实现,核心思路是先标准化对战组合、提取唯一胜负-得分映射,再生成所有无重叠的4人对战分组,最后关联得分并求和。步骤如下:
1. 标准化对战组合并提取唯一得分映射
先把无序的对战组合(比如(P1,P2)和(P2,P1))统一格式,同时保留每一组对战对应胜负结果的得分:
# 标准化对战组合:将玩家按字母排序,避免同一对战的正反序被视为不同组合 df['standard_pair'] = df.apply(lambda row: tuple(sorted([row['Player_1'], row['Player_2']])), axis=1) # 提取唯一的「对战组合-胜负结果-得分」映射 match_score_map = df.groupby(['standard_pair', 'Outcome_1', 'Outcome_2'])['Score'].first().reset_index()
2. 生成所有无重叠的4人对战分组
通过组合工具生成所有合法的4人分组(即两组无重叠的两两对战):
from itertools import combinations # 获取所有唯一玩家 all_players = df[['Player_1', 'Player_2']].stack().unique() # 生成所有两两对战的基础组合 all_pairs = list(combinations(all_players, 2)) # 筛选出无重叠的4人分组(两组对战没有共同玩家) valid_4player_groups = [] for idx, pair1 in enumerate(all_pairs): used_players = set(pair1) # 只遍历后续组合,避免重复分组(比如(P1,P2)+(P3,P4)和(P3,P4)+(P1,P2)视为同一组) for pair2 in all_pairs[idx+1:]: if not used_players.intersection(pair2): valid_4player_groups.append({'Pair1': pair1, 'Pair2': pair2}) # 转换为DataFrame group_df = pd.DataFrame(valid_4player_groups)
3. 关联得分并计算组合总得分
通过笛卡尔积关联所有胜负结果,再分别匹配两组对战的得分,最后求和得到总得分:
# 提取所有唯一的胜负结果组合 all_outcomes = df[['Outcome_1', 'Outcome_2']].drop_duplicates() # 生成「分组-胜负结果」的笛卡尔积,覆盖所有可能的胜负场景 full_result_df = group_df.merge(all_outcomes, how='cross') # 关联第一组对战的得分 full_result_df = full_result_df.merge( match_score_map, left_on=['Pair1', 'Outcome_1', 'Outcome_2'], right_on=['standard_pair', 'Outcome_1', 'Outcome_2'], how='left' ).rename(columns={'Score': 'Score_Pair1'}).drop(columns='standard_pair') # 关联第二组对战的得分 full_result_df = full_result_df.merge( match_score_map, left_on=['Pair2', 'Outcome_1', 'Outcome_2'], right_on=['standard_pair', 'Outcome_1', 'Outcome_2'], how='left' ).rename(columns={'Score': 'Score_Pair2'}).drop(columns='standard_pair') # 计算该分组在当前胜负场景下的总得分 full_result_df['Total_Score'] = full_result_df['Score_Pair1'] + full_result_df['Score_Pair2']
最终的full_result_df就包含了所有4人对战组合在各种胜负情况下的总得分,全程使用merge、groupby等DataFrame原生操作,代码简洁且适合大数据量场景。
内容的提问来源于stack exchange,提问作者Cem Koçak
相关产品推荐
相关产品推荐

