国际足球比赛数据集分析报告(1872-2025)
项目概述
本项目基于Kaggle国际足球比赛数据集,对1872年至2025年间的国际男子足球比赛进行全面分析。
通过数据清洗、多维度数据分析和数据可视化,揭示足球运动的发展趋势、球队表现、赛事特征和主场优势等关键洞察。
数据规模:51,234场国际足球比赛记录
文章摘要
本研究基于Kaggle国际足球比赛数据集,对1872-2025年间51,234场国际男子足球比赛进行系统性分析。研究采用Python技术栈(Pandas、Matplotlib、Seaborn、Pyecharts),通过数据清洗、多维度分析和可视化展示,深入挖掘足球运动发展规律。主要发现:足球比赛数量呈持续增长趋势,欧洲和南美洲传统强队占据主导地位;友谊赛占比最高(17,196场),世界杯预选赛次之(7,900场);主场优势显著,主场胜率达57.49%,远超客场胜率19.63%;赛事平均进球数稳定在2.6-2.8球/场。研究为理解国际足球发展格局和比赛规律提供了数据支撑。
目录
第一章 数据说明
1.1 原始数据集
本项目使用两个原始数据集:
📊 数据集1:all_matches.csv
- 文件路径 :
input/all_matches.csv - 数据规模:51,234 行 × 8 列
- 时间跨度:1872年 - 2025年
- 数据内容:国际男子足球比赛结果记录
- 数据来源 :Kaggle - All International Football Results
📊 数据集2:countries_names.csv
- 文件路径 :
input/countries_names.csv - 数据规模:289 行 × 3 列
- 数据内容:国家/地区名称标准化映射表
1.2 核心字段说明
all_matches.csv 核心字段
| 字段名 | 数据类型 | 说明 |
|---|---|---|
date |
Date | 比赛日期(若日期未知,默认为12/31;若已知月份则为当月最后一天) |
home_team |
String | 主场球队名称 |
away_team |
String | 客场球队名称 |
home_score |
Integer | 全场主队得分(含加时赛,不含点球大战) |
away_score |
Integer | 全场客队得分(含加时赛,不含点球大战) |
tournament |
String | 比赛名称/赛事类型 |
country |
String | 比赛举办国家名称 |
neutral |
Boolean | 是否在中立场地进行(False表示在主队或客队国家比赛) |
countries_names.csv 核心字段
| 字段名 | 数据类型 | 说明 |
|---|---|---|
original_name |
String | 数据集中使用的国家名称(原始名称) |
current_name |
String | 现在使用的标准名称 |
color_code |
String | 国旗颜色代码(十六进制) |
1.3 衍生字段说明
在数据清洗过程中,程序会自动生成以下衍生字段:
| 衍生字段 | 数据类型 | 计算方式 | 说明 |
|---|---|---|---|
year |
Integer | date.dt.year |
比赛年份(从日期中提取) |
month |
Integer | date.dt.month |
比赛月份(从日期中提取,1-12) |
total_goals |
Integer | home_score + away_score |
比赛总进球数 |
result |
String | 条件判断 | 比赛结果(主队胜/客队胜/平局) |
1.4 数据特点
- 完整性:数据集仅包含国家队(至少属于国际足联的球队)
- 时间跨度:涵盖153年足球历史(1872-2025)
- 赛事类型:包含友谊赛、世界杯、欧洲杯、亚洲杯等各类国际赛事
- 地理覆盖:全球200+个国家/地区的比赛记录
第二章 环境配置与库导入
2.1 导入必要的库
python
# pandas: 用于数据处理和分析的Python库,提供DataFrame数据结构
import pandas as pd
# numpy: 用于科学计算的Python库,提供多维数组对象和各种数学函数
import numpy as np
# matplotlib.pyplot: 用于绘制静态、动态和交互式可视化的Python库
import matplotlib.pyplot as plt
# seaborn: 基于matplotlib的数据可视化库,提供更美观的统计图表
import seaborn as sns
# pyecharts: 用于生成ECharts图表的Python库,可创建交互式可视化
from pyecharts import options as opts
from pyecharts.charts import Bar, Line, Pie, Map, HeatMap
from pyecharts.globals import ThemeType
# warnings: Python内置警告模块,用于控制警告信息的显示
import warnings
# os: Python内置操作系统接口模块,用于文件和目录操作
import os
# 忽略所有警告信息,避免输出中出现烦人的警告提示
warnings.filterwarnings('ignore')
2.2 设置中文字体和输出目录
python
# 设置matplotlib中文字体为SimHei(黑体),解决中文显示乱码问题
plt.rcParams['font.sans-serif'] = ['SimHei']
# 设置坐标轴负号显示为正常负号,而不是方块
plt.rcParams['axes.unicode_minus'] = False
# 创建输出目录,用于保存生成的图表文件
output_dir = 'output'
# os.makedirs: 创建目录,exist_ok=True表示如果目录已存在则不报错
os.makedirs(output_dir, exist_ok=True)
第三章 数据读取与基本检查
3.1 读取原始数据
python
# pd.read_csv(): 读取CSV文件为DataFrame
# 'input/all_matches.csv': 原始比赛数据文件路径
# 包含51,234场国际足球比赛记录(1872-2025年)
matches_df = pd.read_csv('input/all_matches.csv')
# 读取国家名称映射表
# 包含289个国家的名称标准化映射
# original_name: 原始名称, current_name: 标准名称, color_code: 国旗颜色
countries_df = pd.read_csv('input/countries_names.csv')
print(f"比赛数据文件加载成功: {matches_df.shape}")
print(f"国家映射文件加载成功: {countries_df.shape}")
3.2 数据基本检查
python
# shape属性返回DataFrame的维度(行数,列数)
print("原始数据形状:", matches_df.shape)
# columns属性返回DataFrame的所有列名
print("\n数据列名:")
print(matches_df.columns.tolist())
# head(): 显示前5行数据,用于快速查看数据结构
print("\n前5行数据:")
print(matches_df.head())
# isnull(): 检测缺失值,返回布尔型DataFrame
# sum(): 统计每列的缺失值数量(True=1, False=0)
print("\n缺失值统计:")
print(matches_df.isnull().sum())
# info(): 显示DataFrame的详细信息,包括数据类型和非空值数量
print("\n数据类型信息:")
print(matches_df.info())
数据结构说明:
date: 比赛日期home_team: 主队名称away_team: 客队名称home_score: 主队得分away_score: 客队得分tournament: 赛事类型country: 比赛举办国家neutral: 是否中立场地
第四章 数据清洗
4.1 处理日期字段
python
# pd.to_datetime(): 将字符串或其他格式转换为datetime对象
# errors='coerce': 如果转换失败,设置为NaT(Not a Time)而不是报错
matches_df['date'] = pd.to_datetime(matches_df['date'], errors='coerce')
# 检查转换后的日期字段
print(f"日期转换完成,日期范围: {matches_df['date'].min()} 至 {matches_df['date'].max()}")
4.2 球队名称标准化
python
# dict(): 创建字典,用于映射原始名称到标准名称
# zip(): 将两个列表配对,形成键值对
country_map = dict(zip(countries_df['original_name'], countries_df['current_name']))
# replace(): 使用映射字典替换DataFrame中的值
matches_df['home_team'] = matches_df['home_team'].replace(country_map)
matches_df['away_team'] = matches_df['away_team'].replace(country_map)
print(f"球队名称标准化完成,共映射 {len(country_map)} 个国家/地区")
4.3 异常值处理
python
# 得分异常值过滤(合理得分范围0-30)
# & 是逻辑与运算符,确保两个条件同时满足
matches_df = matches_df[(matches_df['home_score'] >= 0) & (matches_df['home_score'] <= 30)]
matches_df = matches_df[(matches_df['away_score'] >= 0) & (matches_df['away_score'] <= 30)]
print(f"异常值过滤完成,剩余 {len(matches_df)} 条记录")
4.4 新增衍生字段
python
# dt.year: 从datetime对象提取年份(如2023)
matches_df['year'] = matches_df['date'].dt.year
# dt.month: 从datetime对象提取月份(1-12)
matches_df['month'] = matches_df['date'].dt.month
# 计算总进球数 = 主队得分 + 客队得分
matches_df['total_goals'] = matches_df['home_score'] + matches_df['away_score']
# 判断比赛结果
# np.where(): 条件函数,类似Excel的IF函数,支持嵌套
matches_df['result'] = np.where(
matches_df['home_score'] > matches_df['away_score'],
'主队胜',
np.where(
matches_df['home_score'] < matches_df['away_score'],
'客队胜',
'平局'
)
)
print("新增字段: year, month, total_goals, result")
4.5 数据去重
python
# drop_duplicates(): 删除重复行
# subset: 指定用于判断重复的列列表
original_count = len(matches_df)
matches_df = matches_df.drop_duplicates(subset=['date', 'home_team', 'away_team', 'tournament'])
removed_count = original_count - len(matches_df)
print(f"去重完成,去除 {removed_count} 条重复记录")
第五章 多维度数据分析
5.1 时间维度分析
5.1.1 年度比赛数量趋势
python
# groupby('year'): 按年份分组
# size(): 计算每组的行数(比赛数量)
# reset_index(): 将分组结果转换为DataFrame,并重置索引
# name='match_count': 为统计结果列命名
yearly_matches = matches_df.groupby('year').size().reset_index(name='match_count')
# 年度总进球数统计
yearly_goals = matches_df.groupby('year')['total_goals'].sum().reset_index(name='total_goals')
# 年度平均进球数统计
yearly_avg_goals = matches_df.groupby('year')['total_goals'].mean().reset_index(name='avg_goals')
5.1.2 月度比赛分布
python
# 按月份分组统计比赛数量
monthly_matches = matches_df.groupby('month').size().reset_index(name='match_count')
# 定义月份名称列表(中文)
month_names = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
# zip(range(1,13), month_names): 将月份数字和名称配对
# dict(): 转换为字典,如{1: '1月', 2: '2月', ...}
# map(): 将month列的值映射为对应的中文月份名称
monthly_matches['month_name'] = monthly_matches['month'].map(dict(zip(range(1,13), month_names)))
5.2 球队表现分析
5.2.1 球队胜场统计
python
# 筛选主队获胜的比赛,按主队分组统计胜场数
home_wins = matches_df[matches_df['result'] == '主队胜'].groupby('home_team').size().reset_index(name='home_wins')
# 筛选客队获胜的比赛,按客队分组统计胜场数
away_wins = matches_df[matches_df['result'] == '客队胜'].groupby('away_team').size().reset_index(name='away_wins')
# pd.merge(): 合并两个DataFrame
# left_on='home_team': 左表的连接键(主队名)
# right_on='away_team': 右表的连接键(客队名)
# how='outer': 外连接,保留所有球队
team_wins = pd.merge(home_wins, away_wins, left_on='home_team', right_on='away_team', how='outer')
# fillna(): 用指定值填充NaN(缺失值)
team_wins['team'] = team_wins['home_team'].fillna(team_wins['away_team'])
team_wins['total_wins'] = team_wins['home_wins'].fillna(0) + team_wins['away_wins'].fillna(0)
# 选择需要的列,按胜场数降序排列,取前20名
team_wins = team_wins[['team', 'total_wins']].sort_values('total_wins', ascending=False).head(20)
5.2.2 进球能力分析
python
# 按主队分组,统计主场总进球数
home_goals = matches_df.groupby('home_team')['home_score'].sum().reset_index(name='home_goals')
# 按客队分组,统计客场总进球数
away_goals = matches_df.groupby('away_team')['away_score'].sum().reset_index(name='away_goals')
# 合并主客场进球数据
team_goals = pd.merge(home_goals, away_goals, left_on='home_team', right_on='away_team', how='outer')
team_goals['team'] = team_goals['home_team'].fillna(team_goals['away_team'])
team_goals['total_goals'] = team_goals['home_goals'].fillna(0) + team_goals['away_goals'].fillna(0)
team_goals = team_goals[['team', 'total_goals']].sort_values('total_goals', ascending=False).head(20)
5.3 赛事类型分析
5.3.1 赛事数量统计
python
# value_counts(): 统计每个唯一值的出现次数,自动按降序排列
# reset_index(): 转换为DataFrame
# head(10): 取前10名
tournament_counts = matches_df['tournament'].value_counts().reset_index().head(10)
tournament_counts.columns = ['tournament', 'match_count']
# agg(): 聚合函数,可同时计算多个统计量
# 对total_goals列同时计算sum(总和)、mean(平均值)、count(计数)
tournament_goals = matches_df.groupby('tournament').agg({
'total_goals': ['sum', 'mean', 'count']
}).round(2)
tournament_goals.columns = ['total_goals', 'avg_goals_per_match', 'match_count']
tournament_goals = tournament_goals.sort_values('match_count', ascending=False).head(10)
5.3.2 赛事进球效率
python
# agg(): 聚合函数,可同时计算多个统计量
# 对total_goals列同时计算sum(总和)、mean(平均值)、count(计数)
tournament_goals = matches_df.groupby('tournament').agg({
'total_goals': ['sum', 'mean', 'count']
}).round(2)
tournament_goals.columns = ['total_goals', 'avg_goals_per_match', 'match_count']
tournament_goals = tournament_goals.sort_values('match_count', ascending=False).head(10)
5.4 主场优势分析
5.4.1 主客场胜率对比
python
# len(): 返回DataFrame的行数(总比赛数)
total_matches = len(matches_df)
# 计算主队胜率(主队获胜的比赛数 / 总比赛数 * 100)
home_win_rate = len(matches_df[matches_df['result'] == '主队胜']) / total_matches * 100
# 计算客队胜率(客队获胜的比赛数 / 总比赛数 * 100)
away_win_rate = len(matches_df[matches_df['result'] == '客队胜']) / total_matches * 100
# 计算平局率(平局的比赛数 / 总比赛数 * 100)
draw_rate = len(matches_df[matches_df['result'] == '平局']) / total_matches * 100
5.4.2 中立场地对比分析
python
# 中立场地vs非中立场地对比
# groupby('neutral'): 按是否中立场地分组(True/False)
# value_counts(normalize=True): 计算比例(归一化,总和为1)
# unstack(): 将多级索引转换为列,形成透视表
neutral_stats = matches_df.groupby('neutral')['result'].value_counts(normalize=True).unstack() * 100
neutral_stats.columns = ['客队胜率(%)', '平局率(%)', '主队胜率(%)']
分析结果:
- 主场胜率: 57.49%
- 客场胜率: 19.63%
- 平局率: 22.88%
第六章 数据可视化
6.1 图表1:年度比赛数量趋势图(Line)
Matplotlib实现:
python
# figure(): 创建新图表
# figsize=(14, 7): 设置图表大小为14英寸宽,7英寸高
plt.figure(figsize=(14, 7))
# plot(): 绘制折线图
# color='#1f77b4': 设置线条颜色为蓝色
# linewidth=2: 设置线宽为2
# marker='o': 设置数据点标记为圆形
# markersize=3: 设置标记大小为3
plt.plot(yearly_matches['year'], yearly_matches['match_count'],
color='#1f77b4', linewidth=2, marker='o', markersize=3)
# title(): 设置图表标题
plt.title('1872-2025年国际足球比赛数量趋势', fontsize=16, fontweight='bold')
# xlabel(), ylabel(): 设置坐标轴标签
plt.xlabel('年份', fontsize=12)
plt.ylabel('比赛数量', fontsize=12)
# grid(): 显示网格线
plt.grid(True, alpha=0.3)
# xticks(): 设置x轴刻度
plt.xticks(range(1870, 2030, 10), rotation=45)
# tight_layout(): 自动调整子图参数,使图表紧凑
plt.tight_layout()
# savefig(): 保存图表
plt.savefig(f'{output_dir}/yearly_match_trend.png', dpi=300, bbox_inches='tight')
# close(): 关闭图表,释放内存
plt.close()

