本文目录导读:

我来详细介绍如何用Python分析球员跑动热点图(热力图),这是一个完整的实战案例:
数据准备
我们需要球员跑动数据,这里提供两种方式:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import seaborn as sns
from scipy.stats import gaussian_kde
import warnings
warnings.filterwarnings('ignore')
# 方法1:模拟数据(假设场景)
def generate_position_data(num_frames=1000, player_speed=8):
"""生成模拟球员跑动位置数据"""
np.random.seed(42)
# 足球场尺寸:105m x 68m
positions = []
x, y = 50, 34 # 初始位置(中场)
for i in range(num_frames):
# 模拟球员移动模式
if i % 100 == 0: # 改变跑动策略
target_x = np.random.uniform(0, 105)
target_y = np.random.uniform(0, 68)
# 向目标移动
dx = target_x - x
dy = target_y - y
dist = np.sqrt(dx**2 + dy**2)
if dist > 1:
# 加入一些随机性
step = min(player_speed, dist)
x += (dx/dist) * step + np.random.normal(0, 0.5)
y += (dy/dist) * step + np.random.normal(0, 0.5)
# 限制在场地内
x = np.clip(x, 0, 105)
y = np.clip(y, 0, 68)
positions.append([i*0.1, x, y]) # 时间戳、x坐标、y坐标
return pd.DataFrame(positions, columns=['time', 'x', 'y'])
# 方法2:加载真实数据(如果需要)
# df = pd.read_csv('player_tracking_data.csv')
# 生成示例数据
df = generate_position_data(num_frames=2000, player_speed=8)
print(df.head())
print(f"数据点数量: {len(df)}")
基础热力图绘制
def plot_basic_heatmap(df, title="球员跑动热点图"):
"""绘制基础热力图"""
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
# 图1:2D直方图(热力图)
heatmap, xedges, yedges = np.histogram2d(
df['x'], df['y'],
bins=(40, 30), # 网格数量
range=[[0, 105], [0, 68]]
)
im = ax[0].imshow(heatmap.T, origin='lower',
extent=[0, 105, 0, 68],
cmap='hot', # 使用热点图配色
aspect='equal', alpha=0.8)
plt.colorbar(im, ax=ax[0], label='访问频率')
# 图2:等高线图
x = df['x'].values
y = df['y'].values
# 使用KDE核密度估计
kde = gaussian_kde(np.vstack([x, y]),
bw_method=0.1) # 带宽参数控制平滑度
# 创建网格
xi = np.linspace(0, 105, 100)
yi = np.linspace(0, 68, 100)
zi = kde(np.meshgrid(xi, yi))
# 绘制等高线
contour = ax[1].contourf(xi, yi, zi, levels=15, cmap='YlOrRd')
plt.colorbar(contour, ax=ax[1], label='密度')
for axes in ax:
# 绘制球场边界
axes.set_xlim(0, 105)
axes.set_ylim(0, 68)
axes.set_aspect('equal')
axes.set_xlabel('X坐标(米)')
axes.set_ylabel('Y坐标(米)')
axes.set_title(title)
# 绘制中场线
axes.axhline(y=34, color='green', linestyle='--', alpha=0.3)
axes.axvline(x=52.5, color='green', linestyle='--', alpha=0.3)
# 绘制禁区
axes.axhline(y=13.84, xmin=0.9, xmax=1, color='gray', alpha=0.5)
axes.axhline(y=54.16, xmin=0.9, xmax=1, color='gray', alpha=0.5)
plt.tight_layout()
plt.show()
return heatmap
# 运行基础热力图
heatmap = plot_basic_heatmap(df)
高级热力图分析
class PlayerHeatmapAnalyzer:
"""球员跑动热力图分析器"""
def __init__(self, df, field_length=105, field_width=68):
self.df = df
self.field_length = field_length
self.field_width = field_width
def analyze_zone_coverage(self):
"""分析不同区域的覆盖情况"""
# 将球场分为9个区域
zones = {
'前场左路': (0, 33, 45, 68), # (x_min, x_max, y_min, y_max)
'前场中路': (0, 33, 22, 45),
'前场右路': (0, 33, 0, 22),
'中场左路': (33, 66, 45, 68),
'中场中路': (33, 66, 22, 45),
'中场右路': (33, 66, 0, 22),
'后场左路': (66, 105, 45, 68),
'后场中路': (66, 105, 22, 45),
'后场右路': (66, 105, 0, 22)
}
zone_presence = {}
zone_hotspots = {}
for zone, (x_min, x_max, y_min, y_max) in zones.items():
mask = (self.df['x'] >= x_min) & (self.df['x'] < x_max) & \
(self.df['y'] >= y_min) & (self.df['y'] < y_max)
zone_presence[zone] = self.df[mask].shape[0] / len(self.df) * 100
# 计算区域内的热点(最常出现的位置)
if self.df[mask].shape[0] > 0:
zone_data = self.df[mask]
kde = gaussian_kde(np.vstack([zone_data['x'].values,
zone_data['y'].values]))
xi = np.linspace(x_min, x_max, 20)
yi = np.linspace(y_min, y_max, 20)
zi = kde(np.meshgrid(xi, yi))
max_idx = np.unravel_index(zi.argmax(), zi.shape)
zone_hotspots[zone] = (xi[max_idx[0]], yi[max_idx[1]])
return zone_presence, zone_hotspots
def analyze_time_patterns(self):
"""分析时间维度的跑动模式"""
# 按时间段分析
if 'time' in self.df.columns:
max_time = self.df['time'].max()
time_bins = pd.cut(self.df['time'], bins=5)
time_stats = {}
for interval, group in self.df.groupby(time_bins):
# 计算该时段内的跑动距离
if len(group) > 1:
distance = np.sqrt(np.diff(group['x'].values)**2 +
np.diff(group['y'].values)**2)
total_distance = np.sum(distance)
avg_speed = total_distance / (len(group) * 0.1) # 假设0.1秒每帧
time_stats[str(interval)] = {
'distance': total_distance,
'avg_speed': avg_speed,
'positions': len(group)
}
return time_stats
return None
def visualize_comprehensive(self):
"""综合可视化分析"""
fig = plt.figure(figsize=(18, 12))
# 1. 主热力图(密度图)
ax1 = plt.subplot(2, 3, 1)
self._plot_density_heatmap(ax1)
# 2. 区域热力图
ax2 = plt.subplot(2, 3, 2)
self._plot_zone_heatmap(ax2)
# 3. 时间序列图
ax3 = plt.subplot(2, 3, 3)
self._plot_temporal_analysis(ax3)
# 4. 移动轨迹图
ax4 = plt.subplot(2, 3, 4)
self._plot_movement_trace(ax4)
# 5. 速度分布图
ax5 = plt.subplot(2, 3, 5)
self._plot_speed_distribution(ax5)
# 6. 热区百分比饼图
ax6 = plt.subplot(2, 3, 6)
self._plot_zone_distribution(ax6)
plt.tight_layout()
plt.show()
def _plot_density_heatmap(self, ax):
"""绘制密度热力图"""
x = self.df['x'].values
y = self.df['y'].values
kde = gaussian_kde(np.vstack([x, y]), bw_method=0.08)
xi = np.linspace(0, self.field_length, 100)
yi = np.linspace(0, self.field_width, 100)
zi = kde(np.meshgrid(xi, yi))
# 使用自定义配色
colors = ['blue', 'cyan', 'green', 'yellow', 'red']
cmap = LinearSegmentedColormap.from_list('custom', colors, N=100)
im = ax.contourf(xi, yi, zi, levels=20, cmap=cmap)
plt.colorbar(im, ax=ax, label='密度')
self._draw_field(ax)
ax.set_title('球员跑动热点密度图')
def _plot_zone_heatmap(self, ax):
"""绘制区域热力图"""
zone_presence, zone_hotspots = self.analyze_zone_coverage()
# 绘制9宫格区域
colors = ['red', 'orange', 'yellow', 'lightgreen', 'green']
for idx, (zone, percentage) in enumerate(zone_presence.items()):
# 根据覆盖率设置颜色深浅
color_intensity = percentage / 20 # 归一化
if '前场' in zone:
x_start, x_end = 0, 35
elif '中场' in zone:
x_start, x_end = 35, 70
else:
x_start, x_end = 70, 105
if '左路' in zone:
y_start, y_end = 45, 68
elif '中路' in zone:
y_start, y_end = 22, 45
else:
y_start, y_end = 0, 22
rect = plt.Rectangle((x_start, y_start), x_end-x_start, y_end-y_start,
alpha=min(color_intensity, 1),
color='red' if percentage > 15 else 'orange' if percentage > 10 else 'yellow')
ax.add_patch(rect)
# 标注区域覆盖率
ax.text(x_start+10, y_start+8, f'{percentage:.1f}%',
fontsize=8, ha='center')
self._draw_field(ax)
ax.set_title('区域跑动覆盖率')
def _plot_temporal_analysis(self, ax):
"""绘制时间序列分析"""
time_stats = self.analyze_time_patterns()
if time_stats:
times = list(time_stats.keys())
distances = [stats['distance'] for stats in time_stats.values()]
speeds = [stats['avg_speed'] for stats in time_stats.values()]
ax2 = ax.twinx()
line1, = ax.plot(range(len(distances)), distances, 'b-', label='距离')
line2, = ax2.plot(range(len(speeds)), speeds, 'r-', label='速度')
ax.set_xlabel('时间段')
ax.set_ylabel('跑动距离 (m)', color='b')
ax2.set_ylabel('平均速度 (m/s)', color='r')
ax.set_xticks(range(len(times)))
ax.set_xticklabels([t[8:13] for t in times], rotation=45)
lines = [line1, line2]
labels = [l.get_label() for l in lines]
ax.legend(lines, labels, loc='upper left')
ax.set_title('时间维度分析')
def _plot_movement_trace(self, ax):
"""绘制运动轨迹"""
# 采样部分数据点避免过于密集
sample = self.df.iloc[::50]
ax.plot(sample['x'], sample['y'], 'b-', alpha=0.3, linewidth=1, label='轨迹')
# 标记起始点和终点
ax.plot(self.df['x'].iloc[0], self.df['y'].iloc[0], 'go', markersize=10, label='起始点')
ax.plot(self.df['x'].iloc[-1], self.df['y'].iloc[-1], 'r*', markersize=15, label='终点')
self._draw_field(ax, draw_center=False)
ax.legend(loc='upper right')
ax.set_title('球员移动轨迹')
def _plot_speed_distribution(self, ax):
"""绘制速度分布"""
# 计算连续帧之间的速度和方向
dx = np.diff(self.df['x'].values)
dy = np.diff(self.df['y'].values)
distance = np.sqrt(dx**2 + dy**2)
# 假设采样频率为10Hz (0.1秒每帧)
speed = distance / 0.1
# 过滤明显异常值
speed = speed[speed < 15] # 足球运动员最大速度约10m/s
ax.hist(speed, bins=30, color='skyblue', edgecolor='black', alpha=0.7)
ax.axvline(speed.mean(), color='red', linestyle='--', label=f'平均速度: {speed.mean():.2f} m/s')
ax.axvline(speed.median(), color='green', linestyle='--', label=f'中位数: {speed.median():.2f} m/s')
ax.set_xlabel('速度 (m/s)')
ax.set_ylabel('频次')
ax.set_title('速度分布')
ax.legend()
def _plot_zone_distribution(self, ax):
"""绘制区域分布饼图"""
zone_presence, _ = self.analyze_zone_coverage()
# 分类
categories = {
'前场': 0,
'中场': 0,
'后场': 0
}
for zone, percentage in zone_presence.items():
for key in categories:
if key in zone:
categories[key] += percentage
labels = list(categories.keys())
values = list(categories.values())
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1']
wedges, texts, autotexts = ax.pie(values, labels=labels, colors=colors,
autopct='%1.1f%%', startangle=90)
ax.set_title('区域分布占比')
def _draw_field(self, ax, draw_center=True):
"""绘制球场背景"""
ax.set_xlim(0, self.field_length)
ax.set_ylim(0, self.field_width)
ax.set_aspect('equal')
ax.set_facecolor('lightgreen')
# 绘制边界线
ax.plot([0, 0, 105, 105, 0], [0, 68, 68, 0, 0], 'k-', linewidth=2)
# 中场线
if draw_center:
ax.axhline(y=34, color='white', linestyle='-', alpha=0.5)
ax.axvline(x=52.5, color='white', linestyle='-', alpha=0.5)
# 中圈
circle = plt.Circle((52.5, 34), 9.15, color='white', fill=False, alpha=0.5)
ax.add_patch(circle)
# 禁区
# 左侧禁区
ax.add_patch(plt.Rectangle((0, 13.84), 16.5, 14.32,
facecolor='none', edgecolor='white', alpha=0.5))
ax.add_patch(plt.Rectangle((0, 30.34), 5.5, 7.32,
facecolor='none', edgecolor='white', alpha=0.5))
# 右侧禁区
ax.add_patch(plt.Rectangle((88.5, 13.84), 16.5, 14.32,
facecolor='none', edgecolor='white', alpha=0.5))
ax.add_patch(plt.Rectangle((99.5, 30.34), 5.5, 7.32,
facecolor='none', edgecolor='white', alpha=0.5))
ax.set_xlabel('X坐标 (米)')
ax.set_ylabel('Y坐标 (米)')
# 使用分析器
analyzer = PlayerHeatmapAnalyzer(df)
analyzer.visualize_comprehensive()
交互式可视化
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def interactive_heatmap(df):
"""创建交互式热力图"""
# 计算KDE
x = df['x'].values
y = df['y'].values
kde = gaussian_kde(np.vstack([x, y]), bw_method=0.1)
# 创建网格
xi = np.linspace(0, 105, 200)
yi = np.linspace(0, 68, 200)
xi, yi = np.meshgrid(xi, yi)
positions = np.vstack([xi.ravel(), yi.ravel()])
zi = kde(positions).reshape(xi.shape)
# 创建交互式图
fig = make_subplots(
rows=1, cols=2,
subplot_titles=('2D Heatmap', '3D Surface'),
specs=[[{'type': 'heatmap'}, {'type': 'surface'}]]
)
# 2D热力图
fig.add_trace(
go.Heatmap(
z=zi,
x=xi[0],
y=yi[:, 0],
colorscale='Jet',
colorbar=dict(title='密度'),
hovertemplate='X: %{x:.1f}m<br>Y: %{y:.1f}m<br>密度: %{z:.3f}<extra></extra>'
),
row=1, col=1
)
# 3D曲面图
fig.add_trace(
go.Surface(
x=xi,
y=yi,
z=zi,
colorscale='Jet',
hovertemplate='X: %{x:.1f}m<br>Y: %{y:.1f}m<br>密度: %{z:.3f}<extra></extra>'
),
row=1, col=2
)
# 更新布局
fig.update_layout(
title='球员跑动热点图 - 交互式分析',
height=600,
showlegend=False
)
fig.update_xaxes(title_text='X坐标 (m)', row=1, col=1)
fig.update_yaxes(title_text='Y坐标 (m)', row=1, col=1)
fig.show()
# 运行交互式可视化
interactive_heatmap(df)
输出分析报告
def generate_analysis_report(df):
"""生成分析报告"""
analyzer = PlayerHeatmapAnalyzer(df)
print("=" * 50)
print("球员跑动热力图分析报告")
print("=" * 50)
# 基础统计
print("\n1. 基础统计:")
print(f" - 总数据点数: {len(df)}")
print(f" - 覆盖时间: {df['time'].max():.1f}秒")
# 计算总跑动距离
dx = np.diff(df['x'].values)
dy = np.diff(df['y'].values)
total_distance = np.sum(np.sqrt(dx**2 + dy**2))
print(f" - 总跑动距离: {total_distance:.1f}米")
# 计算平均速度
avg_speed = total_distance / df['time'].max()
print(f" - 平均速度: {avg_speed:.2f}米/秒")
# 区域分析
print("\n2. 区域覆盖分析:")
zone_presence, zone_hotspots = analyzer.analyze_zone_coverage()
for zone, percentage in sorted(zone_presence.items(), key=lambda x: x[1], reverse=True):
print(f" - {zone}: {percentage:.1f}% 的时间")
# 热点位置
print("\n3. 主要热点位置:")
for zone, (x, y) in zone_hotspots.items():
print(f" - {zone}: ({x:.1f}, {y:.1f})")
# 时间模式
print("\n4. 时间模式分析:")
time_stats = analyzer.analyze_time_patterns()
if time_stats:
for time_slot, stats in time_stats.items():
print(f" - {time_slot}: 距离{stats['distance']:.1f}m, "
f"速度{stats['avg_speed']:.2f}m/s")
print("\n5. 分析结论:")
# 判断球员类型
forward_percentage = zone_presence.get('前场中路', 0) + zone_presence.get('前场左路', 0) + zone_presence.get('前场右路', 0)
if forward_percentage > 40:
print(" * 球员倾向于前场活动,可能是前锋或攻击型球员")
elif forward_percentage < 20:
print(" * 球员主要在己方半场活动,可能是防守型球员")
else:
print(" * 球员覆盖全场,活动范围广泛")
# 判断活动模式
hot_zone = max(zone_hotspots.items(), key=lambda x: x[0])
print(f" * 最活跃区域: {hot_zone[0]}")
print("=" * 50)
# 生成报告
generate_analysis_report(df)
完整运行示例
# 完整流程
if __name__ == "__main__":
# 1. 生成数据
print("生成球员跑动数据...")
df = generate_position_data(num_frames=3000, player_speed=8)
# 2. 创建分析器
analyzer = PlayerHeatmapAnalyzer(df)
# 3. 绘制基础热力图
print("绘制基础热力图...")
plot_basic_heatmap(df)
# 4. 进行综合分析
print("进行综合分析...")
analyzer.visualize_comprehensive()
# 5. 交互式可视化
print("创建交互式可视化...")
interactive_heatmap(df)
# 6. 生成报告
generate_analysis_report(df)
这个完整的Python案例涵盖了:
- 数据准备和模拟
- 基础热力图绘制
- 高级区域分析
- 时间序列分析
- 速度分析
- 交互式可视化
- 综合分析报告
你可以根据实际数据调整参数(如球场尺寸、采样频率等),或者添加更多自定义的分析功能。