Python 百年奥运数据分析实战|Pandas 清洗 + Matplotlib/Pyecharts 可视化 + 拖拽大屏完整项目(附源码)
2026/6/26 3:28:42 网站建设 项目流程

一、前言

大家好,今天分享一套大一课内数据分析完整实战项目 ——1896-2016 百年奥运数据探索与体育强国分析,基于 Kaggle 经典奥运数据集,完整覆盖数据读取、多表关联、缺失值清洗、特征工程、多维探索分析、中国专题、交互式可视化大屏全流程,适合数据分析入门练手、课程大作业、期末报告。

项目信息

  • 数据集:Kaggle 120 年奥运历史数据(athlete_events.csv + noc_regions.csv)
  • 数据规模:27 万 + 运动员参赛记录
  • 技术栈:Pandas、NumPy、Matplotlib、Pyecharts
  • 项目周期:课内 4h + 课外自主实践
  • 学习目标:掌握多表合并、缺失值处理、BMI 特征衍生、静态 + 交互式可视化、拖拽式数据大屏开发

二、数据集介绍

1. 两张核心 CSV

  1. athlete_events.csv 运动员明细表| 字段 | 含义 | | ---- | ---- | | ID | 运动员唯一编号 | | Name、Sex、Age、Height、Weight | 姓名、性别、年龄、身高 (cm)、体重 (kg) | | NOC | 国家奥委会三位代码 | | Games、Year、Season、City | 赛事全称、年份、夏 / 冬奥、举办城市 | | Sport、Event | 大项、细分小项 | | Medal | 奖牌:Gold/Silver/Bronze/ 空(无奖牌) |

  2. noc_regions.csv 国家代码映射表

  • NOC:奥委会编码
  • region:国家 / 地区全称
  • notes:备注(历史政权、特殊代表团说明)

2. 数据痛点

  1. 大量身高、体重、年龄缺失;
  2. Medal 字段空值代表未获奖,不能直接删除;
  3. 历史政权代码冗余:URS (苏联)、EUN (独联体)、RUS (俄罗斯);GER/FRG/GDR (两德);
  4. 特殊 NOC:ROT 难民代表团、UNK 未知地区、TUV 小众岛国,无法匹配国家名称。

三、完整项目代码分步实现

步骤 1:环境导入与全局配置

python

运行

import pandas as pd import numpy as np import matplotlib.pyplot as plt import warnings # 全局设置 warnings.filterwarnings('ignore') plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文显示 plt.rcParams['axes.unicode_minus'] = False # 负号正常显示 from pyecharts import options as opts from pyecharts.charts import Bar, Line, Pie, Radar, Boxplot, Page

步骤 2:数据加载 + 左连接多表融合(阶段一)

使用左连接 Left Join保证所有运动员记录不丢失,关联国家名称

python

运行

# 读取数据 df_athletes = pd.read_csv("athlete_events.csv") df_regions = pd.read_csv("noc_regions.csv") # 左连接合并两张表 df = pd.merge(df_athletes, df_regions, on="NOC", how="left") # 查看未匹配到国家的特殊NOC(难民、未知地区) missing_NOC = df[df["region"].isna()]["NOC"].unique() print("无匹配国家的NOC代码:", missing_NOC) # 输出:['SGP', 'ROT', 'UNK', 'TUV'] # 数据基础统计 print(df.describe())

步骤 3:数据清洗 + 特征工程(阶段二核心)

3.1 缺失值填充策略
  1. region 空值填充为 Unknown(难民 / 未知代表团)
  2. Medal 空值统一标记No Medal,区分获奖 / 未获奖
  3. 身高、体重、年龄缺失:身高用中位数(不受极端身高干扰),年龄、体重用均值

python

运行

# 1. 国家缺失填充 df["region"] = df["region"].fillna("Unknown") # 2. 奖牌缺失填充 df["Medal"] = df["Medal"].fillna("No Medal") # 3. 身体指标缺失填充 df["Height"] = df["Height"].fillna(df["Height"].median()) df["Weight"] = df["Weight"].fillna(df["Weight"].mean()) df["Age"] = df["Age"].fillna(df["Age"].mean()) # 衍生特征:BMI指数 = 体重(kg) / 身高(m)² df["BMI"] = df["Weight"] / ((df["Height"] / 100) ** 2) df["BMI"] = df["BMI"].round(2) # 查看历史政权统一映射示例 print("俄罗斯相关NOC:", df[df["region"]=="Russia"]["NOC"].unique()) # ['RUS' 'URS' 'EUN'] 苏联、独联体、俄罗斯统一归类Russia print("德国相关NOC:", df[df["region"]=="Germany"]["NOC"].unique()) # ['GER' 'FRG' 'GDR' 'SAA'] 东德、西德、统一德国合并统计
策略思考题解答
  1. 统一合并 URS/EUN/RUS 统计总奖牌优势:无需手动拼接多段政权数据,直接按 region 分组即可得到俄罗斯全历史奖牌;
  2. 单独分析俄罗斯联邦:增加过滤条件df[(df["region"]=="Russia") & (df["Year"] >= 1992)],苏联解体后年份单独提取。

