本文目录导读:

在足球数据分析中,“长短传比例”通常指短传(成功率较高、距离较近)与长传(距离远、转移或发动进攻)的分布情况,由于不同数据源(如Opta、StatsBomb)对“长传”的定义不同(通常以距离为标准,如超过30米/35码,或传球性质为标准),这里我提供一个通用且可自定义阈值的Python统计脚本。
该脚本将自动完成以下功能:
- 读取包含传球数据的CSV/Excel文件。
- 根据你设定的距离阈值(或自定义规则)将传球分为“短传”和“长传”。
- 统计总占比、成功/失败比例,以及不同区域(前场/后场)的分布。
- 生成可视化图表(堆叠柱状图)。
准备工作
请确保你的传球数据表包含以下列(名称可自定义,脚本中会提示修改):
- player:球员名(可选)
- x, y:传球起点坐标
- end_x, end_y:传球终点坐标
- outcome:传球结果(如 “Complete” 或 “Incomplete”)
如果没有坐标,只有“传球距离”和“结果”列,也可以直接使用。
脚本代码(Python + Pandas + Matplotlib)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# ================== 配置区 ==================
FILE_PATH = 'passes.csv' # 文件路径
LONG_PASS_DISTANCE = 30.0 # 长传定义:距离(米)超过此值即算长传
# 如果你的数据是英制(码),请调整此值,25码 = 22.86米
# 坐标比例尺:如果坐标是0-100的百分比坐标,通常1单位=1米;如果是0-1,则乘以100
# 如果坐标单位是米,保持为1;如果是0-1,设置为100
COORD_SCALE = 1.0
# 列名映射(如果你的列名不同,请修改)
COL_X = 'x'
COL_Y = 'y'
COL_END_X = 'end_x'
COL_END_Y = 'end_y'
COL_OUTCOME = 'outcome'
SUCCESS_LABEL = 'Complete' # 表示成功传球的标记
# ================== 配置结束 ==================
print("正在读取数据...")
df = pd.read_csv(FILE_PATH)
# 数据预处理:确保坐标是数值
for col in [COL_X, COL_Y, COL_END_X, COL_END_Y]:
df[col] = pd.to_numeric(df[col], errors='coerce')
# 删除缺少坐标的数据
df = df.dropna(subset=[COL_X, COL_Y, COL_END_X, COL_END_Y])
# 计算传球距离(欧氏距离)
df['pass_distance'] = np.sqrt(
(df[COL_END_X] - df[COL_X])**2 +
(df[COL_END_Y] - df[COL_Y])**2
) * COORD_SCALE
# 根据距离分类
df['pass_type'] = np.where(df['pass_distance'] > LONG_PASS_DISTANCE, '长传', '短传')
# 判断成功与否(标准化:成功标记设为1)
df['is_success'] = (df[COL_OUTCOME] == SUCCESS_LABEL).astype(int)
print(f"总传球次数: {len(df)}")
print(f"平均传球距离: {df['pass_distance'].mean():.2f} 米")
# ========== 统计总体分布 ==========
print("\n===== 长短传总体分布 =====")
type_counts = df['pass_type'].value_counts()
type_percent = df['pass_type'].value_counts(normalize=True) * 100
for t in ['短传', '长传']:
if t in type_counts.index:
print(f"{t}: {type_counts[t]}次 ({type_percent[t]:.1f}%)")
else:
print(f"{t}: 0次 (0.0%)")
# ========== 统计成功率 ==========
print("\n===== 长短传成功率 =====")
success_stats = df.groupby('pass_type')['is_success'].agg(['mean', 'count'])
success_stats['mean'] = success_stats['mean'] * 100
success_stats.columns = ['成功率(%)', '总次数']
print(success_stats)
# ========== 按区域统计(前后场) ==========
print("\n===== 按球场区域分布(以x轴中线为界) =====")
# 假设x为横坐标,0为后场,100为前场
mid_line = 50.0
df['zone'] = np.where(df[COL_X] > mid_line, '前场', '后场')
# 分组统计
zone_type_stats = df.groupby(['zone', 'pass_type']).size().unstack(fill_value=0)
zone_type_stats['总数'] = zone_type_stats.sum(axis=1)
# 计算比例
zone_type_stats['短传占比'] = zone_type_stats.get('短传', 0) / zone_type_stats['总数'] * 100
zone_type_stats['长传占比'] = zone_type_stats.get('长传', 0) / zone_type_stats['总数'] * 100
print(zone_type_stats[['总数', '短传占比', '长传占比']])
# ========== 可视化 ==========
plt.rcParams['font.sans-serif'] = ['SimHei'] # 支持中文
plt.rcParams['axes.unicode_minus'] = False
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 图1:长短传数量及成功失败堆叠柱状图
plot_df = df.groupby(['pass_type', 'is_success']).size().unstack(fill_value=0)
plot_df.columns = ['失败', '成功']
plot_df = plot_df.reindex(['短传', '长传'])
plot_df.plot(kind='bar', stacked=True, ax=axes[0], color=['#d62728', '#2ca02c'])
axes[0].set_title('长短传成功/失败分布')
axes[0].set_ylabel('传球次数')
axes[0].set_xlabel('传球类型')
axes[0].legend(title='结果')
axes[0].tick_params(axis='x', rotation=0)
# 图2:前后场长短传占比堆积图
zone_chart_data = zone_type_stats[['短传占比', '长传占比']]
zone_chart_data.plot(kind='bar', stacked=True, ax=axes[1], color=['#1f77b4', '#ff7f0e'])
axes[1].set_title('前后场长短传占比分布')
axes[1].set_ylabel('占比 (%)')
axes[1].set_xlabel('球场区域')
axes[1].legend(title='传球类型')
axes[1].tick_params(axis='x', rotation=0)
plt.tight_layout()
plt.show()
# ========== 附加:进阶分析(可选) ==========
# 输出每个球员的长短传比例(如果有player列)
if 'player' in df.columns:
print("\n===== 球员长短传比例 (前10) =====")
player_stats = df.groupby(['player', 'pass_type']).size().unstack(fill_value=0)
player_stats['总数'] = player_stats.sum(axis=1)
player_stats['短传占比'] = player_stats.get('短传', 0) / player_stats['总数'] * 100
player_stats['长传占比'] = player_stats.get('长传', 0) / player_stats['总数'] * 100
# 过滤至少尝试10次传球的球员
player_stats = player_stats[player_stats['总数'] >= 10]
top_players = player_stats.sort_values('长传占比', ascending=False).head(10)
print(top_players[['总数', '短传占比', '长传占比']])
使用方法
-
安装依赖:
pip install pandas matplotlib seaborn numpy
-
准备数据:将你的传球数据保存为
passes.csv,确保包含上述列。 -
调整参数:根据你的数据源单位,修改
LONG_PASS_DISTANCE和COORD_SCALE。
常见数据格式适配
| 数据源 | 坐标范围 | 脚本调整 |
|---|---|---|
| StatsBomb | 0-120 (x), 0-80 (y) | COORD_SCALE = 1,阈值设为 30 (米) |
| Opta | 0-100 | COORD_SCALE = 1,阈值设为 25~30,视打法而定 |
| 事件数据(无坐标) | 只有“distance”列 | 可将脚本中计算距离的行替换为 df['distance'] |
如果你想直接用传球距离(distance列)而非坐标,替换这段代码:
# 原来的坐标计算 df['pass_distance'] = df['distance'] # 直接用现成的
输出示例
运行后终端会输出:
总传球次数: 2456
平均传球距离: 18.32 米
===== 长短传总体分布 =====
短传: 1820次 (74.1%)
长传: 636次 (25.9%)
===== 长短传成功率 =====
成功率(%) 总次数
pass_type
长传 56.2 636
短传 91.3 1820
以及两张图表:
- 左图:红绿堆叠柱状图,显示短传/长传的成功与失败次数。
- 右图:前后场长短传占比,直观看出球队在进攻三区是否更倾向于短传渗透。
进阶玩法
- 定义复杂长传:如果长传不仅是距离,还有“传中”、“转移”等属性,可在数据中加入
type列,df['pass_type'] = df.apply(lambda x: '长传' if (x['distance']>30 and x['type']=='cross') else '短传', axis=1)。 - 时间序列:按分钟分组,看比赛不同阶段的传接球策略变化。
- 方向分布:结合传球终点x坐标,判断是向前传球还是横传/回传。
这个脚本可完全按需修改,适应不同的统计口径,如果数据格式有特定要求,欢迎提供样例,我可以进一步优化。