python案例如何分析球员跑动热点图?

wen python案例 1

本文目录导读:

python案例如何分析球员跑动热点图?

  1. 数据获取与准备
  2. 热点图生成
  3. 高级分析功能
  4. 统计分析指标
  5. 实时可视化系统
  6. 实战建议

我来详细介绍如何用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')

关键要点:

  1. 数据质量:确保位置数据准确,处理异常值
  2. 可视化选择:根据分析目的选择合适的可视化方法
  3. 统计分析:结合定量指标(距离、速度、面积)和空间分析
  4. 实战应用:可用于战术分析、球员评估、对手研究等

常见应用场景:

  • 战术布置评估
  • 球员位置适应性分析
  • 体能分配研究
  • 对阵型影响分析
  • 球员对比与选拔

抱歉,评论功能暂时关闭!