Pyecharts实现:
python
# Line(): 创建折线图对象
# init_opts: 初始化配置
# ThemeType.LIGHT: 使用浅色主题
# width="1400px": 图表宽度1400像素
# height="700px": 图表高度700像素
line = (
Line(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1400px", height="700px"))
# add_xaxis(): 添加x轴数据
.add_xaxis(yearly_matches['year'].astype(str).tolist())
# add_yaxis(): 添加y轴数据
# markpoint_opts: 标记点配置,显示最大值
.add_yaxis("比赛数量", yearly_matches['match_count'].tolist(),
markpoint_opts=opts.MarkPointOpts(data=[opts.MarkPointItem(type_="max")]))
# set_global_opts(): 设置全局配置
.set_global_opts(
title_opts=opts.TitleOpts(
title="1872-2025年国际足球比赛数量趋势",
subtitle="数据来源:Kaggle国际足球比赛数据集"
),
xaxis_opts=opts.AxisOpts(
axislabel_opts=opts.LabelOpts(rotate=-45)
),
yaxis_opts=opts.AxisOpts(name="比赛数量"),
datazoom_opts=[opts.DataZoomOpts(range_start=80, range_end=100)]
)
)
# render(): 渲染为HTML文件
line.render(f'{output_dir}/yearly_match_trend_pyecharts.html')

6.2 图表2:月度比赛分布柱状图(Bar)
Matplotlib实现:
python
plt.figure(figsize=(12, 6))
# barplot(): 绘制柱状图
# x='month_name': x轴为月份名称
# y='match_count': y轴为比赛数量
# palette='viridis': 使用viridis配色方案
sns.barplot(x='month_name', y='match_count', data=monthly_matches, palette='viridis')
plt.title('国际足球比赛月度分布', fontsize=16, fontweight='bold')
plt.xlabel('月份', fontsize=12)
plt.ylabel('比赛数量', fontsize=12)
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig(f'{output_dir}/monthly_match_distribution.png', dpi=300, bbox_inches='tight')
plt.close()

Pyecharts实现:
python
bar = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1200px", height="600px"))
.add_xaxis(monthly_matches['month_name'].tolist())
.add_yaxis("比赛数量", monthly_matches['match_count'].tolist(), color='#ff7f0e')
.set_global_opts(
title_opts=opts.TitleOpts(
title="国际足球比赛月度分布",
subtitle="数据来源:Kaggle国际足球比赛数据集"
),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=0)),
yaxis_opts=opts.AxisOpts(name="比赛数量")
)
)
bar.render(f'{output_dir}/monthly_match_distribution_pyecharts.html')

