本文目录导读:

- 案例设计:足球俱乐部新任主教练“蜜月期”分析
- 第一部分:数据准备(模拟数据)
- 第二部分:核心分析逻辑(寻找蜜月期终结拐点)
- 第三部分:可视化呈现
- 第四部分:进阶分析(短中长效应拆解)
- 第五部分:最终模型总结与输出
- 运行效果预期
- 深入思考(如果使用真实数据)
这是一个非常经典且有趣的数据分析和数据可视化综合案例,我们可以从体育数据网站(如 Kaggle 或 Transfermarkt)获取数据,或者利用 Python 模拟一份数据,来探究“新帅上任”后的战绩变化,从而量化“蜜月期”的时长。
下面,我将为您设计一个完整的 Python 实战案例,包含数据模拟、数据清洗、特征工程、可视化分析和结论输出。
案例设计:足球俱乐部新任主教练“蜜月期”分析
核心假设:新教练上任后,球队通常会因为“更衣室氛围改善”或“战术新刺激”而短期内战绩提升,但随时间推移,战绩会回落至球队真实实力水平。蜜月期就是从上任开始到球队状态回落到长期平均水平之间的时间窗口。
第一部分:数据准备(模拟数据)
由于真实数据获取受限,我们用 numpy 生成一段带有“回升-高峰-回落”特征的时间序列数据,模拟某球队在新帅上任后 20 场比赛的积分表现。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.signal import savgol_filter
import seaborn as sns
# 设置中文和风格
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False
sns.set_style("whitegrid")
# 设置随机种子保证可复现
np.random.seed(42)
# 模拟场景:球队真实实力(长线均值)每场积分约为 1.2 分(胜=3,平=1,负=0)
# 新帅上任带来了约 5 场的战术红利期
n_matches = 30 # 观察30场
# 生成基础趋势:先低(前教练遗留问题),冲高(红利),回落(回归均值)
base_trend = np.concatenate([
np.linspace(0.5, 0.8, 5), # 上任前或初期磨合(积分率低)
np.linspace(2.2, 2.8, 6), # 蜜月期爆发(胜率高)
np.linspace(2.0, 1.2, 10), # 蜜月期结束,回落到真实水平
np.linspace(1.2, 1.0, 9) # 稳定期
])
# 加入噪声(比赛随机性)
noise = np.random.normal(0, 0.8, n_matches)
points_per_game = np.clip(base_trend + noise, 0, 3) # 每场得分0-3
# 构造 DataFrame
df = pd.DataFrame({
'match_no': range(1, n_matches + 1),
'points': points_per_game.round(2),
'cumulative_points': np.cumsum(points_per_game).round(2)
})
print("前10行数据预览:")
print(df.head(10))
第二部分:核心分析逻辑(寻找蜜月期终结拐点)
我们不能直接用原始数据进行判断,因为噪声大,我们需要平滑数据,并计算滚动平均和斜率变化。
方法论:
- 计算“累积场均积分”的滚动窗口均值(例如窗口=5),用于平滑。
- 寻找滚动均值的最高点。
- 蜜月期通常定义为:从新帅执教首场比赛,到“滚动积分率”首次跌破“球队长期平均值(使用整体均值)”。
# 添加列:滚动场均积分(窗口为5,覆盖最近5场)
df['rolling_avg_5'] = df['points'].rolling(window=5, min_periods=1).mean()
# 计算全局长期均值(作为基线实力)
long_term_avg = df['points'].mean()
# 计算平滑后的曲线(使用Savitzky-Golay滤波,保留趋势特征)
# window_length需要是奇数,这里使用11
df['smoothed'] = savgol_filter(df['points'], window_length=11, polyorder=2)
# 寻找蜜月期拐点:
# 拐点定义:平滑曲线从峰值开始下降,并且跌破长期均值的那一场比赛
smoothed_series = df['smoothed'].values
peak_idx = np.argmax(smoothed_series) # 峰值位置
# 在峰值之后,寻找第一次跌破长期均值的点
honeymoon_end_idx = None
for i in range(peak_idx, len(smoothed_series)):
if smoothed_series[i] < long_term_avg:
honeymoon_end_idx = i
break
# 如果全程都高于均值,则认为蜜月期较长,取最后一个点
if honeymoon_end_idx is None:
honeymoon_end_idx = len(smoothed_series) - 1
print("="*60)
print(f"📊 球队长期场均积分均值: {long_term_avg:.2f} 分")
print(f"🏔️ 战绩峰值出现在第 {peak_idx + 1} 轮 (场均 {smoothed_series[peak_idx]:.2f} 分)")
print(f"⏰ 模型判定:新任主帅的蜜月期大约持续了 {honeymoon_end_idx + 1} 轮比赛")
print(f" (即从第1轮到第{honeymoon_end_idx + 1}轮)")
# 保存结论用于可视化
honeymoon_length = honeymoon_end_idx + 1
第三部分:可视化呈现
我们要画出核心图:原始散点、平滑曲线、长期均值线,并高亮蜜月期区域。
# 创建画布
fig, ax = plt.subplots(figsize=(12, 6))
# 1. 原始数据点(每场比赛得分)
ax.scatter(df['match_no'], df['points'],
color='lightgray', s=40, label='单场实际得分 (含噪声)', zorder=1)
# 2. 滚动平均线(更直观)
ax.plot(df['match_no'], df['rolling_avg_5'],
color='blue', linestyle='--', linewidth=2, label='5场滚动场均积分', zorder=2)
# 3. 平滑趋势线(核心分析线)
ax.plot(df['match_no'], df['smoothed'],
color='crimson', linewidth=3, label='平滑趋势线 (过滤噪声)', zorder=3)
# 4. 长期平均水平线
ax.axhline(y=long_term_avg, color='green', linestyle=':', linewidth=2,
label=f'长期平均水平 ({long_term_avg:.2f}分/场)')
# 5. 高亮蜜月期区域
ax.axvspan(xmin=1, xmax=honeymoon_length, alpha=0.2, color='orange',
label=f'🏆 判定蜜月期 ({honeymoon_length}轮)')
# 6. 标记峰值点和蜜月期结束点
peak_row = df.iloc[peak_idx]
ax.plot(peak_row['match_no'], peak_row['smoothed'], 'o',
color='gold', markersize=12, markeredgecolor='black', label='战术红利峰值', zorder=5)
if honeymoon_end_idx != peak_idx:
end_row = df.iloc[honeymoon_end_idx]
ax.plot(end_row['match_no'], end_row['smoothed'], 'X',
color='purple', markersize=12, label='蜜月期结束 (回落至均值)', zorder=5)
# 图表属性
ax.set_title(f'🏟️ 新帅上任 "蜜月期" 分析 (模型判定: {honeymoon_length} 轮)', fontsize=14, fontweight='bold')
ax.set_xlabel('执教轮次 (Match No.)', fontsize=12)
ax.set_ylabel('单场积分 / 场均积分', fontsize=12)
ax.set_xlim(1, n_matches)
ax.legend(loc='upper right', fontsize=9)
plt.tight_layout()
plt.show()
第四部分:进阶分析(短中长效应拆解)
仅仅看总时长不够,我们还可以分段评估“换帅红利”的衰减过程,用回归分析来对比不同阶段的斜率。
# 定义阶段
phase1_end = min(peak_idx + 1, len(df)) # 上升期
phase2_end = honeymoon_length # 蜜月结束
phase3_end = len(df) # 长期稳定
# 分段线性拟合 (使用 numpy.polyfit)
def fit_slope(start, end):
"""返回该区间内一条线性回归线的斜率"""
x_data = df['match_no'].iloc[start:end]
y_data = df['cumulative_points'].iloc[start:end] # 用累积值更容易看出增速
coeffs = np.polyfit(x_data, y_data, 1)
return coeffs[0] # 斜率 (每轮增加积分)
# 三个阶段的增益(斜率)
slope_phase1 = fit_slope(0, phase1_end)
slope_phase2 = fit_slope(phase1_end, phase2_end)
slope_phase3 = fit_slope(phase2_end, phase3_end)
print("📈 阶段斜率分析 (累积积分线性拟合斜率):")
print(f" 第一阶段 (战术爆发期, 前{phase1_end}轮): 斜率 = {slope_phase1:.2f} 分/轮")
print(f" 第二阶段 (蜜月维持期, 第{phase1_end+1}-{phase2_end}轮): 斜率 = {slope_phase2:.2f} 分/轮")
print(f" 第三阶段 (常态化运营, 第{phase2_end+1}轮后): 斜率 = {slope_phase3:.2f} 分/轮")
# 可视化累积积分曲线 (显示换帅红利)
fig2, ax2 = plt.subplots(figsize=(10, 5))
ax2.plot(df['match_no'], df['cumulative_points'],
marker='o', linewidth=2, color='darkblue', label='累积积分')
# 绘制三段趋势参考线
for (start, end, color, name) in [
(0, phase1_end, 'red', '爆发期'),
(phase1_end, phase2_end, 'orange', '蜜月期'),
(phase2_end, phase3_end, 'gray', '稳定期')
]:
x = df['match_no'].iloc[start:end]
y = df['cumulative_points'].iloc[start:end]
if len(x) > 1:
z = np.polyfit(x, y, 1)
p = np.poly1d(z)
ax2.plot(x, p(x), linestyle='--', linewidth=2, color=color, label=f'{name}趋势线')
ax2.set_title('换帅效应:累积积分增长规律与斜率变化', fontsize=14)
ax2.set_xlabel('场次')
ax2.set_ylabel('累积积分')
ax2.legend()
plt.tight_layout()
plt.show()
第五部分:最终模型总结与输出
将以上分析整合成一个函数,便于复用。
def analyze_new_coach_honeymoon(points_series, plot=True):
"""
输入: 新帅带队后各场的积分数组(0-3分/场)
输出: 蜜月期长度 , 峰值场次
"""
n = len(points_series)
df = pd.DataFrame({'match_no': range(1, n+1), 'points': points_series})
df['smoothed'] = savgol_filter(df['points'], window_length=min(11, n if n%2==1 else n-1), polyorder=2)
long_term_avg = df['points'].mean()
peak_idx = np.argmax(df['smoothed'])
honeymoon_end_idx = None
for i in range(peak_idx, n):
if df['smoothed'].iloc[i] < long_term_avg:
honeymoon_end_idx = i
break
if honeymoon_end_idx is None:
honeymoon_end_idx = n-1
if plot:
# ... (上面的绘图代码,此处省略)
pass
return {
"honeymoon_length": honeymoon_end_idx + 1,
"peak_match": peak_idx + 1,
"peak_points_per_game": df['smoothed'].iloc[peak_idx],
"long_term_average": long_term_avg
}
# 调用分析
results = analyze_new_coach_honeymoon(df['points'].values, plot=False)
print("\n🎯 输出结果摘要:")
for key, value in results.items():
print(f" - {key}: {value:.2f}" if isinstance(value, float) else f" - {key}: {value}")
运行效果预期
在上述随机种子(seed=42)生成的模拟数据下,程序会输出类似:
- 球队长期场均积分均值:约 1.4 分
- 峰值出现在约第 8 轮,场均约 2.5 分
- 判定蜜月期约 12-15 轮(取决于噪声影响)
- 可视化图表会显示清晰的“上升-平台-下滑”三段式曲线,橙色区域即为蜜月期。
深入思考(如果使用真实数据)
如果你有某一球队的真实数据,可以应用同样的逻辑,只需替换 df['points'] 的数据源,如果是跨教练的对比,只需循环调用该函数即可。
这种分析思路可扩展到股价、商业指标、政策实施效果等多个场景,核心方法就是平滑去噪 + 基线比较 + 拐点搜寻,这也是数据科学在实际业务中常用的“增量价值分析”逻辑。