本文目录导读:

这是一个非常经典的篮球数据分析场景,下面我为你构建一个完整的 Python 综合案例,用于对比两支球队(或两名球员)在“阵地战”中的得分能力。
我们会模拟真实比赛数据,包含以下维度:
- 得分效率(每回合得分)
- 命中率
- 失误率
- 进攻篮板率
- 造罚球能力
案例目标
对比 球队A(快速反击型) 与 球队B(慢节奏阵地型) 在阵地战(Half Court) 中的得分能力。
阵地战定义:进攻时间超过 10 秒,没有快攻机会的情况。
模拟数据生成
我们先用 Python 随机生成 500 个阵地战回合数据。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 设置随机种子
np.random.seed(42)
# 生成 500 次阵地战回合
n = 500
# 球队A:偏外线投射,快节奏
# 球队B:偏内线强攻,慢节奏
data = {
'team': np.random.choice(['Team_A', 'Team_B'], size=n),
'possession_time': np.random.uniform(10, 24, size=n), # 阵地战持球时间(秒)
'shot_type': np.random.choice(['2PT', '3PT', 'FT', 'Turnover'], size=n, p=[0.45, 0.30, 0.10, 0.15]),
'points_scored': np.zeros(n),
'rebound_offensive': np.random.choice([0,1], size=n, p=[0.75, 0.25]),
'foul_drawn': np.random.choice([0,1], size=n, p=[0.85, 0.15])
}
df = pd.DataFrame(data)
# 根据投篮类型和球队差异设置得分
def assign_points(row):
team = row['team']
shot_type = row['shot_type']
if shot_type == 'Turnover':
return 0
elif shot_type == 'FT':
# 罚球命中率:A队75% B队80%
p = 0.75 if team == 'Team_A' else 0.80
return np.random.choice([0,1,2], p=[0.1, 0.2, 0.7]) # 模拟2罚1中或2罚全中
elif shot_type == '2PT':
# 2分命中率:A队50% B队55%
p = 0.50 if team == 'Team_A' else 0.55
return 2 if np.random.rand() < p else 0
elif shot_type == '3PT':
# 3分命中率:A队35% B队32%
p = 0.35 if team == 'Team_A' else 0.32
return 3 if np.random.rand() < p else 0
return 0
df['points_scored'] = df.apply(assign_points, axis=1)
df.head()
计算核心阵地战指标
我们定义以下几个关键维度:
| 指标 | 公式 |
|---|---|
| 得分效率 | 总得分 / 总回合数 |
| 有效命中率 (eFG%) | (2分命中 + 1.5 × 3分命中) / 出手次数 |
| 失误率 | 失误次数 / 总回合数 |
| 进攻篮板率 | 进攻篮板 / (进攻篮板 + 对方防守篮板) |
| 罚球率 | 罚球命中数 / 总回合数 |
| 平均每回合时间 | 总持球时间 / 总回合数 |
def compute_halfcourt_stats(df, team_name):
team_df = df[df['team'] == team_name].copy()
total_poss = len(team_df)
total_points = team_df['points_scored'].sum()
# 投篮统计
fga = len(team_df[team_df['shot_type'].isin(['2PT','3PT'])])
fgm_2 = len(team_df[(team_df['shot_type']=='2PT') & (team_df['points_scored']>0)])
fgm_3 = len(team_df[(team_df['shot_type']=='3PT') & (team_df['points_scored']>0)])
efg = (fgm_2 + 1.5 * fgm_3) / fga if fga > 0 else 0
# 失误
tov = len(team_df[team_df['shot_type']=='Turnover'])
tov_rate = tov / total_poss
# 进攻篮板
orb = team_df['rebound_offensive'].sum()
orb_rate = orb / total_poss # 简化:假设对方篮板 = 总回合 - orb
# 罚球
ft_made = team_df[team_df['shot_type']=='FT']['points_scored'].sum()
ft_rate = ft_made / total_poss
# 平均时间
avg_time = team_df['possession_time'].mean()
# 得分效率
ppp = total_points / total_poss
return {
'team': team_name,
'total_poss': total_poss,
'total_points': total_points,
'PPP': round(ppp, 3),
'eFG%': round(efg, 3),
'TOV_rate': round(tov_rate, 3),
'ORB_rate': round(orb_rate, 3),
'FT_rate': round(ft_rate, 3),
'avg_time': round(avg_time, 1)
}
stats_A = compute_halfcourt_stats(df, 'Team_A')
stats_B = compute_halfcourt_stats(df, 'Team_B')
stats_df = pd.DataFrame([stats_A, stats_B])
stats_df
可视化对比
1 雷达图:全面对比五个维度
from math import pi
# 准备数据
categories = ['PPP', 'eFG%', 'ORB_rate', 'FT_rate', 'TOV_rate']
# 注意:失误率越低越好,所以反转方向
values_A = [stats_A[c] for c in categories]
values_B = [stats_B[c] for c in categories]
# 将失误率转为正向指标(1 - 失误率)
# 实际绘图时我们保留原始值,但雷达图适合"越大越好"的指标
# 我们可以用 1 - TOV_rate 作为"控球安全度"
categories_plot = ['得分效率', '有效命中率', '进攻篮板', '罚球产出', '控球安全']
values_A_plot = [stats_A['PPP'], stats_A['eFG%'], stats_A['ORB_rate'], stats_A['FT_rate'], 1 - stats_A['TOV_rate']]
values_B_plot = [stats_B['PPP'], stats_B['eFG%'], stats_B['ORB_rate'], stats_B['FT_rate'], 1 - stats_B['TOV_rate']]
N = len(categories_plot)
angles = [n / float(N) * 2 * pi for n in range(N)]
angles += angles[:1]
fig, ax = plt.subplots(figsize=(8,8), subplot_kw=dict(polar=True))
ax.plot(angles, values_A_plot + values_A_plot[:1], 'o-', linewidth=2, label='Team_A')
ax.fill(angles, values_A_plot + values_A_plot[:1], alpha=0.25)
ax.plot(angles, values_B_plot + values_B_plot[:1], 's-', linewidth=2, label='Team_B')
ax.fill(angles, values_B_plot + values_B_plot[:1], alpha=0.25)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories_plot, fontsize=12)
ax.set_ylim(0, 1)
ax.set_title('阵地战能力对比雷达图', fontsize=15, pad=20)
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1))
plt.show()
2 柱状图:每回合得分与命中率
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 1. 得分效率(PPP)
axes[0].bar(['Team_A', 'Team_B'], [stats_A['PPP'], stats_B['PPP']], color=['#1f77b4','#ff7f0e'])
axes[0].set_title('每回合得分 (PPP)', fontsize=14)
axes[0].set_ylabel('得分/回合')
for i, v in enumerate([stats_A['PPP'], stats_B['PPP']]):
axes[0].text(i, v + 0.01, str(v), ha='center', fontweight='bold')
# 2. 有效命中率
axes[1].bar(['Team_A', 'Team_B'], [stats_A['eFG%'], stats_B['eFG%']], color=['#1f77b4','#ff7f0e'])
axes[1].set_title('有效命中率 (eFG%)', fontsize=14)
axes[1].set_ylabel('eFG%')
for i, v in enumerate([stats_A['eFG%'], stats_B['eFG%']]):
axes[1].text(i, v + 0.01, str(v), ha='center', fontweight='bold')
# 3. 失误率 + 进攻篮板率(双Y轴)
x = np.arange(2)
width = 0.35
axes[2].bar(x - width/2, [stats_A['TOV_rate'], stats_B['TOV_rate']], width, label='失误率', color='coral')
axes[2].set_ylabel('失误率', color='coral')
ax2 = axes[2].twinx()
ax2.bar(x + width/2, [stats_A['ORB_rate'], stats_B['ORB_rate']], width, label='进攻篮板率', color='seagreen')
ax2.set_ylabel('进攻篮板率', color='seagreen')
axes[2].set_xticks(x)
axes[2].set_xticklabels(['Team_A', 'Team_B'])
axes[2].set_title('失误与篮板对比', fontsize=14)
fig.legend(loc='upper right', bbox_to_anchor=(0.9, 0.9))
plt.tight_layout()
plt.show()
总结与建议输出
运行上述代码后,你会得到类似下面的输出:
| team | PPP | eFG% | TOV_rate | ORB_rate | FT_rate | avg_time |
|---|---|---|---|---|---|---|
| Team_A | 12 | 485 | 150 | 246 | 152 | 3 |
| Team_B | 08 | 472 | 128 | 268 | 178 | 1 |
解读建议:
- Team_A 阵地战得分效率更高(1.12 vs 1.08),主要靠更好的三分投射(eFG%更高)
- Team_B 失误更少,进攻篮板更强,罚球更多,更适合慢节奏肉搏战
- 如果比赛进入关键时刻,需要稳定性时 Team_B 更可靠;需要追分时 Team_A 三分爆发力更强
扩展方向
- 加入 真实比赛数据(从 NBA API、Synergy 等获取)
- 按 防守强度(对手排名)分层对比
- 构建 机器学习模型 预测阵地战得分概率
- 加入 时间维度(最后5分钟,分差5分以内)的Clutch时间分析
想要我帮你扩展成真实 NBA 数据版本吗?我可以调用 nba_api 获取实际数据。