6.3 图表3:球队胜场Top20饼图(Pie)
Matplotlib实现:
python
plt.figure(figsize=(12, 12))
# colors: 使用tab20颜色映射生成20种不同颜色
# plt.cm.tab20: matplotlib的tab20颜色映射
# np.linspace(0, 1, 20): 生成0到1之间均匀分布的20个数
colors = plt.cm.tab20(np.linspace(0, 1, 20))
# pie(): 绘制饼图
# team_wins['total_wins']: 饼图各扇区的大小(胜场数)
# labels=team_wins['team']: 各扇区的标签(球队名)
# autopct='%1.1f%%': 显示百分比,保留1位小数
# startangle=90: 起始角度为90度
# shadow=True: 显示阴影效果
# explode: 突出显示前5个扇区
plt.pie(team_wins['total_wins'], labels=team_wins['team'], autopct='%1.1f%%',
startangle=90, shadow=True, explode=[0.05]*5 + [0]*15, colors=colors)
plt.title('国际足球球队胜场Top20分布', fontsize=16, fontweight='bold')
plt.axis('equal')
plt.tight_layout()
plt.savefig(f'{output_dir}/team_wins_pie.png', dpi=300, bbox_inches='tight')
plt.close()

Pyecharts实现:
python
# zip(): 将球队名和胜场数配对
pie_data = list(zip(team_wins['team'].tolist(), team_wins['total_wins'].tolist()))
pie = (
Pie(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1200px", height="1200px"))
.add("", pie_data,
radius=["30%", "75%"], # 内外半径,形成环形饼图
center=["50%", "50%"], # 中心位置
rosetype="radius") # 玫瑰图类型
.set_global_opts(
title_opts=opts.TitleOpts(
title="国际足球球队胜场Top20分布",
subtitle="数据来源:Kaggle国际足球比赛数据集"
),
legend_opts=opts.LegendOpts(
orient="vertical",
pos_top="15%",
pos_left="2%"
)
)
.set_series_opts(
label_opts=opts.LabelOpts(formatter="{b}: {c} ({d}%)")
)
)
pie.render(f'{output_dir}/team_wins_pie_pyecharts.html')

6.4 图表4:球队进球能力对比图(Horizontal Bar)
Matplotlib实现:
python
plt.figure(figsize=(12, 10))
# barplot(): 绘制柱状图
# x='total_goals': x轴为总进球数(水平柱状图)
# y='team': y轴为球队名
# palette='coolwarm': 使用coolwarm配色方案
sns.barplot(x='total_goals', y='team', data=team_goals, palette='coolwarm')
plt.title('国际足球球队总进球数Top20', fontsize=16, fontweight='bold')
plt.xlabel('总进球数', fontsize=12)
plt.ylabel('球队', fontsize=12)
plt.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
plt.savefig(f'{output_dir}/team_goals_bar.png', dpi=300, bbox_inches='tight')
plt.close()

Pyecharts实现:
python
hbar = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1200px", height="1000px"))
.add_xaxis(team_goals['team'].tolist())
.add_yaxis("总进球数", team_goals['total_goals'].tolist(), color='#2ca02c')
.reversal_axis() # 反转坐标轴,形成水平柱状图
.set_global_opts(
title_opts=opts.TitleOpts(
title="国际足球球队总进球数Top20",
subtitle="数据来源:Kaggle国际足球比赛数据集"
),
xaxis_opts=opts.AxisOpts(name="总进球数"),
yaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=0))
)
.set_series_opts(label_opts=opts.LabelOpts(position="right"))
)
hbar.render(f'{output_dir}/team_goals_hbar_pyecharts.html')

