康心伴Logo
康心伴WellAlly
Health

Python时间序列预测:识别运动平台期 | WellAlly康心伴

5 分钟阅读

Python时间序列预测:识别运动平台期

概述

运动平台期是健身过程中常见的现象——无论如何努力训练,体重、力量或耐力指标都无法继续提升。通过时间序列分析,我们可以提前预测平台期的到来,并及时调整训练计划。

本文将介绍如何使用Python的Prophet库进行运动数据的时间序列预测,帮助你:

  • 识别当前是否处于平台期
  • 预测未来几周的运动表现
  • 生成个性化的预警信号

数据准备

1. 收集运动数据

code
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# 模拟运动数据(实际应用中从健身API获取)
def generate_workout_data(days=90):
    """生成模拟的运动数据"""
    dates = pd.date_range(start='2026-01-01', periods=days)

    # 基础体重 + 趋势 + 周期性 + 噪声
    base_weight = 75
    trend = np.linspace(0, -3, days)  # 90天减重3kg
    weekly_pattern = 0.5 * np.sin(2 * np.pi * np.arange(days) / 7)
    noise = np.random.normal(0, 0.2, days)

    weight = base_weight + trend + weekly_pattern + noise

    df = pd.DataFrame({
        'ds': dates,
        'y': weight
    })

    return df

# 生成数据
workout_data = generate_workout_data(90)
print(workout_data.head())
Code collapsed

2. 处理真实数据

code
# 从Apple Health或Google Fit导入数据
import json

def import_health_data(file_path):
    """从健康应用导出的JSON文件导入数据"""
    with open(file_path, 'r') as f:
        data = json.load(f)

    records = []
    for entry in data.get('records', []):
        if entry.get('type') == 'weight':
            records.append({
                'ds': pd.to_datetime(entry['date']),
                'y': float(entry['value'])
            })

    return pd.DataFrame(records).sort_values('ds')
Code collapsed

Prophet模型训练

基础预测

code
from prophet import Prophet
import matplotlib.pyplot as plt

# 创建Prophet模型
model = Prophet(
    yearly_seasonality=False,  # 数据少于一年
    weekly_seasonality=True,   # 每周周期(训练/休息)
    daily_seasonality=False,
    changepoint_prior_scale=0.05,  # 控制趋势灵活性
    seasonality_prior_scale=10.0   # 季节性强度
)

# 训练模型
model.fit(workout_data)

# 创建未来30天的预测
future = model.make_future_dataframe(periods=30)
forecast = model.predict(future)

# 可视化预测
fig1 = model.plot(forecast)
plt.title('运动表现预测(体重变化)')
plt.xlabel('日期')
plt.ylabel('体重 (kg)')
plt.show()
Code collapsed

添加自定义季节性

code
# 添加月度周期(女性生理周期影响)
model.add_seasonality(
    name='monthly',
    period=30.5,
    fourier_order=5
)

# 添加训练日/休息日影响
workout_data['is_training_day'] = workout_data['ds'].dt.dayofweek < 6  # 周一到周六

model.add_regressor('is_training_day')

# 重新训练
model.fit(workout_data)
forecast = model.predict(future)
Code collapsed

平台期检测算法

1. 趋势斜率分析

code
def detect_plateau_by_slope(forecast, window_days=14, threshold=0.01):
    """
    通过趋势斜率检测平台期

    参数:
        forecast: Prophet预测结果
        window_days: 检测窗口(天)
        threshold: 斜率阈值

    返回:
        plateau_start: 平台期开始日期
        is_plateau: 是否处于平台期
    """
    # 获取最近window_days的预测趋势
    recent_forecast = forecast.tail(window_days)

    # 计算线性回归斜率
    x = np.arange(len(recent_forecast))
    y = recent_forecast['trend'].values
    slope = np.polyfit(x, y, 1)[0]

    # 判断是否平台期
    is_plateau = abs(slope) < threshold

    if is_plateau:
        # 找到平台期开始点
        trend_changes = forecast[forecast['trend'].diff().abs() > threshold]
        if len(trend_changes) > 0:
            plateau_start = trend_changes.iloc[-1]['ds']
        else:
            plateau_start = forecast.iloc[0]['ds']
    else:
        plateau_start = None

    return {
        'is_plateau': is_plateau,
        'slope': slope,
        'plateau_start': plateau_start,
        'days_in_plateau': window_days if is_plateau else 0
    }

