一、前言大家好今天分享一套大一课内数据分析完整实战项目 ——1896-2016 百年奥运数据探索与体育强国分析基于 Kaggle 经典奥运数据集完整覆盖数据读取、多表关联、缺失值清洗、特征工程、多维探索分析、中国专题、交互式可视化大屏全流程适合数据分析入门练手、课程大作业、期末报告。项目信息数据集Kaggle 120 年奥运历史数据athlete_events.csv noc_regions.csv数据规模27 万 运动员参赛记录技术栈Pandas、NumPy、Matplotlib、Pyecharts项目周期课内 4h 课外自主实践学习目标掌握多表合并、缺失值处理、BMI 特征衍生、静态 交互式可视化、拖拽式数据大屏开发二、数据集介绍1. 两张核心 CSVathlete_events.csv 运动员明细表| 字段 | 含义 | | ---- | ---- | | ID | 运动员唯一编号 | | Name、Sex、Age、Height、Weight | 姓名、性别、年龄、身高 (cm)、体重 (kg) | | NOC | 国家奥委会三位代码 | | Games、Year、Season、City | 赛事全称、年份、夏 / 冬奥、举办城市 | | Sport、Event | 大项、细分小项 | | Medal | 奖牌Gold/Silver/Bronze/ 空无奖牌 |noc_regions.csv 国家代码映射表NOC奥委会编码region国家 / 地区全称notes备注历史政权、特殊代表团说明2. 数据痛点大量身高、体重、年龄缺失Medal 字段空值代表未获奖不能直接删除历史政权代码冗余URS (苏联)、EUN (独联体)、RUS (俄罗斯)GER/FRG/GDR (两德)特殊 NOCROT 难民代表团、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, onNOC, howleft) # 查看未匹配到国家的特殊NOC难民、未知地区 missing_NOC df[df[region].isna()][NOC].unique() print(无匹配国家的NOC代码, missing_NOC) # 输出[SGP, ROT, UNK, TUV] # 数据基础统计 print(df.describe())步骤 3数据清洗 特征工程阶段二核心3.1 缺失值填充策略region 空值填充为 Unknown难民 / 未知代表团Medal 空值统一标记No Medal区分获奖 / 未获奖身高、体重、年龄缺失身高用中位数不受极端身高干扰年龄、体重用均值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] 东德、西德、统一德国合并统计策略思考题解答统一合并 URS/EUN/RUS 统计总奖牌优势无需手动拼接多段政权数据直接按 region 分组即可得到俄罗斯全历史奖牌单独分析俄罗斯联邦增加过滤条件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), shadowTrue) plt.title(百年奥运男女运动员参赛占比) plt.show() # 2. 男女年龄箱线图 df.boxplot(columnAge, bySex, figsize(8,5)) plt.title(男女运动员年龄分布) plt.suptitle() plt.show() # 3. 男女BMI分布直方图 plt.figure(figsize(14,6)) plt.hist(df[df[Sex]M][BMI], bins30, alpha0.5, label男性) plt.hist(df[df[Sex]F][BMI], bins30, alpha0.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(markero, 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(ascendingFalse).head(20) top20_all.plot(kindbarh, 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(ascendingFalse).head(20) top20_modern.plot(kindbarh, 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(kindpie, 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(markero, 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_value0) gender_year.plot(kindbar, stackedTrue, 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(kindpie, autopct%.2f%%, figsize(10,10)) plt.title(中国获奖最多十大运动项目) plt.ylabel() plt.show()步骤 6Pyecharts 拖拽式可视化大屏阶段六完整代码支持暗黑主题、自由拖拽调整布局最终固化为正式大屏 HTMLpython运行# 统一全局暗黑主题配置 TEXT_COLOR #ffffff BG_COLOR #0a0e27 def get_base_opts(title_name): return opts.InitOpts(bg_colorBG_COLOR, themedark, width500px, height350px) # 图表1历史总奖牌TOP10横向柱状图 def bar_medal_rank(): top10 df_medals[region].value_counts().head(10).sort_values() c ( Bar(init_optsget_base_opts(历史总奖牌榜TOP10)) .add_xaxis(top10.index.tolist()) .add_yaxis(奖牌总数, top10.values.tolist(), color#00d2ff) .reversal_axis() .set_global_opts( title_optsopts.TitleOpts(title百年奥运总奖牌榜TOP10, textstyle_optsopts.TextStyleOpts(colorTEXT_COLOR)), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(colorTEXT_COLOR)), yaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(colorTEXT_COLOR)) ) ) return c # 图表2中美俄德英五国奖牌历年折线 def line_super_power(): top5 [USA, China, Russia, UK, Germany] years sorted(df_medals[Year].unique()) c Line(init_optsget_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_smoothTrue, linestyle_optsopts.LineStyleOpts(colorcolor)) c.add_xaxis([str(i) for i in years]) c.set_global_opts( title_optsopts.TitleOpts(title五大强国历届奖牌趋势, textstyle_optsopts.TextStyleOpts(colorTEXT_COLOR)), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(colorTEXT_COLOR)), yaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(colorTEXT_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(namei, max_int(top6_sport.max()*1.2)) for i in top6_sport.index] c ( Radar(init_optsget_base_opts(中国金牌优势领域)) .add_schema(schemaschema, splitarea_optsopts.SplitAreaOpts(is_showTrue)) .add(金牌数, [top6_sport.values.tolist()], color#ffd700) .set_global_opts(title_optsopts.TitleOpts(title中国金牌优势项目雷达图, textstyle_optsopts.TextStyleOpts(colorTEXT_COLOR))) ) return c # 图表4百年女性参赛比例面积图 def area_female_ratio(): gender_cnt df.groupby([Year,Sex])[ID].count().unstack(fill_value0) gender_cnt[Female_Ratio] (gender_cnt[F] / (gender_cnt[M] gender_cnt[F]) * 100).round(2) c ( Line(init_optsget_base_opts(女性运动员参赛占比演变)) .add_xaxis([str(i) for i in gender_cnt.index]) .add_yaxis(女性占比(%), gender_cnt[Female_Ratio].tolist(), areastyle_optsopts.AreaStyleOpts(opacity0.5, color#ff69b4), color#ff69b4) .set_global_opts(title_optsopts.TitleOpts(title百年奥运女性参赛比例变化, textstyle_optsopts.TextStyleOpts(colorTEXT_COLOR))) ) return c # 图表5热门参赛项目TOP10柱状图 def bar_sport_top10(): sport_cnt df[Sport].value_counts().head(10).sort_values() c ( Bar(init_optsget_base_opts(参赛人次最多十大项目)) .add_xaxis(sport_cnt.index.tolist()) .add_yaxis(参赛人次, sport_cnt.values.tolist(), color#7b68ee) .reversal_axis() .set_global_opts(title_optsopts.TitleOpts(title热门运动项目TOP10, textstyle_optsopts.TextStyleOpts(colorTEXT_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_optsget_base_opts(三大项目运动员BMI分布)) .add_xaxis(sport_list) .add_yaxis(BMI指数, Boxplot.prepare_data(data_list)) .set_global_opts(title_optsopts.TitleOpts(title不同项目运动员体格对比, textstyle_optsopts.TextStyleOpts(colorTEXT_COLOR))) ) return c # 组装可拖拽大屏 page Page(layoutPage.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_filechart_config.json, # dest奥运最终可视化大屏.html # ) # print(固化大屏完成)大屏使用步骤运行代码生成奥运大屏草稿.htmlChrome 浏览器打开拖拽、缩放所有图表自定义大屏布局点击页面左上角Save Config自动下载chart_config.json执行固化代码生成无多余边框、固定布局的正式大屏 HTML。四、核心分析结论写报告直接复制1. 全球运动员画像百年奥运男性参赛占比 72.5%女性仅 27.5%但女性参赛比例持续逐年上升体现全球性别平等进程男性 BMI 整体高于女性篮球、举重选手 BMI 显著高于体操运动员运动员平均年龄稳定在 24-26 岁区间无明显高龄化 / 低龄化趋势。2. 世界体育版图地缘变化全历史榜单美国、俄罗斯含苏联、独联体、德国长期稳居奖牌前三1994 年后现代格局苏联解体后俄罗斯奖牌总量大幅下滑中国稳步上升挤进世界第一梯队长尾效应明显超半数国家仅获得 1-3 枚奖牌多为小型岛国、发展中国家。3. 中国奥运崛起核心发现爆发拐点1984 年洛杉矶奥运会实现金牌零突破2008 北京东道主奖牌达到峰值性别结构早期女子奖牌占比极高跳水、乒乓球、举重近年男子项目成绩稳步提升男女发展趋于均衡优势项目跳水、乒乓球、体操、举重、射击是中国夺金基本盘符合 “二八定律”80% 金牌来自 20% 优势项目冬季奥运起步晚但短道速滑逐步成为冬奥核心夺金项目。4. 社会学验证结论东道主效应主办国当年奖牌数显著高于前后两届主场优势客观存在女性平权1896 首届奥运无女性参赛2016 里约女性参赛占比突破 40%是全球女性权益发展的缩影政权更迭直接改变奖牌榜单格局奥运数据是地缘政治、国家综合实力的直观镜像。五、项目拓展思考题课程作业加分项为什么身体指标缺失值身高用中位数、体重 / 年龄用均值统一 URS/EUN/RUS 为 Russia 分组统计有什么优缺点单独分析俄罗斯联邦如何过滤年份数据集查询首金与历史许海峰是否一致数据偏差来源是什么如何设计帕累托图验证 “80% 奖牌来自 20% 优势项目”如何通过 City、Year 字段自动识别每届东道主量化验证东道主光环六、项目总结本项目完整复刻企业级数据分析流水线数据加载→多表关联→清洗补全→特征衍生→多维探索→静态可视化→交互式拖拽大屏覆盖大一数据分析全部核心知识点。数据集公开易得代码可直接运行课程报告、期末大作业、个人练手都非常合适。配套资源数据集可在 Kaggle 搜索120 years of Olympic history: athletes and results下载完整代码已全部贴出复制即可运行。标签#Python 数据分析 #Pandas 实战 #Pyecharts 可视化 #奥运数据分析 #数据大屏 #大一课程作业 #Matplotlib #数据挖掘