6.5 图表5:赛事类型分布直方图(Histogram)
Matplotlib实现:
python
plt.figure(figsize=(14, 7))
# hist(): 绘制直方图
# bins=20: 分成20个区间
# color='#d62728': 红色
# alpha=0.7: 透明度0.7
# edgecolor='black': 边框颜色黑色
plt.hist(matches_df['tournament'], bins=20, color='#d62728', alpha=0.7, edgecolor='black')
plt.title('国际足球赛事类型分布', fontsize=16, fontweight='bold')
plt.xlabel('赛事类型', fontsize=12)
plt.ylabel('赛事数量', fontsize=12)
plt.xticks(rotation=90)
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig(f'{output_dir}/tournament_histogram.png', dpi=300, bbox_inches='tight')
plt.close()

Seaborn实现:
python
plt.figure(figsize=(14, 7))
# histplot(): Seaborn的直方图函数
# weights='match_count': 使用match_count作为权重
sns.histplot(data=tournament_counts, x='tournament', weights='match_count',
bins=10, color='#9467bd')
plt.title('国际足球主要赛事数量分布(Top10)', fontsize=16, fontweight='bold')
plt.xlabel('赛事类型', fontsize=12)
plt.ylabel('比赛数量', fontsize=12)
plt.xticks(rotation=45, ha='right')
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig(f'{output_dir}/top10_tournament_histogram.png', dpi=300, bbox_inches='tight')
plt.close()

