基于Pandas Series嵌套列表实现篮球赛事阵容动态更新问题及解决方案
解决篮球PBP数据中动态更新场上阵容的问题
我之前也处理过类似的篮球赛事逐场(Play-by-Play)数据的阵容追踪需求,结合你的场景,咱们一步步来拆解问题和解决方案:
问题背景
你有一个名为pbp的DataFrame,包含gameID、actionNumber、team、players、actionType、subType、starters等列,已经按赛事和动作序号排序。核心需求是根据每一次换人动作动态记录每一步的场上阵容,但初始代码运行后所有行的阵容都被覆盖成最后一套,无法正确追踪变化。
初始代码的问题分析
你的第一版代码主要有两个关键问题:
- 直接修改原始
starters列的数据:比如a = row["starters"]后直接执行a.append(row.player),这会修改DataFrame里的原始starters值,导致后续所有引用该行starters的操作都用了被修改后的列表,最终所有行的阵容都被覆盖成最后一次修改的结果。 - 阵容追踪逻辑混乱:没有为每个球队单独维护一个"当前阵容"的状态,而是每次都试图从行里的
starters列修改,忽略了阵容是随着动作逐步更新的,不是每次都从初始阵容重新调整。
正确的解决方案思路
正确的核心逻辑应该是:
- 为每个赛事的每支球队单独维护当前阵容的状态,而不是每次从
starters列复制。 - 针对不同动作类型(跳球、换人、其他动作)分别处理:
- 跳球动作:初始化对应球队的初始阵容。
- 换人动作:基于该球队上一次的阵容,复制后执行添加/删除操作,更新当前阵容。
- 其他动作:直接沿用该球队上一次的阵容。
- 确保每次修改阵容时都使用深拷贝,避免修改原始数据。
最终可行代码
import numpy as np import pandas as pd team = [] lineup = [] for idx, row in pbp.iterrows(): # 获取当前行的球队 equipo = row["team"] if row["actionType"] == "jumpball": # 处理跳球动作,初始化初始阵容 if row["subType"] in ["won", "lost"]: # 复制初始阵容,避免修改原始数据 current_starters = row.starters.copy() lineup.append(current_starters) team.append(equipo) result = "gana el salto" if row["subType"] == "won" else "pierde el salto" print(f"{idx}: {row.team} El jugador {row.player} {result}: {lineup[idx]}") else: # 其他跳球类型,标记为NaN lineup.append(np.nan) team.append(np.nan) print(f"{idx}: Se produce una lucha: {lineup[idx-1] if idx > 0 else 'N/A'}") elif row["actionType"] == "substitution": # 获取该球队最后一次记录的阵容索引 last_team_idx = max([i for i in range(len(team)) if team[i] == equipo]) # 复制上一次的阵容,避免修改历史数据 current_lineup = lineup[last_team_idx].copy() if row["subType"] == "in": # 球员上场:添加到阵容 current_lineup.append(row.scoreboardName) lineup.append(current_lineup) team.append(equipo) print(f"{idx}: El jugador {row.player} del equipo {row.team} entra en pista: {lineup[idx]}") else: # 球员下场:从阵容移除 current_lineup.remove(row.scoreboardName) lineup.append(current_lineup) team.append(equipo) print(f"{idx}: El jugador {row.player} del equipo {row.team} sale de pista: {lineup[idx]}") else: # 处理其他非换人动作 if pd.notna(equipo): # 当前动作属于某支球队,沿用上次阵容 last_team_idx = max([i for i in range(len(team)) if team[i] == equipo]) current_lineup = lineup[last_team_idx].copy() lineup.append(current_lineup) team.append(equipo) print(f"{idx}: No hay sustitución: {team[idx]} {lineup[idx]}") else: # 无球队参与的动作(如暂停、节次变更) lineup.append(np.nan) team.append(np.nan) print(f"{idx}: No hay acción equipo: {lineup[idx]}") # 最后可以把lineup和team列添加回原DataFrame,方便后续分析 pbp["current_lineup"] = lineup pbp["current_team"] = team
关键优化点
- 单独维护球队阵容状态:通过
team和lineup列表追踪每支球队的历史阵容,每次更新都基于该球队的上一次状态,而不是原始的starters。 - 强制使用
copy():每次获取阵容时都执行copy(),确保修改的是副本,不会污染历史数据或原始DataFrame的starters列。 - 清晰的分支逻辑:把跳球、换人、其他动作分成独立的代码块,逻辑更易读和维护。
内容的提问来源于stack exchange,提问作者PedroSanchez95
相关产品推荐
相关产品推荐

