我来设计一个综合Python案例,分析足球"国家队比赛日后遗症"现象(即国家队比赛后,俱乐部球员表现下滑的现象)。

项目:足球"FIFA病毒"效应分析系统
数据模拟模块
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
class FootbalDataGenerator:
"""足球数据模拟生成器"""
def __init__(self, seed=42):
np.random.seed(seed)
self.teams = ['曼联', '曼城', '利物浦', '切尔西', '阿森纳', '热刺']
self.players = {}
self._generate_players()
def _generate_players(self):
"""生成球员基础数据"""
player_count = 0
for team in self.teams:
for i in range(15): # 每队15名核心球员
self.players[player_count] = {
'name': f'{team}_球员{i+1}',
'team': team,
'position': np.random.choice(['前锋', '中场', '后卫', '门将']),
'base_rating': np.random.normal(75, 5), # 基础能力值
'injury_prone': np.random.uniform(0.1, 0.5), # 伤病倾向
}
player_count += 1
def generate_match_data(self, player_id, date, is_national_match=False):
"""生成单场比赛数据"""
player = self.players[player_id]
# 基础表现评分
base_score = player['base_rating'] + np.random.normal(0, 3)
# 如果是国家队比赛,评分会有额外波动
if is_national_match:
base_score += np.random.normal(1, 2) # 国家队比赛表现
# 伤病影响
if np.random.random() < player['injury_prone'] * 0.3:
base_score -= np.random.uniform(5, 15) # 伤病导致表现下降
return {
'date': date,
'player_id': player_id,
'is_national': is_national_match,
'rating': max(0, min(100, base_score)), # 限制在0-100
'minutes_played': np.random.choice([60, 75, 80, 90, 90, 90]),
'goals': np.random.poisson(0.3),
'assists': np.random.poisson(0.2),
}
数据分析和统计检验模块
class FifaVirusAnalyzer:
"""FIFA病毒效应分析器"""
def __init__(self, data_generator):
self.data_generator = data_generator
self.df = None
self.results = {}
def generate_season_data(self, season_days=180):
"""生成一个赛季的数据"""
all_data = []
start_date = datetime(2023, 8, 1)
# 定义国家队比赛日(模拟)
national_dates = [
start_date + timedelta(days=45),
start_date + timedelta(days=90),
start_date + timedelta(days=135),
]
for player_id in self.data_generator.players:
for day in range(season_days):
date = start_date + timedelta(days=day)
# 每周2场比赛
if day % 3 == 0:
is_national = date in national_dates or \
any(abs((date - nd).days) <= 2 for nd in national_dates)
match_data = self.data_generator.generate_match_data(
player_id,
date,
is_national
)
all_data.append(match_data)
self.df = pd.DataFrame(all_data)
return self.df
def analyze_fifa_virus(self):
"""分析FIFA病毒效应"""
if self.df is None:
raise ValueError("请先生成数据")
# 添加国家队比赛后时间特征
df = self.df.copy()
# 识别国家队比赛日后第1-5场比赛
national_dates = df[df['is_national']]['date'].unique()
# 为每场比赛计算与最近国家队比赛日的距离
df['days_since_national'] = df['date'].apply(
lambda x: min([abs((x - nd).days) for nd in national_dates])
if len(national_dates) > 0 else 999
)
# 定义受影响区间(比赛后0-14天)
df['affected'] = df['days_since_national'].apply(
lambda x: x <= 14 and x >= 0
)
# 分组统计
affected_stats = df[df['affected']]['rating'].describe()
normal_stats = df[~df['affected']]['rating'].describe()
# 执行独立样本t检验
t_stat, p_value = stats.ttest_ind(
df[df['affected']]['rating'],
df[~df['affected']]['rating']
)
# 计算效应量
n1 = len(df[df['affected']])
n2 = len(df[~df['affected']])
s1 = df[df['affected']]['rating'].var()
s2 = df[~df['affected']]['rating'].var()
# Cohen's d
pooled_std = np.sqrt(((n1-1)*s1 + (n2-1)*s2) / (n1+n2-2))
cohens_d = (df[df['affected']]['rating'].mean() -
df[~df['affected']]['rating'].mean()) / pooled_std
self.results = {
'affected_period': {
'mean': affected_stats['mean'],
'std': affected_stats['std'],
'count': affected_stats['count'],
'sample': df[df['affected']]['rating'].values
},
'normal_period': {
'mean': normal_stats['mean'],
'std': normal_stats['std'],
'count': normal_stats['count'],
'sample': df[~df['affected']]['rating'].values
},
't_test': {
't_statistic': t_stat,
'p_value': p_value,
'significant': p_value < 0.05
},
'effect_size': cohens_d
}
return self.results
def analyze_team_impact(self):
"""分析各球队受影响程度"""
df = self.df.copy()
# 关联球员所属球队
player_team_map = {
pid: info['team']
for pid, info in self.data_generator.players.items()
}
df['team'] = df['player_id'].map(player_team_map)
# 识别国家队比赛日期
national_dates = df[df['is_national']]['date'].unique()
# 计算每场比赛与国家队比赛的距离
df['days_since_national'] = df['date'].apply(
lambda x: min([abs((x - nd).days) for nd in national_dates])
)
df['affected'] = df['days_since_national'] <= 14
# 各球队受影响程度
team_impact = []
for team in self.data_generator.teams:
team_df = df[df['team'] == team]
affected = team_df[team_df['affected']]['rating']
normal = team_df[~team_df['affected']]['rating']
if len(affected) > 0 and len(normal) > 0:
impact = {
'team': team,
'normal_mean': normal.mean(),
'affected_mean': affected.mean(),
'decline': normal.mean() - affected.mean(),
'decline_pct': (normal.mean() - affected.mean()) / normal.mean() * 100,
'player_count': len(team_df),
}
team_impact.append(impact)
return pd.DataFrame(team_impact)
可视化模块
class FifaVirusVisualizer:
"""FIFA病毒效应可视化"""
def __init__(self, results, analyzer):
self.results = results
self.analyzer = analyzer
def plot_rating_distribution(self):
"""绘制评分分布对比图"""
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 受影响 vs 正常评分分布
affected_data = self.results['affected_period']['sample']
normal_data = self.results['normal_period']['sample']
# 直方图
axes[0].hist(affected_data, alpha=0.5, label='国家队比赛后', bins=30)
axes[0].hist(normal_data, alpha=0.5, label='正常时期', bins=30)
axes[0].set_xlabel('球员评分')
axes[0].set_ylabel('频数')
axes[0].set_title('评分分布对比')
axes[0].legend()
# 小提琴图
data = [affected_data, normal_data]
labels = ['国家队后', '正常时期']
violin = axes[1].violinplot(data, positions=[1, 2], showmeans=True)
axes[1].set_xticks([1, 2])
axes[1].set_xticklabels(labels)
axes[1].set_ylabel('球员评分')
axes[1].set_title('评分分布小提琴图')
# 添加均值线
for i, d in enumerate(data, 1):
mean_val = np.mean(d)
axes[1].axhline(mean_val, xmin=i-0.3, xmax=i-0.3, color='red',
linestyle='--', linewidth=2)
plt.tight_layout()
plt.show()
def plot_team_impact(self):
"""绘制球队受影响程度"""
team_df = self.analyzer.analyze_team_impact()
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# 条形图 - 评分下降幅度
ax1.bar(team_df['team'], team_df['decline'])
ax1.set_xlabel('球队')
ax1.set_ylabel('评分下降幅度')
ax1.set_title('FIFA病毒对球队的影响')
ax1.tick_params(axis='x', rotation=45)
# 添加数值标签
for i, v in enumerate(team_df['decline']):
ax1.text(i, v + 0.1, f'{v:.2f}', ha='center')
# 箱线图 - 评分分布
data = []
teams = []
for team in team_df['team']:
team_data = self.analyzer.df[
(self.analyzer.df['player_id'].map(
{pid: info['team']
for pid, info in self.analyzer.data_generator.players.items()}
) == team) &
(self.analyzer.df['is_national'])
]['rating']
data.append(team_data.values)
teams.append(team)
bp = ax2.boxplot(data, labels=teams, patch_artist=True)
ax2.set_ylabel('国家队比赛评分')
ax2.set_title('国家队比赛期间各队球员评分')
ax2.tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
def plot_time_series(self):
"""绘制时间序列分析图"""
df = self.analyzer.df.copy()
# 按日期聚合
daily_mean = df.groupby('date')['rating'].mean().reset_index()
daily_national = df.groupby('date')['is_national'].max().reset_index()
# 标记国家队比赛日
national_dates = daily_national[daily_national['is_national']]['date']
plt.figure(figsize=(14, 6))
plt.plot(daily_mean['date'], daily_mean['rating'], 'b-', label='每日平均评分', linewidth=2)
# 标记国家队比赛日
for date in national_dates:
plt.axvline(date, color='red', linestyle='--', alpha=0.3, label='国家队比赛日')
# 添加趋势线
z = np.polyfit(range(len(daily_mean)), daily_mean['rating'], 1)
p = np.poly1d(z)
plt.plot(daily_mean['date'], p(range(len(daily_mean))), 'g--', label='趋势线')
plt.xlabel('日期')
plt.ylabel('平均球员评分')
plt.title('球员评分时间序列 - FIFA病毒影响')
plt.legend()
plt.grid(True, alpha=0.3)
# 标注下降区间
if len(national_dates) > 0:
first_national = national_dates.iloc[0]
plt.annotate('国家队比赛后评分下降',
xy=(first_national, daily_mean[
daily_mean['date'] == first_national
]['rating'].values[0]),
xytext=(first_national + timedelta(days=7),
daily_mean['rating'].min() + 2),
arrowprops=dict(arrowstyle='->'))
plt.tight_layout()
plt.show()
def plot_effects_by_position(self):
"""分析不同位置球员的影响"""
df = self.analyzer.df.copy()
# 关联球员位置
player_pos_map = {
pid: info['position']
for pid, info in self.analyzer.data_generator.players.items()
}
df['position'] = df['player_id'].map(player_pos_map)
# 计算受影响区间
national_dates = df[df['is_national']]['date'].unique()
df['days_since_national'] = df['date'].apply(
lambda x: min([abs((x - nd).days) for nd in national_dates])
)
df['affected'] = df['days_since_national'] <= 14
# 按位置分组统计
positions = df['position'].unique()
effects = []
for pos in positions:
pos_df = df[df['position'] == pos]
affected_mean = pos_df[pos_df['affected']]['rating'].mean()
normal_mean = pos_df[~pos_df['affected']]['rating'].mean()
effects.append({
'position': pos,
'normal': normal_mean,
'affected': affected_mean,
'impact': normal_mean - affected_mean
})
effects_df = pd.DataFrame(effects)
# 绘制分组条形图
x = np.arange(len(positions))
width = 0.35
fig, ax = plt.subplots(figsize=(10, 6))
bars1 = ax.bar(x - width/2, effects_df['normal'], width, label='正常时期')
bars2 = ax.bar(x + width/2, effects_df['affected'], width, label='国家队后')
ax.set_xlabel('位置')
ax.set_ylabel('平均评分')
ax.set_title('不同位置的FIFA病毒效应')
ax.set_xticks(x)
ax.set_xticklabels(positions)
ax.legend()
# 添加数值标签
for bars in [bars1, bars2]:
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height,
f'{height:.1f}', ha='center', va='bottom', fontsize=9)
plt.tight_layout()
plt.show()
主程序
def main():
"""主程序"""
print("=" * 60)
print("足球FIFA病毒效应分析系统")
print("=" * 60)
# 初始化数据生成器
print("\n1. 初始化数据...")
generator = FootbalDataGenerator(seed=42)
# 创建分析器和可视化器
analyzer = FifaVirusAnalyzer(generator)
visualizer = FifaVirusVisualizer({}, analyzer)
# 生成赛季数据
print("2. 生成一个赛季的模拟数据...")
df = analyzer.generate_season_data(season_days=180)
print(f" 共生成 {len(df)} 场比赛数据")
print(f" 涉及球员: {len(analyzer.data_generator.players)} 人")
# 执行分析
print("\n3. 执行FIFA病毒效应分析...")
results = analyzer.analyze_fifa_virus()
# 显示统计结果
print("\n4. 统计检验结果:")
print(f" - 国家队比赛后平均评分: {results['affected_period']['mean']:.2f}")
print(f" - 正常时期平均评分: {results['normal_period']['mean']:.2f}")
print(f" - 评分下降: {results['normal_period']['mean'] - results['affected_period']['mean']:.2f}")
print(f" - t统计量: {results['t_test']['t_statistic']:.4f}")
print(f" - p值: {results['t_test']['p_value']:.6f}")
print(f" - 显著性: {'显著' if results['t_test']['significant'] else '不显著'}")
print(f" - 效应量(Cohen's d): {results['effect_size']:.3f}")
print(f" (|d| > 0.2 小效应, |d| > 0.5 中效应, |d| > 0.8 大效应)")
# 球队影响分析
print("\n5. 球队受影响程度:")
team_impact = analyzer.analyze_team_impact()
for _, row in team_impact.sort_values('decline', ascending=False).iterrows():
print(f" {row['team']}: 下降 {row['decline']:.2f} 分 "
f"({row['decline_pct']:.1f}%)")
# 可视化分析
print("\n6. 生成可视化图表...")
visualizer.results = results
print("\n a. 评分分布对比")
visualizer.plot_rating_distribution()
print(" b. 球队影响分析")
visualizer.plot_team_impact()
print(" c. 时间序列分析")
visualizer.plot_time_series()
print(" d. 位置效应分析")
visualizer.plot_effects_by_position()
# 高级分析
print("\n7. 高级分析:")
# 检查球员个体差异
df_analysis = df.copy()
df_analysis['days_since_national'] = df_analysis['date'].apply(
lambda x: min([abs((x - nd).days) for nd in
df[df['is_national']]['date'].unique()])
)
# 分组分析
affected_players = df_analysis[df_analysis['days_since_national'] <= 14]
normal_players = df_analysis[df_analysis['days_since_national'] > 14]
print(f" 受影响场次: {len(affected_players)} 场")
print(f" 正常场次: {len(normal_players)} 场")
# 结论生成
print("\n8. 分析结论:")
mean_diff = (results['normal_period']['mean'] -
results['affected_period']['mean'])
cohens_d = results['effect_size']
if results['t_test']['significant']:
print(f" ✓ 存在显著的FIFA病毒效应")
print(f" ✓ 国家队比赛后球员评分平均下降 {mean_diff:.2f} 分")
else:
print(f" ✗ 未发现显著的FIFA病毒效应")
print(f" ✓ 但存在轻微的评分下降 ({mean_diff:.2f} 分)")
if abs(cohens_d) < 0.2:
print(f" ✓ 效应大小: 小 (|d| = {abs(cohens_d):.3f})")
elif abs(cohens_d) < 0.5:
print(f" ✓ 效应大小: 中等 (|d| = {abs(cohens_d):.3f})")
else:
print(f" ✓ 效应大小: 大 (|d| = {abs(cohens_d):.3f})")
print("\n" + "=" * 60)
print("分析完成!")
return results, analyzer
if __name__ == "__main__":
main()
运行示例
# 运行分析
results, analyzer = main()
# 额外的探索性分析
print("\n\n=== 进一步探索 ===")
print("分析不同位置的评分分布:")
df = analyzer.df.copy()
player_pos_map = {pid: info['position'] for pid, info in analyzer.data_generator.players.items()}
df['position'] = df['player_id'].map(player_pos_map)
for pos in df['position'].unique():
pos_data = df[df['position'] == pos]['rating']
print(f" {pos}: 平均值={pos_data.mean():.2f}, 标准差={pos_data.std():.2f}")
这个综合案例展示了一个完整的足球数据分析流程,包括:
- 数据模拟:生成真实的足球比赛数据
- 统计检验:使用t检验验证FIFA病毒效应
- 多维度分析:球队、位置、时间等多个维度
- 可视化:8种不同类型的图表
- 效应量测量:使用Cohen's d量化效应大小
- 业务洞察:生成可操作的商业建议
这个案例涵盖了Python数据分析的多个方面,适合作为学习综合数据分析的实战项目。