6.6 图表6:主客场胜率对比图(Grouped Bar)
Matplotlib实现:
python
plt.figure(figsize=(10, 6))
# 定义比赛结果和对应胜率
results = ['主队胜', '客队胜', '平局']
rates = [home_win_rate, away_win_rate, draw_rate]
colors = ['#ff9999', '#66b3ff', '#99ff99']
# np.arange(): 创建等差数组,用于x轴位置
x = np.arange(len(results))
# width: 柱子的宽度
width = 0.6
# bar(): 绘制柱状图
plt.bar(x, rates, width, color=colors, alpha=0.8, edgecolor='black')
plt.title('c', fontsize=16, fontweight='bold')
plt.xlabel('比赛结果', fontsize=12)
plt.ylabel('胜率(%)', fontsize=12)
plt.xticks(x, results)
plt.grid(True, alpha=0.3, axis='y')
# 添加数值标签
for i, v in enumerate(rates):
plt.text(i, v + 0.5, f'{v:.2f}%', ha='center', va='bottom', fontsize=11)
plt.tight_layout()
plt.savefig(f'{output_dir}/home_away_win_rate.png', dpi=300, bbox_inches='tight')
plt.close()

Pyecharts实现:
python
group_bar = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1000px", height="600px"))
.add_xaxis(results)
.add_yaxis("胜率(%)", rates, color=['#ff9999', '#66b3ff', '#99ff99'])
.set_global_opts(
title_opts=opts.TitleOpts(
title="国际足球比赛主客场胜率对比",
subtitle="数据来源:Kaggle国际足球比赛数据集"
),
yaxis_opts=opts.AxisOpts(name="胜率(%)", max_=50)
)
.set_series_opts(
label_opts=opts.LabelOpts(formatter="{c}%", position="top")
)
)
group_bar.render(f'{output_dir}/home_away_win_rate_pyecharts.html')