步骤 4:全球宏观探索分析(阶段三)

4.1 运动员基础画像可视化

python

运行

# 1. 男女参赛比例饼图 plt.figure(figsize=(8,6)) df["Sex"].value_counts().plot.pie(autopct="%1.1f%%", explode=(0.1,0), shadow=True) plt.title("百年奥运男女运动员参赛占比") plt.show() # 2. 男女年龄箱线图 df.boxplot(column="Age", by="Sex", figsize=(8,5)) plt.title("男女运动员年龄分布") plt.suptitle("") plt.show() # 3. 男女BMI分布直方图 plt.figure(figsize=(14,6)) plt.hist(df[df["Sex"]=="M"]["BMI"], bins=30, alpha=0.5, label="男性") plt.hist(df[df["Sex"]=="F"]["BMI"], bins=30, alpha=0.5, label="女性") plt.xlabel("BMI指数") plt.legend() plt.title("男女运动员BMI分布对比") plt.show() # 4. 历年男女平均年龄折线图 plt.figure(figsize=(14,6)) df[df["Sex"]=="M"].groupby("Year")["Age"].mean().plot(marker="o", label="男") df[df["Sex"]=="F"].groupby("Year")["Age"].mean().plot(marker="*", label="女") plt.title("1896-2016男女运动员平均年龄变化") plt.xlabel("年份") plt.ylabel("平均年龄") plt.legend() plt.show()
4.2 全球奖牌格局对比

python

运行

# 筛选所有获奖记录 df_medal = df[df["Medal"] != "No Medal"] # 1. 全历史总奖牌Top20 top20_all = df_medal.groupby("region")["Medal"].count().sort_values(ascending=False).head(20) top20_all.plot(kind="barh", figsize=(12,7), color="#0099cc") plt.title("奥运百年总奖牌榜TOP20国家") plt.xlabel("奖牌总数") plt.show() # 2. 1994苏联解体后现代格局奖牌Top20 df_modern = df_medal[df_medal["Year"] >= 1994] top20_modern = df_modern.groupby("region")["Medal"].count().sort_values(ascending=False).head(20) top20_modern.plot(kind="barh", figsize=(12,7), color="#ff6666") plt.title("1994年后现代奥运奖牌榜TOP20") plt.xlabel("奖牌总数") plt.show() # 3. 仅获得≤3枚奖牌的长尾小国 less_medal = df_medal.groupby("region")["Medal"].count() less_medal = less_medal[less_medal <= 3] less_medal.plot(kind="pie", figsize=(9,9)) plt.title("仅获得少量奖牌的国家分布") plt.ylabel("") plt.show()

步骤 5:中国奥运崛起专题分析(阶段四)

python

运行

# 提取中国全部数据 df_cn = df[df["region"] == "China"] df_cn_medal = df_cn[df_cn["Medal"] != "No Medal"] # 1. 夏/冬奥奖牌历年走势 summer_cn = df_cn_medal[df_cn_medal["Season"]=="Summer"].groupby("Year")["Medal"].count() winter_cn = df_cn_medal[df_cn_medal["Season"]=="Winter"].groupby("Year")["Medal"].count() plt.figure(figsize=(12,6)) summer_cn.plot(marker="o", label="夏季奥运") winter_cn.plot(marker="*", label="冬季奥运") plt.title("中国历届夏/冬奥会奖牌走势") plt.legend() plt.show() # 2. 中国首金查询 first_gold_year = df_cn_medal[df_cn_medal["Medal"]=="Gold"]["Year"].min() first_gold = df_cn_medal[(df_cn_medal["Year"]==first_gold_year) & (df_cn_medal["Medal"]=="Gold")] print("中国首金年份:", first_gold_year) print(first_gold[["Name","Sport","Event"]].head(1)) # 3. 男女奖牌历年堆叠柱状图 gender_year = df_cn_medal.groupby(["Year","Sex"])["Medal"].count().unstack(fill_value=0) gender_year.plot(kind="bar", stacked=True, figsize=(13,6)) plt.title("中国历年男女运动员奖牌贡献") plt.xlabel("年份") plt.ylabel("奖牌数量") plt.show() # 4. 中国优势项目饼图 top_sport_cn = df_cn_medal["Sport"].value_counts().head(10) top_sport_cn.plot(kind="pie", autopct="%.2f%%", figsize=(10,10)) plt.title("中国获奖最多十大运动项目") plt.ylabel("") plt.show()