# 检测平台期
plateau_info = detect_plateau_by_slope(forecast)
print(f"是否处于平台期: {plateau_info['is_plateau']}")
print(f"当前斜率: {plateau_info['slope']:.4f} kg/天")
Code collapsed

2. 置信区间分析

code
def detect_plateau_by_uncertainty(forecast, confidence_threshold=0.5):
    """
    通过预测不确定性检测平台期

    当预测的置信区间变宽时,说明模型无法确定趋势
    这通常是平台期的信号
    """
    recent = forecast.tail(14)

    # 计算置信区间宽度
    uncertainty = recent['yhat_upper'] - recent['yhat_lower']
    avg_uncertainty = uncertainty.mean()

    # 与历史不确定性比较
    historical_uncertainty = (
        forecast['yhat_upper'] - forecast['yhat_lower']
    ).mean()

    # 不确定性显著增加 = 平台期
    is_plateau = avg_uncertainty > 1.5 * historical_uncertainty

    return {
        'is_plateau': is_plateau,
        'uncertainty_ratio': avg_uncertainty / historical_uncertainty,
        'avg_uncertainty': avg_uncertainty
    }
Code collapsed

预警系统

综合预警评分

code
class WorkoutPlateauDetector:
    def __init__(self, forecast):
        self.forecast = forecast
        self.indicators = {}

    def calculate_plateau_score(self):
        """计算平台期综合评分 (0-100)"""
        scores = {}

        # 1. 趋势斜率评分
        slope_info = detect_plateau_by_slope(self.forecast)
        slope_score = min(100, abs(slope_info['slope']) * 1000)
        scores['trend_slope'] = 100 - slope_score
        self.indicators['trend_slope'] = slope_info['slope']

        # 2. 不确定性评分
        uncertainty_info = detect_plateau_by_uncertainty(self.forecast)
        uncertainty_score = min(100, uncertainty_info['uncertainty_ratio'] * 50)
        scores['uncertainty'] = uncertainty_score
        self.indicators['uncertainty_ratio'] = uncertainty_info['uncertainty_ratio']

        # 3. 实际值vs预测值偏差
        recent_actual = self.forecast['yhat'].tail(14).values
        recent_predicted = self.forecast['yhat'].tail(14).values
        mae = np.mean(np.abs(recent_actual - recent_predicted))
        deviation_score = min(100, mae * 100)
        scores['deviation'] = deviation_score
        self.indicators['mae'] = mae

        # 综合评分
        plateau_score = np.mean(list(scores.values()))

        return {
            'plateau_score': plateau_score,
            'level': self._get_plateau_level(plateau_score),
            'indicators': self.indicators,
            'recommendations': self._get_recommendations(plateau_score)
        }

    def _get_plateau_level(self, score):
        if score < 30:
            return '正常进步期'
        elif score < 50:
            return '进步放缓'
        elif score < 70:
            return '平台期预警'
        else:
            return '平台期'

    def _get_recommendations(self, score):
        if score < 30:
            return ['保持当前训练计划', '继续监测数据']
        elif score < 50:
            return ['考虑增加训练强度', '调整饮食结构']
        elif score < 70:
            return ['建议引入新的训练方式', '检查恢复是否充分']
        else:
            return ['强烈建议更换训练计划', '考虑安排减周', '咨询专业教练']

# 使用预警系统
detector = WorkoutPlateauDetector(forecast)
result = detector.calculate_plateau_score()

print(f"平台期评分: {result['plateau_score']:.1f}/100")
print(f"当前状态: {result['level']}")
print(f"\n建议:")
for rec in result['recommendations']:
    print(f"  - {rec}")
Code collapsed

可视化仪表板

Plotly交互式图表

code
import plotly.graph_objects as go
from plotly.subplots import make_subplots