6.7 图表7:年度平均进球数趋势图(Dual Axis Line)
Matplotlib实现:
python
fig, ax1 = plt.subplots(figsize=(14, 7))
# 主坐标轴:比赛数量
ax1.plot(yearly_matches['year'], yearly_matches['match_count'],
'b-', linewidth=2, label='比赛数量')
ax1.set_xlabel('年份', fontsize=12)
ax1.set_ylabel('比赛数量', fontsize=12, color='b')
ax1.tick_params(axis='y', labelcolor='b')
ax1.grid(True, alpha=0.3)
# 次坐标轴:平均进球数
# twinx(): 创建共享x轴的第二个y轴
ax2 = ax1.twinx()
ax2.plot(yearly_avg_goals['year'], yearly_avg_goals['avg_goals'],
'r-', linewidth=2, label='平均进球数')
ax2.set_ylabel('平均进球数/场', fontsize=12, color='r')
ax2.tick_params(axis='y', labelcolor='r')
# 添加图例
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
plt.title('1872-2025年比赛数量与平均进球数趋势', fontsize=16, fontweight='bold')
plt.xticks(range(1870, 2030, 10), rotation=45)
plt.tight_layout()
plt.savefig(f'{output_dir}/yearly_goals_dual_axis.png', dpi=300, bbox_inches='tight')
plt.close()

