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

wen python案例 2

本文目录导读:

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

  1. 数据准备
  2. 基础热力图绘制
  3. 高级热力图分析
  4. 交互式可视化
  5. 输出分析报告
  6. 完整运行示例

我来详细介绍如何用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案例涵盖了:

  • 数据准备和模拟
  • 基础热力图绘制
  • 高级区域分析
  • 时间序列分析
  • 速度分析
  • 交互式可视化
  • 综合分析报告

你可以根据实际数据调整参数(如球场尺寸、采样频率等),或者添加更多自定义的分析功能。

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