基于赛事规则与参赛数据计算足球运动员出生年份
基于参赛类别与赛季推导足球运动员出生年份的Pandas逻辑优化
规则与需求说明
出生年份映射规则
存在如下细分类别、赛季与出生年份的对应关系(Age为对应参赛年龄):
| Age | Category | 2023 | 2022 | 2021 | 2020 | 2019 | 2018 | 2017 |
|---|---|---|---|---|---|---|---|---|
| 19 | A3 | 2004 | 2003 | 2002 | 2001 | 2000 | 1999 | 1998 |
| 18 | A2 | 2005 | 2004 | 2003 | 2002 | 2001 | 2000 | 1999 |
| 17 | A1 | 2006 | 2005 | 2004 | 2003 | 2002 | 2001 | 2000 |
| 16 | B2 | 2007 | 2006 | 2005 | 2004 | 2003 | 2002 | 2001 |
| 15 | B1 | 2008 | 2007 | 2006 | 2005 | 2004 | 2003 | 2002 |
| 14 | C2 | 2009 | 2008 | 2007 | 2006 | 2005 | 2004 | 2003 |
| 13 | C1 | 2010 | 2009 | 2008 | 2007 | 2006 | 2005 | 2004 |
| 12 | D2 | 2011 | 2010 | 2009 | 2008 | 2007 | 2006 | 2005 |
| 11 | D1 | 2012 | 2011 | 2010 | 2009 | 2008 | 2007 | 2006 |
示例:2023赛季参加A3类别的球员出生年份为2004;2020赛季参加D2类别的球员出生年份为2008。
现有DataFrame字段
手头的DataFrame仅包含以下字段:
Player:球员姓名Season:参赛赛季(如2023、2022等)Category:参赛大类(仅A、B、C、D,无细分的A1、B2等)
赛事晋级规则
- D类别最多参赛2赛季,之后需晋级至C类别
- C类别最多参赛2赛季,之后需晋级至B类别
- B类别最多参赛2赛季,之后需晋级至A类别
- A类别最多可参赛3赛季
核心需求
编写函数,结合球员各赛季的参赛大类数据与晋级规则,推导其出生年份(同一球员的出生年份需统一)。对于缺失部分数据的情况(如仅单条2023赛季B类别记录),需返回类似2007 or 2008的结果。
示例目标输出
Player Category Season Year Birth Charly A 2023 2006 Andrew A 2023 2005 Louis A 2023 2004 Peter B 2023 2007 Charly B 2022 2006 Andrew A 2022 2005 Louis A 2022 2004 Louis A 2021 2004 Andrew B 2021 2005 Charly B 2021 2006 Peter B 2022 2007 Juan A 2022 2003 Juan A 2021 2003 Juan A 2020 2003 Michael C 2023 2011 Michael D 2022 2011
现有问题代码
import pandas as pd data = { 'Player': ['Charly', 'Andrew', 'Louis', 'Peter', 'Charly', 'Andrew', 'Louis', 'Louis', 'Andrew', 'Charly', 'Peter', 'Juan', 'Juan', 'Juan', 'Michael', 'Michael'], 'Category': ['A', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'B', 'B', 'B', 'A', 'A', 'A', 'C', 'D'], 'Season': [2023, 2023, 2023, 2023, 2022, 2022, 2022, 2021, 2021, 2021, 2022, 2022, 2021, 2020, 2023, 2022], } df = pd.DataFrame(data) def year_birth(player, df): seasons_a = df[(df['Player'] == player) & (df['Category'] == 'A')]['Season'] seasons_b = df[(df['Player'] == player) & (df['Category'] == 'B')]['Season'] seasons_c = df[(df['Player'] == player) & (df['Category'] == 'C')]['Season'] seasons_d = df[(df['Player'] == player) & (df['Category'] == 'D')]['Season'] if len(seasons_a) >= 3: return 2003 elif len(seasons_a) == 2: if len(seasons_b) >= 1: return 2004 else: return 2005 elif len(seasons_a) == 1: if len(seasons_b) >= 2: return 2005 else: return 2006 elif len(seasons_b) >= 2: return 2006 elif len(seasons_b) == 1: return 2007 elif len(seasons_c) >= 2: return 2008 elif len(seasons_d) == 1: return 2009 else: return None df['Year Birth'] = df.apply(lambda row: year_birth(row['Player'], df), axis=1) print(df)
优化方案
问题分析
现有代码存在以下缺陷:
- 硬编码出生年份,未结合赛季动态计算,无法适配不同赛季的映射关系
- 仅统计各类别参赛次数,未考虑赛季先后顺序与晋级规则的关联
- 未处理缺失数据的模糊匹配场景
优化代码实现
import pandas as pd # 1. 构建映射表数据并转换为结构化查询格式 mapping_data = [ [19, 'A3', 2004, 2003, 2002, 2001, 2000, 1999, 1998], [18, 'A2', 2005, 2004, 2003, 2002, 2001, 2000, 1999], [17, 'A1', 2006, 2005, 2004, 2003, 2002, 2001, 2000], [16, 'B2', 2007, 2006, 2005, 2004, 2003, 2002, 2001], [15, 'B1', 2008, 2007, 2006, 2005, 2004, 2003, 2002], [14, 'C2', 2009, 2008, 2007, 2006, 2005, 2004, 2003], [13, 'C1', 2010, 2009, 2008, 2007, 2006, 2005, 2004], [12, 'D2', 2011, 2010, 2009, 2008, 2007, 2006, 2005], [11, 'D1', 2012, 2011, 2010, 2009, 2008, 2007, 2006] ] mapping_df = pd.DataFrame(mapping_data, columns=['Age', 'Category', 2023, 2022, 2021, 2020, 2019, 2018, 2017]) # 建立年龄到大类的映射关系 age_to_main_cat = {} for _, row in mapping_df.iterrows(): age_to_main_cat[row['Age']] = row['Category'][0] # 定义各品类参赛次数上限 cat_max_seasons = {'A':3, 'B':2, 'C':2, 'D':2} # 定义品类晋级顺序(用于判断是否降级) cat_order = {'D':0, 'C':1, 'B':2, 'A':3} def get_birth_year(player, df): # 获取该球员所有参赛记录,按赛季升序排序 player_records = df[df['Player'] == player].sort_values('Season').reset_index(drop=True) if len(player_records) == 0: return None # 确定可能的出生年范围(从映射表的最小到最大出生年) min_year = mapping_df[2023].min() max_year = mapping_df[2023].max() valid_years = [] # 枚举所有可能的出生年,逐一验证是否符合规则 for birth_year in range(min_year, max_year + 1): valid = True cat_counts = {'A':0, 'B':0, 'C':0, 'D':0} prev_cat_level = -1 prev_age = -1 for _, record in player_records.iterrows(): season = record['Season'] record_cat = record['Category'] age = season - birth_year # 检查年龄是否在映射表范围内 if age not in age_to_main_cat: valid = False break # 检查年龄对应的大类是否与记录一致 if age_to_main_cat[age] != record_cat: valid = False break # 检查当前品类参赛次数是否超限 cat_counts[record_cat] += 1 if cat_counts[record_cat] > cat_max_seasons[record_cat]: valid = False break # 检查是否出现降级(比如从B类回到D类) current_cat_level = cat_order[record_cat] if current_cat_level < prev_cat_level: valid = False break # 检查年龄是否随赛季递增 if age <= prev_age: valid = False break prev_cat_level = current_cat_level prev_age = age if valid: valid_years.append(birth_year) # 处理结果输出 if len(valid_years) == 1: return valid_years[0] elif len(valid_years) > 1: return ' or '.join(map(str, sorted(valid_years))) else: return None # 按球员分组计算出生年,保证同一球员结果统一 df['Year Birth'] = df.groupby('Player')['Player'].transform(lambda x: get_birth_year(x.iloc[0], df)) print(df)
代码说明
- 结构化映射:将原始映射表转换为年龄与大类的对应关系,便于快速校验
- 枚举验证逻辑:遍历所有可能的出生年,逐一验证是否符合参赛记录、晋级规则(无降级、次数不超限)、年龄递增要求
- 统一结果处理:通过
groupby对每个球员单独计算,确保同一球员的出生年一致;支持多结果的模糊输出格式 - 适配全场景:既覆盖完整参赛记录的准确推导,也支持缺失数据的模糊匹配
内容的提问来源于stack exchange,提问作者nokvk
相关产品推荐
相关产品推荐