步骤 6:Pyecharts 拖拽式可视化大屏(阶段六完整代码)

支持暗黑主题、自由拖拽调整布局,最终固化为正式大屏 HTML

python

运行

# 统一全局暗黑主题配置 TEXT_COLOR = "#ffffff" BG_COLOR = "#0a0e27" def get_base_opts(title_name): return opts.InitOpts(bg_color=BG_COLOR, theme="dark", width="500px", height="350px") # 图表1:历史总奖牌TOP10横向柱状图 def bar_medal_rank(): top10 = df_medals["region"].value_counts().head(10).sort_values() c = ( Bar(init_opts=get_base_opts("历史总奖牌榜TOP10")) .add_xaxis(top10.index.tolist()) .add_yaxis("奖牌总数", top10.values.tolist(), color="#00d2ff") .reversal_axis() .set_global_opts( title_opts=opts.TitleOpts(title="百年奥运总奖牌榜TOP10", textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(color=TEXT_COLOR)), yaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(color=TEXT_COLOR)) ) ) return c # 图表2:中美俄德英五国奖牌历年折线 def line_super_power(): top5 = ["USA", "China", "Russia", "UK", "Germany"] years = sorted(df_medals["Year"].unique()) c = Line(init_opts=get_base_opts("五大体育强国历年奖牌走势")) color_list = ["#ff4500", "#ffd700", "#00ff7f", "#1e90ff", "#da70d6"] for country, color in zip(top5, color_list): cnt = df_medals[df_medals["region"]==country].groupby("Year")["Medal"].count() y_data = [cnt.get(y,0) for y in years] c.add_yaxis(country, y_data, is_smooth=True, linestyle_opts=opts.LineStyleOpts(color=color)) c.add_xaxis([str(i) for i in years]) c.set_global_opts( title_opts=opts.TitleOpts(title="五大强国历届奖牌趋势", textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR)), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(color=TEXT_COLOR)), yaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(color=TEXT_COLOR)) ) return c # 图表3:中国金牌优势项目雷达图 def radar_cn_gold(): cn_gold = df_medals[(df_medals["region"]=="China") & (df_medals["Medal"]=="Gold")] top6_sport = cn_gold["Sport"].value_counts().head(6) schema = [opts.RadarIndicatorItem(name=i, max_=int(top6_sport.max()*1.2)) for i in top6_sport.index] c = ( Radar(init_opts=get_base_opts("中国金牌优势领域")) .add_schema(schema=schema, splitarea_opts=opts.SplitAreaOpts(is_show=True)) .add("金牌数", [top6_sport.values.tolist()], color="#ffd700") .set_global_opts(title_opts=opts.TitleOpts(title="中国金牌优势项目雷达图", textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR))) ) return c # 图表4:百年女性参赛比例面积图 def area_female_ratio(): gender_cnt = df.groupby(["Year","Sex"])["ID"].count().unstack(fill_value=0) gender_cnt["Female_Ratio"] = (gender_cnt["F"] / (gender_cnt["M"] + gender_cnt["F"]) * 100).round(2) c = ( Line(init_opts=get_base_opts("女性运动员参赛占比演变")) .add_xaxis([str(i) for i in gender_cnt.index]) .add_yaxis("女性占比(%)", gender_cnt["Female_Ratio"].tolist(), areastyle_opts=opts.AreaStyleOpts(opacity=0.5, color="#ff69b4"), color="#ff69b4") .set_global_opts(title_opts=opts.TitleOpts(title="百年奥运女性参赛比例变化", textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR))) ) return c # 图表5:热门参赛项目TOP10柱状图 def bar_sport_top10(): sport_cnt = df["Sport"].value_counts().head(10).sort_values() c = ( Bar(init_opts=get_base_opts("参赛人次最多十大项目")) .add_xaxis(sport_cnt.index.tolist()) .add_yaxis("参赛人次", sport_cnt.values.tolist(), color="#7b68ee") .reversal_axis() .set_global_opts(title_opts=opts.TitleOpts(title="热门运动项目TOP10", textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR))) ) return c # 图表6:篮球/举重/体操BMI箱线图 def box_bmi_compare(): sport_list = ["Basketball", "Weightlifting", "Gymnastics"] df_bmi = df[df["Sport"].isin(sport_list)][["Sport","BMI"]] df_bmi = df_bmi[(df_bmi["BMI"]>10) & (df_bmi["BMI"]<60)] data_list = [df_bmi[df_bmi["Sport"]==s]["BMI"].tolist() for s in sport_list] c = ( Boxplot(init_opts=get_base_opts("三大项目运动员BMI分布")) .add_xaxis(sport_list) .add_yaxis("BMI指数", Boxplot.prepare_data(data_list)) .set_global_opts(title_opts=opts.TitleOpts(title="不同项目运动员体格对比", textstyle_opts=opts.TextStyleOpts(color=TEXT_COLOR))) ) return c # 组装可拖拽大屏 page = Page(layout=Page.DraggablePageLayout) page.add( bar_medal_rank(), line_super_power(), radar_cn_gold(), area_female_ratio(), bar_sport_top10(), box_bmi_compare() ) # 生成草稿页面,自由拖拽布局 page.render("奥运大屏草稿.html") print("草稿大屏已生成:奥运大屏草稿.html,打开拖拽调整布局") # 布局固化代码(拖拽完成后执行) # from pyecharts.charts import Page # Page.save_resize_html( # source="奥运大屏草稿.html", # cfg_file="chart_config.json", # dest="奥运最终可视化大屏.html" # ) # print("固化大屏完成!")

