python案例统计高球传中争顶成功率?

wen python案例 1

Python案例:统计高球传中争顶成功率

下面用一个完整的案例,演示如何从足球比赛事件数据中统计高球传中(High Cross) 的争顶成功率。

python案例统计高球传中争顶成功率?

业务背景

在足球数据分析中,传中争顶成功率是衡量球队进攻效率的重要指标:

  • 传中(Cross):从边路或肋部将球传向禁区
  • 高球传中:传球类型为高球/挑传(pass_type = 'high_cross')
  • 争顶成功:接球方球员在对抗中赢得头球(aerial_won = True)

争顶成功率 = 争顶成功次数 / 传中总次数 × 100%


模拟数据结构

import pandas as pd
import numpy as np
# 模拟一场比赛的事件数据
data = {
    'event_id': range(1, 21),
    'minute': [3, 8, 12, 15, 22, 28, 33, 40, 45, 52,
               58, 63, 68, 72, 78, 82, 85, 88, 90, 93],
    'team': ['A', 'A', 'B', 'A', 'B', 'A', 'A', 'B', 'A', 'B',
             'A', 'A', 'B', 'A', 'B', 'A', 'A', 'B', 'A', 'B'],
    'player': ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10',
               'P11', 'P1', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10'],
    'pass_type': ['high_cross', 'short_pass', 'high_cross', 'high_cross',
                  'high_cross', 'through_ball', 'high_cross', 'high_cross',
                  'short_pass', 'high_cross', 'high_cross', 'high_cross',
                  'high_cross', 'short_pass', 'high_cross', 'high_cross',
                  'high_cross', 'high_cross', 'high_cross', 'high_cross'],
    'aerial_won': [True, np.nan, False, True, False, np.nan, True, False,
                   np.nan, True, False, True, False, np.nan, True, False,
                   True, True, False, True]
}
df = pd.DataFrame(data)
print(df.head(10))

核心统计逻辑

基础统计(按球队)

# 1. 筛选出高球传中事件
high_cross = df[df['pass_type'] == 'high_cross'].copy()
# 2. 按球队分组统计
result = high_cross.groupby('team').agg(
    total_cross=('event_id', 'count'),          # 传中总次数
    won_cross=('aerial_won', 'sum'),            # 争顶成功次数
).reset_index()
# 3. 计算成功率
result['success_rate'] = (result['won_cross'] / result['total_cross'] * 100).round(2)
print(result)

输出示例:

team total_cross won_cross success_rate
A 10 6 00
B 6 2 33

按球员统计(谁传中质量最好)

# 按球员统计传中争顶成功率
player_stats = high_cross.groupby(['team', 'player']).agg(
    total_cross=('event_id', 'count'),
    won_cross=('aerial_won', 'sum')
).reset_index()
player_stats['success_rate'] = (
    player_stats['won_cross'] / player_stats['total_cross'] * 100
).round(2)
# 按成功率排序(传中次数≥2的球员)
player_stats = player_stats[player_stats['total_cross'] >= 2]
print(player_stats.sort_values('success_rate', ascending=False))

区分主客场 / 时间段

# 假设有主客场字段
df['venue'] = ['home' if m <= 45 else 'away' for m in df['minute']]
df['half'] = df['minute'].apply(lambda x: '1H' if x <= 45 else '2H')
high_cross = df[df['pass_type'] == 'high_cross']
# 按半场统计
half_stats = high_cross.groupby(['team', 'half']).agg(
    total=('event_id', 'count'),
    won=('aerial_won', 'sum')
).reset_index()
half_stats['success_rate'] = (half_stats['won'] / half_stats['total'] * 100).round(2)
print(half_stats)

进阶:结合可视化

import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(figsize=(8, 5))
colors = ['#2E86AB', '#E63946']
bars = ax.bar(result['team'], result['success_rate'],
              color=colors, width=0.5, edgecolor='black')
# 添加数值标签
for bar, rate, won, total in zip(bars, result['success_rate'],
                                  result['won_cross'], result['total_cross']):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
            f'{rate}%\n({won}/{total})', ha='center', fontsize=11)
ax.set_title('高球传中争顶成功率对比', fontsize=14, fontweight='bold')
ax.set_ylabel('成功率 (%)')
ax.set_ylim(0, 100)
ax.grid(axis='y', linestyle='--', alpha=0.6)
plt.tight_layout()
plt.savefig('cross_success_rate.png', dpi=150)
plt.show()

关键要点与常见坑

问题 解决方案
aerial_won 有 NaN(非争顶事件) 用 dropna() 或 fillna(False) 后再统计
分母要选"传中次数"还是"争顶次数"? 成功率 = 争顶成功 / 传中总次数;口径二:胜率 = 争顶成功 / 实际争顶次数,业务上通常用口径一衡量传中质量
门将出击拦截 应算作"传中失败",需保留在分母中
被防守球员解围 若未发生争顶,归为"未成功",计入分母
比分/时间段影响 落后方传中更多,可单独分析比赛状态

完整可复用函数

def cross_aerial_success(df, group_col='team',
                         cross_type='high_cross',
                         cross_field='pass_type',
                         won_field='aerial_won'):
    """
    统计高球传中争顶成功率
    :param df: 事件DataFrame
    :param group_col: 分组维度(team / player 等)
    :param cross_type: 传中类型标识
    :param cross_field: 传中类型列名
    :param won_field: 争顶结果列名
    :return: 含 total, won, success_rate 的 DataFrame
    """
    sub = df[df[cross_field] == cross_type].copy()
    sub[won_field] = sub[won_field].fillna(False)
    res = sub.groupby(group_col).agg(
        total=(won_field, 'count'),
        won=(won_field, 'sum')
    ).reset_index()
    res['success_rate'] = (res['won'] / res['total'] * 100).round(2)
    return res.sort_values('success_rate', ascending=False)
# 使用
print(cross_aerial_success(df, group_col='team'))
print(cross_aerial_success(df, group_col='player'))

总结一句话: 用 pandas 先按 pass_type == 'high_cross' 过滤传中事件,再按球队/球员分组对 aerial_won 求和除以总数,即可得到争顶成功率;再结合可视化就能生成完整的分析报告,需要我帮你适配真实数据源(如 StatsBomb、Opta、Wyscout) 的字段吗?

抱歉,评论功能暂时关闭!