本文目录导读:

我来详细介绍如何用Python分析球员跑动热点图,包括数据获取、处理和可视化的完整流程。
数据获取与准备
1 数据来源
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import seaborn as sns
from scipy import stats
from scipy.ndimage import gaussian_filter
# 模拟球员位置数据(实际可从以下来源获取)
# - StatsBomb API
# - Opta Sports
# - Metrica Sports
# - 自定义GPS追踪数据
# 创建示例数据
np.random.seed(42)
n_points = 5000
player_data = pd.DataFrame({
'timestamp': pd.date_range('2024-01-01', periods=n_points, freq='1S'),
'x': np.random.normal(50, 15, n_points), # 球场长度方向 0-100
'y': np.random.normal(34, 10, n_points), # 球场宽度方向 0-68
'speed': np.random.exponential(2, n_points), # 速度 m/s
'player_id': 'Player_1'
})
# 限制在球场范围内
player_data['x'] = player_data['x'].clip(0, 100)
player_data['y'] = player_data['y'].clip(0, 68)
热点图生成
1 基础热点图
def create_heatmap(data, bins=50, cmap='hot'):
"""
创建基础热点图
"""
fig, ax = plt.subplots(figsize=(12, 8))
# 绘制球场背景
draw_pitch(ax)
# 生成2D直方图
heatmap, xedges, yedges = np.histogram2d(
data['x'], data['y'],
bins=bins,
range=[[0, 100], [0, 68]]
)
# 平滑处理
heatmap_smooth = gaussian_filter(heatmap, sigma=1.5)
# 绘制热点图
extent = [0, 100, 0, 68]
im = ax.imshow(
heatmap_smooth.T,
extent=extent,
origin='lower',
cmap=cmap,
alpha=0.7,
aspect='auto'
)
plt.colorbar(im, ax=ax, label='活动频率')
ax.set_title('球员跑动热点图', fontsize=16)
ax.set_xlabel('球场长度 (米)')
ax.set_ylabel('球场宽度 (米)')
return fig, ax
def draw_pitch(ax):
"""
绘制标准足球场
"""
# 球场边界
ax.add_patch(Rectangle((0, 0), 100, 68, fill=False, edgecolor='black', linewidth=2))
# 中线
ax.plot([50, 50], [0, 68], 'k-', linewidth=1.5)
# 中圈
center_circle = plt.Circle((50, 34), 9.15, fill=False, edgecolor='black', linewidth=1.5)
ax.add_patch(center_circle)
# 禁区
ax.add_patch(Rectangle((0, 13.84), 16.5, 40.32, fill=False, edgecolor='black', linewidth=1.5))
ax.add_patch(Rectangle((83.5, 13.84), 16.5, 40.32, fill=False, edgecolor='black', linewidth=1.5))
# 小禁区
ax.add_patch(Rectangle((0, 24.84), 5.5, 18.32, fill=False, edgecolor='black', linewidth=1.5))
ax.add_patch(Rectangle((94.5, 24.84), 5.5, 18.32, fill=False, edgecolor='black', linewidth=1.5))
ax.set_xlim(-2, 102)
ax.set_ylim(-2, 70)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
2 基于速度的加权热点图
def create_weighted_heatmap(data, weight_col='speed', bins=50):
"""
根据速度加权生成热点图
"""
fig, axes = plt.subplots(1, 2, figsize=(20, 8))
# 基础热点图
ax1 = axes[0]
draw_pitch(ax1)
heatmap, _, _ = np.histogram2d(
data['x'], data['y'],
bins=bins,
range=[[0, 100], [0, 68]]
)
heatmap_smooth = gaussian_filter(heatmap, sigma=1.5)
im1 = ax1.imshow(
heatmap_smooth.T,
extent=[0, 100, 0, 68],
origin='lower',
cmap='hot',
alpha=0.8,
aspect='auto'
)
ax1.set_title('基础热点图', fontsize=14)
plt.colorbar(im1, ax=ax1, label='活动频率')
# 加权热点图
ax2 = axes[1]
draw_pitch(ax2)
weights = data[weight_col].values
heatmap_weighted, _, _ = np.histogram2d(
data['x'], data['y'],
bins=bins,
range=[[0, 100], [0, 68]],
weights=weights
)
heatmap_weighted_smooth = gaussian_filter(heatmap_weighted, sigma=1.5)
im2 = ax2.imshow(
heatmap_weighted_smooth.T,
extent=[0, 100, 0, 68],
origin='lower',
cmap='hot',
alpha=0.8,
aspect='auto'
)
ax2.set_title('速度加权热点图', fontsize=14)
plt.colorbar(im2, ax=ax2, label='速度加权活动量')
plt.tight_layout()
return fig
# 使用示例
fig = create_weighted_heatmap(player_data)
plt.show()
高级分析功能
1 分区域统计
def analyze_zones(data):
"""
将球场分为多个区域进行统计分析
"""
# 定义区域
zones = {
'防守三区': (0, 33),
'中场三区': (33, 67),
'进攻三区': (67, 100)
}
# 统计每个区域的停留时间
results = {}
for zone_name, (x_min, x_max) in zones.items():
zone_data = data[(data['x'] >= x_min) & (data['x'] < x_max)]
results[zone_name] = {
'停留时间(秒)': len(zone_data),
'平均速度': zone_data['speed'].mean(),
'最大速度': zone_data['speed'].max(),
'跑动距离(米)': (zone_data['speed'] * 1).sum() # 假设每秒采样
}
return pd.DataFrame(results).T
# 区域分析
zone_stats = analyze_zones(player_data)
print("区域统计结果:")
print(zone_stats.round(2))
2 动态热点图(时间序列)
def create_animated_heatmap(data, time_windows=10):
"""
创建时间序列热点图
"""
fig, axes = plt.subplots(2, 5, figsize=(25, 10))
axes = axes.flatten()
# 将数据分成时间段
data_sorted = data.sort_values('timestamp')
window_size = len(data) // time_windows
for i in range(time_windows):
ax = axes[i]
draw_pitch(ax)
start_idx = i * window_size
end_idx = (i + 1) * window_size if i < time_windows - 1 else len(data)
window_data = data_sorted.iloc[start_idx:end_idx]
if len(window_data) > 0:
heatmap, _, _ = np.histogram2d(
window_data['x'], window_data['y'],
bins=30,
range=[[0, 100], [0, 68]]
)
heatmap_smooth = gaussian_filter(heatmap, sigma=2)
im = ax.imshow(
heatmap_smooth.T,
extent=[0, 100, 0, 68],
origin='lower',
cmap='hot',
alpha=0.7,
aspect='auto'
)
time_start = window_data['timestamp'].min().strftime('%M:%S')
ax.set_title(f'时间段 {i+1}: {time_start}', fontsize=10)
else:
ax.set_title(f'时间段 {i+1}: 无数据', fontsize=10)
ax.set_xticks([])
ax.set_yticks([])
plt.suptitle('球员跑动热点图时间演化', fontsize=16, y=1.02)
plt.tight_layout()
return fig
3 对比分析
def compare_players_heatmaps(player1_data, player2_data):
"""
对比两个球员的热点图
"""
fig, axes = plt.subplots(1, 3, figsize=(24, 8))
# 球员1热点图
ax1 = axes[0]
draw_pitch(ax1)
heatmap1, _, _ = np.histogram2d(
player1_data['x'], player1_data['y'],
bins=40,
range=[[0, 100], [0, 68]]
)
heatmap1_smooth = gaussian_filter(heatmap1, sigma=1.5)
im1 = ax1.imshow(heatmap1_smooth.T, extent=[0, 100, 0, 68],
origin='lower', cmap='Blues', alpha=0.8, aspect='auto')
ax1.set_title(f"球员1: {player1_data['player_id'].iloc[0]}", fontsize=14)
plt.colorbar(im1, ax=ax1, label='活动频率')
# 球员2热点图
ax2 = axes[1]
draw_pitch(ax2)
heatmap2, _, _ = np.histogram2d(
player2_data['x'], player2_data['y'],
bins=40,
range=[[0, 100], [0, 68]]
)
heatmap2_smooth = gaussian_filter(heatmap2, sigma=1.5)
im2 = ax2.imshow(heatmap2_smooth.T, extent=[0, 100, 0, 68],
origin='lower', cmap='Reds', alpha=0.8, aspect='auto')
ax2.set_title(f"球员2: {player2_data['player_id'].iloc[0]}", fontsize=14)
plt.colorbar(im2, ax=ax2, label='活动频率')
# 差异图
ax3 = axes[2]
draw_pitch(ax3)
diff = heatmap1_smooth - heatmap2_smooth
im3 = ax3.imshow(diff.T, extent=[0, 100, 0, 68],
origin='lower', cmap='RdBu_r', alpha=0.8, aspect='auto')
ax3.set_title('活动差异 (球员1 - 球员2)', fontsize=14)
plt.colorbar(im3, ax=ax3, label='差异值')
plt.tight_layout()
return fig
统计分析指标
class PlayerMovementAnalyzer:
"""
球员跑动分析类
"""
def __init__(self, data):
self.data = data
self.results = {}
def calculate_metrics(self):
"""
计算关键跑动指标
"""
# 总跑动距离
self.results['总跑动距离(米)'] = (self.data['speed'] * 1).sum()
# 平均速度
self.results['平均速度(m/s)'] = self.data['speed'].mean()
# 最大速度
self.results['最大速度(m/s)'] = self.data['speed'].max()
# 高速跑动距离(>5.5 m/s)
high_speed = self.data[self.data['speed'] > 5.5]
self.results['高速跑动距离(米)'] = (high_speed['speed'] * 1).sum()
# 活动范围(覆盖面积)
x_range = self.data['x'].max() - self.data['x'].min()
y_range = self.data['y'].max() - self.data['y'].min()
self.results['活动范围(平方米)'] = x_range * y_range
# 重心位置
self.results['重心位置X'] = self.data['x'].mean()
self.results['重心位置Y'] = self.data['y'].mean()
return self.results
def get_heatmap_centroid(self):
"""
计算热点重心
"""
heatmap, xedges, yedges = np.histogram2d(
self.data['x'], self.data['y'],
bins=50,
range=[[0, 100], [0, 68]]
)
# 计算加权重心
x_centroid = np.sum(heatmap * (xedges[:-1] + xedges[1:]) / 2) / np.sum(heatmap)
y_centroid = np.sum(heatmap * (yedges[:-1] + yedges[1:]) / 2) / np.sum(heatmap)
return x_centroid, y_centroid
# 使用分析类
analyzer = PlayerMovementAnalyzer(player_data)
metrics = analyzer.calculate_metrics()
print("\n球员跑动指标:")
for key, value in metrics.items():
print(f"{key}: {value:.2f}")
centroid = analyzer.get_heatmap_centroid()
print(f"\n热点重心位置: ({centroid[0]:.2f}, {centroid[1]:.2f})")
实时可视化系统
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def create_interactive_heatmap(data):
"""
创建交互式热点图
"""
# 创建热力图
fig = go.Figure()
# 添加热力图
fig.add_trace(go.Histogram2dContour(
x=data['x'],
y=data['y'],
colorscale='Hot',
reversescale=False,
showscale=True,
contours=dict(
showlabels=True,
labelfont=dict(size=12, color='white')
),
colorbar=dict(title='活动密度'),
name='活动密度'
))
# 添加散点(可选)
fig.add_trace(go.Scatter(
x=data['x'].iloc[::10], # 采样显示
y=data['y'].iloc[::10],
mode='markers',
marker=dict(
size=2,
color=data['speed'].iloc[::10],
colorscale='Viridis',
colorbar=dict(title='速度(m/s)', x=1.15),
opacity=0.3
),
name='位置点',
showlegend=True
))
# 更新布局
fig.update_layout(
title='球员跑动交互式热点图',
xaxis=dict(title='球场长度(米)', range=[0, 100], constrain='domain'),
yaxis=dict(title='球场宽度(米)', range=[0, 68], scaleanchor='x', scaleratio=0.68),
width=1000,
height=700,
hovermode='closest'
)
return fig
# 生成交互式图表
# interactive_fig = create_interactive_heatmap(player_data)
# interactive_fig.show()
实战建议
1 数据预处理要点
def preprocess_data(raw_data):
"""
数据预处理流程
"""
df = raw_data.copy()
# 1. 处理缺失值
df = df.dropna(subset=['x', 'y'])
# 2. 坐标转换(如有需要)
# 从其他坐标系转换到0-100, 0-68
if df['x'].max() > 100:
df['x'] = df['x'] * 100 / df['x'].max()
if df['y'].max() > 68:
df['y'] = df['y'] * 68 / df['y'].max()
# 3. 异常值处理
df = df[(df['x'] >= 0) & (df['x'] <= 100)]
df = df[(df['y'] >= 0) & (df['y'] <= 68)]
# 4. 速度平滑(去除GPS噪声)
df['speed_smooth'] = df['speed'].rolling(window=5, center=True).mean()
# 5. 标准化时间
df = df.sort_values('timestamp')
return df
# 应用预处理
clean_data = preprocess_data(player_data)
2 性能优化
from numba import jit
import warnings
@jit(nopython=True, cache=True)
def fast_heatmap(x, y, bins, xrange, yrange):
"""
使用Numba加速的热力图计算
"""
heatmap = np.zeros((bins, bins))
x_bin_size = (xrange[1] - xrange[0]) / bins
y_bin_size = (yrange[1] - yrange[0]) / bins
for i in range(len(x)):
x_idx = min(int((x[i] - xrange[0]) / x_bin_size), bins - 1)
y_idx = min(int((y[i] - yrange[0]) / y_bin_size), bins - 1)
if x_idx >= 0 and y_idx >= 0:
heatmap[x_idx, y_idx] += 1
return heatmap
warnings.filterwarnings('ignore')
关键要点:
- 数据质量:确保位置数据准确,处理异常值
- 可视化选择:根据分析目的选择合适的可视化方法
- 统计分析:结合定量指标(距离、速度、面积)和空间分析
- 实战应用:可用于战术分析、球员评估、对手研究等
常见应用场景:
- 战术布置评估
- 球员位置适应性分析
- 体能分配研究
- 对阵型影响分析
- 球员对比与选拔