大屏使用步骤

  1. 运行代码生成奥运大屏草稿.html,Chrome 浏览器打开;
  2. 拖拽、缩放所有图表,自定义大屏布局;
  3. 点击页面左上角Save Config,自动下载chart_config.json
  4. 执行固化代码,生成无多余边框、固定布局的正式大屏 HTML。

四、核心分析结论(写报告直接复制)

1. 全球运动员画像

  1. 百年奥运男性参赛占比 72.5%,女性仅 27.5%,但女性参赛比例持续逐年上升,体现全球性别平等进程;
  2. 男性 BMI 整体高于女性,篮球、举重选手 BMI 显著高于体操运动员;
  3. 运动员平均年龄稳定在 24-26 岁区间,无明显高龄化 / 低龄化趋势。

2. 世界体育版图地缘变化

  1. 全历史榜单:美国、俄罗斯(含苏联、独联体)、德国长期稳居奖牌前三;
  2. 1994 年后现代格局:苏联解体后俄罗斯奖牌总量大幅下滑,中国稳步上升,挤进世界第一梯队;
  3. 长尾效应明显:超半数国家仅获得 1-3 枚奖牌,多为小型岛国、发展中国家。

3. 中国奥运崛起核心发现

  1. 爆发拐点:1984 年洛杉矶奥运会实现金牌零突破,2008 北京东道主奖牌达到峰值;
  2. 性别结构:早期女子奖牌占比极高(跳水、乒乓球、举重),近年男子项目成绩稳步提升,男女发展趋于均衡;
  3. 优势项目:跳水、乒乓球、体操、举重、射击是中国夺金基本盘,符合 “二八定律”,80% 金牌来自 20% 优势项目;
  4. 冬季奥运起步晚,但短道速滑逐步成为冬奥核心夺金项目。

4. 社会学验证结论

  1. 东道主效应:主办国当年奖牌数显著高于前后两届,主场优势客观存在;
  2. 女性平权:1896 首届奥运无女性参赛,2016 里约女性参赛占比突破 40%,是全球女性权益发展的缩影;
  3. 政权更迭直接改变奖牌榜单格局,奥运数据是地缘政治、国家综合实力的直观镜像。

五、项目拓展思考题(课程作业加分项)

  1. 为什么身体指标缺失值身高用中位数、体重 / 年龄用均值?
  2. 统一 URS/EUN/RUS 为 Russia 分组统计有什么优缺点?单独分析俄罗斯联邦如何过滤年份?
  3. 数据集查询首金与历史许海峰是否一致?数据偏差来源是什么?
  4. 如何设计帕累托图验证 “80% 奖牌来自 20% 优势项目”?
  5. 如何通过 City、Year 字段自动识别每届东道主,量化验证东道主光环?

六、项目总结

本项目完整复刻企业级数据分析流水线:数据加载→多表关联→清洗补全→特征衍生→多维探索→静态可视化→交互式拖拽大屏,覆盖大一数据分析全部核心知识点。数据集公开易得,代码可直接运行,课程报告、期末大作业、个人练手都非常合适。

配套资源:数据集可在 Kaggle 搜索120 years of Olympic history: athletes and results下载,完整代码已全部贴出,复制即可运行。

标签

#Python 数据分析 #Pandas 实战 #Pyecharts 可视化 #奥运数据分析 #数据大屏 #大一课程作业 #Matplotlib #数据挖掘

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询