def create_workout_dashboard(forecast, actual_data, plateau_info):
    """创建运动数据仪表板"""
    fig = make_subplots(
        rows=2, cols=2,
        subplot_titles=('体重趋势预测', '预测组件分解',
                       '平台期检测', '每周模式'),
        specs=[[{"secondary_y": True}, {"secondary_y": True}],
               [{"type": "indicator"} for _ in range(2)]]
    )

    # 1. 主趋势图
    fig.add_trace(
        go.Scatter(x=actual_data['ds'], y=actual_data['y'],
                   mode='markers', name='实际数据', marker=dict(size=4)),
        row=1, col=1
    )
    fig.add_trace(
        go.Scatter(x=forecast['ds'], y=forecast['yhat'],
                   mode='lines', name='预测', line=dict(color='blue')),
        row=1, col=1
    )
    fig.add_trace(
        go.Scatter(x=forecast['ds'], y=forecast['yhat_upper'],
                   mode='lines', line=dict(width=0), showlegend=False),
        row=1, col=1
    )
    fig.add_trace(
        go.Scatter(x=forecast['ds'], y=forecast['yhat_lower'],
                   mode='lines', line=dict(width=0), fill='tonexty',
                   name='置信区间', fillcolor='rgba(0,100,80,0.1)'),
        row=1, col=1
    )

    # 2. 组件分解
    fig.add_trace(
        go.Scatter(x=forecast['ds'], y=forecast['trend'],
                   name='趋势', line=dict(color='red')),
        row=1, col=2
    )
    fig.add_trace(
        go.Scatter(x=forecast['ds'], y=forecast['weekly'],
                   name='每周周期', line=dict(color='green')),
        row=1, col=2
    )

    # 3. 平台期指示器
    fig.add_trace(
        go.Indicator(
            mode="gauge+number",
            value=plateau_info['plateau_score'],
            domain={'x': [0, 1], 'y': [0, 1]},
            title={'text': "平台期风险"},
            gauge={
                'axis': {'range': [None, 100]},
                'bar': {'color': "darkblue"},
                'steps': [
                    {'range': [0, 30], 'color': "lightgreen"},
                    {'range': [30, 50], 'color': "yellow"},
                    {'range': [50, 70], 'color': "orange"},
                    {'range': [70, 100], 'color': "red"}
                ],
                'threshold': {
                    'line': {'color': "red", 'width': 4},
                    'thickness': 0.75,
                    'value': 70
                }
            }
        ),
        row=2, col=1
    )

    fig.update_layout(
        height=800,
        showlegend=True,
        title_text="运动表现分析仪表板"
    )

    return fig

# 创建仪表板
dashboard = create_workout_dashboard(forecast, workout_data, result)
dashboard.show()
Code collapsed

关键要点

  1. Prophet适合运动数据预测:自动处理趋势和季节性
  2. 多指标综合判断:斜率、不确定性、偏差
  3. 提前预警是关键:提前1-2周识别平台期
  4. 可视化辅助决策:交互式图表更直观
  5. 结合专业建议:数据仅供参考,需配合教练指导

常见问题

需要多少数据才能预测?

建议至少60天的连续数据。数据越多,预测越准确。

平台期一定是坏事吗?

不一定。平台期可能意味着:

  • 身体适应了当前训练
  • 需要休息恢复
  • 是突破前的积累期

如何打破平台期?

  1. 改变训练刺激:新的训练方式/强度
  2. 调整营养:增加/减少热量摄入
  3. 优化恢复:睡眠、压力管理
  4. 安排减周:主动恢复后再突破

参考资料

  • Facebook Prophet官方文档
  • 运动科学杂志:平台期研究
  • 《NSCA体能训练指南》

发布日期:2026年3月8日 最后更新:2026年3月8日

免责声明: 本内容仅供教育参考,不能替代专业医疗建议。请咨询医生获取个性化诊断和治疗方案。

#

文章标签

Python
时间序列分析
运动科学
平台期预测
机器学习

觉得这篇文章有帮助?

立即体验康心伴,开始您的健康管理之旅