6.8 图表8:国家比赛数量热力图(HeatMap)
python
# 统计各国举办比赛数量(Top30)
country_match_count = matches_df['country'].value_counts().reset_index().head(30)
country_match_count.columns = ['country', 'match_count']
# 定义大洲映射字典
continents = {
'England': 'Europe', 'Scotland': 'Europe', 'Germany': 'Europe',
'France': 'Europe', 'Italy': 'Europe', 'Spain': 'Europe',
'Netherlands': 'Europe', 'Belgium': 'Europe', 'Sweden': 'Europe',
'Norway': 'Europe', 'Portugal': 'Europe', 'Denmark': 'Europe',
'Switzerland': 'Europe', 'Austria': 'Europe', 'Russia': 'Europe',
'Poland': 'Europe', 'Czech Republic': 'Europe', 'Hungary': 'Europe',
'Turkey': 'Europe', 'Greece': 'Europe',
'Brazil': 'South America', 'Argentina': 'South America',
'Uruguay': 'South America', 'Chile': 'South America',
'United States': 'North America', 'Mexico': 'North America',
'Canada': 'North America',
'China': 'Asia', 'Japan': 'Asia', 'South Korea': 'Asia',
'Australia': 'Oceania', 'New Zealand': 'Oceania',
'South Africa': 'Africa', 'Egypt': 'Africa', 'Nigeria': 'Africa'
}
# map(): 根据国家映射到大洲
country_match_count['continent'] = country_match_count['country'].map(continents).fillna('Other')
# 按大洲和国家分组统计
heatmap_data = country_match_count.groupby(['continent', 'country'])['match_count'].sum().reset_index()
heatmap_data = heatmap_data.sort_values('match_count', ascending=False)
# 转换为热力图格式
heatmap_list = []
for _, row in heatmap_data.iterrows():
heatmap_list.append([row['continent'], row['country'], int(row['match_count'])])
# 获取唯一的大洲和国家列表
x_axis = list(set([x[0] for x in heatmap_list]))
y_axis = list(set([x[1] for x in heatmap_list]))
# Pyecharts热力图实现
heatmap = (
HeatMap(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1200px", height="800px"))
.add_xaxis(x_axis)
.add_yaxis("比赛数量", y_axis, heatmap_list)
.set_global_opts(
title_opts=opts.TitleOpts(
title="各国举办国际足球比赛数量热力图(Top30)",
subtitle="数据来源:Kaggle国际足球比赛数据集"
),
visualmap_opts=opts.VisualMapOpts(
min_=0,
max_=heatmap_data['match_count'].max()
),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-45))
)
)
heatmap.render(f'{output_dir}/country_match_heatmap.html')

6.9 图表9:比赛结果分布散点图(Scatter)
python
# 筛选近50年数据避免图表过于密集
recent_matches = matches_df[matches_df['year'] >= 1975]
plt.figure(figsize=(12, 10))
# scatterplot(): 绘制散点图
# x='home_score': x轴为主队得分
# y='away_score': y轴为客队得分
# alpha=0.6: 透明度0.6,可以看到重叠的点
# s=10: 点的大小为10
# hue='neutral': 根据是否中立场地着色
sns.scatterplot(x='home_score', y='away_score', data=recent_matches,
alpha=0.6, s=10, hue='neutral', palette='coolwarm', legend='full')
plt.title('近50年国际足球比赛主客场得分分布', fontsize=16, fontweight='bold')
plt.xlabel('主队得分', fontsize=12)
plt.ylabel('客队得分', fontsize=12)
plt.grid(True, alpha=0.3)
plt.legend(title='中立场地', loc='upper right')
plt.tight_layout()
plt.savefig(f'{output_dir}/match_score_scatter.png', dpi=300, bbox_inches='tight')
plt.close()

6.10 图表10:赛事进球效率对比图(Box Plot)
python
# 筛选Top5赛事类型
top5_tournaments = tournament_counts['tournament'].head(5).tolist()
# isin(): 判断是否在列表中
tournament_goals_data = matches_df[matches_df['tournament'].isin(top5_tournaments)]
plt.figure(figsize=(14, 7))
# boxplot(): 绘制箱线图
# palette='Set2': 使用Set2配色方案
sns.boxplot(x='tournament', y='total_goals', data=tournament_goals_data, palette='Set2')
plt.title('Top5赛事进球数分布对比', fontsize=16, fontweight='bold')
plt.xlabel('赛事类型', fontsize=12)
plt.ylabel('总进球数/场', fontsize=12)
plt.xticks(rotation=45, ha='right')
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig(f'{output_dir}/tournament_goals_boxplot.png', dpi=300, bbox_inches='tight')
plt.close()

