本文目录导读:

我来详细介绍如何用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')
关键要点:
- 数据质量:确保位置数据准确,处理异常值
- 可视化选择:根据分析目的选择合适的可视化方法
- 统计分析:结合定量指标(距离、速度、面积)和空间分析
- 实战应用:可用于战术分析、球员评估、对手研究等
常见应用场景:
- 战术布置评估
- 球员位置适应性分析
- 体能分配研究
- 对阵型影响分析
- 球员对比与选拔
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播