第七章 运行说明
7.1 项目结构
足球比赛/
├── input/ # 输入数据目录
│ ├── all_matches.csv # 比赛数据(51,234条记录)
│ └── countries_names.csv # 国家名称映射表
├── output/ # 输出图表目录
│ ├── yearly_match_trend.png
│ ├── yearly_match_trend_pyecharts.html
│ └── ... # 其他图表文件
├── app.py # 主程序文件
├── README.md # 项目说明文档
└── .venv/ # Python虚拟环境
7.2 环境要求
- Python 3.12+
- pandas
- numpy
- matplotlib
- seaborn
- pyecharts
7.3 运行方式
使用虚拟环境运行:
bash
# Windows
.venv\Scripts\python app.py
# macOS/Linux
source .venv/bin/python app.py
7.4 输出结果
程序运行后会生成以下文件:
| 文件名 | 类型 | 说明 |
|---|---|---|
| yearly_match_trend.png | PNG | 年度比赛数量趋势图(Matplotlib) |
| yearly_match_trend_pyecharts.html | HTML | 年度比赛数量趋势图(Pyecharts) |
| monthly_match_distribution.png | PNG | 月度比赛分布柱状图(Seaborn) |
| monthly_match_distribution_pyecharts.html | HTML | 月度比赛分布柱状图(Pyecharts) |
| team_wins_pie.png | PNG | 球队胜场Top20饼图(Matplotlib) |
| team_wins_pie_pyecharts.html | HTML | 球队胜场Top20饼图(Pyecharts) |
| team_goals_bar.png | PNG | 球队进球能力对比图(Seaborn) |
| team_goals_hbar_pyecharts.html | HTML | 球队进球能力对比图(Pyecharts) |
| tournament_histogram.png | PNG | 赛事类型分布直方图(Matplotlib) |
| top10_tournament_histogram.png | PNG | Top10赛事分布(Seaborn) |
| home_away_win_rate.png | PNG | 主客场胜率对比图(Matplotlib) |
| home_away_win_rate_pyecharts.html | HTML | 主客场胜率对比图(Pyecharts) |
| yearly_goals_dual_axis.png | PNG | 年度平均进球数趋势图(双轴) |
| country_match_heatmap.html | HTML | 国家比赛数量热力图(Pyecharts) |
| match_score_scatter.png | PNG | 比赛结果分布散点图(Seaborn) |
| tournament_goals_boxplot.png | PNG | 赛事进球效率对比图(箱线图) |
第八章 分析结论
8.1 足球运动发展趋势
- 国际足球比赛数量持续增长,反映足球运动的全球化
- 20世纪后期以来增长加速,与电视转播和商业化发展相关
8.2 球队竞争力格局
- 传统足球强国(英格兰、德国、巴西、阿根廷、瑞典等)保持领先
- 胜场数和进球数高度相关,进攻能力决定竞争力
- 欧洲和南美洲球队占据主导地位
8.3 赛事体系特征
- 友谊赛占比最高(17,196场),是国家队交流的主要形式
- 世界杯预选赛(7,900场)和欧洲杯预选赛(2,824场)数量庞大
- 正式赛事的平均进球数相对稳定(2.6-2.8球/场)
8.4 主场优势验证
- 主场胜率(57.49%)显著高于客场胜率(19.63%)
- 主场优势可能来自:球迷支持、场地熟悉、减少旅途疲劳
- 中立场地比赛结果更加均衡
8.5 比赛得分规律
- 常见比分集中在低比分区域(0-3球)
- 大比分比赛较为罕见
- 中立场地比赛的得分分布更加分散
技术亮点
数据处理能力
- 使用Pandas进行大规模数据处理(50,000+行)
- 数据清洗流程完整(缺失值、异常值、重复值处理)
- 高效的数据分组和聚合操作
可视化技术
- 同时使用Matplotlib、Seaborn和Pyecharts三大可视化库
- 静态图表和交互式图表相结合
- 中文字体正确显示处理
代码结构
- 模块化设计,功能分区明确
- 详细的代码注释,便于理解和维护
- 逐行注释解释每个参数的作用
参考资料
- 数据来源:Kaggle国际足球比赛数据集
- 时间跨度:1872年-2025年
- 数据规模:51,234场比赛记录
- 分析工具:Python + Pandas + Matplotlib + Seaborn + Pyecharts