简介:面向高校Python课程及数据分析初学者,这份全国高校数据分析与可视化期末大作业提供了从数据读取、缺失值处理到可视化展示的完整实践方案。资料包内含源码、数据与文档,代码借助pandas完成数据加载、缺失值填充与异常值检测,并用matplotlib、seaborn产出统计图表,覆盖数据预处理、清洗、标准化和绘制等关键环节,适合参考课程设计、完成同类分析任务或巩固数据科学基础。
包体共19个文件,以HTML报告、CSV原始数据、CSS样式表为核心,另有Python脚本、Jupyter Notebook、Markdown说明文档及图片资源,整体约1.52MB,目录结构清晰,便于对不同类型文件按用途检索。目前已有2687人学习,说明其内容对初学者具有较强的参考价值。
获取后可基于真实高校数据复现从读取到呈现的完整流程,也可直接借鉴代码中的清理逻辑与可视化样式,用于拓展自身项目的交互展示。
1. 全国高校数据分析与可视化:拿到源码包之后,真正要交付的是什么
一份标注为“Python数据可视化分析大作业-全国高校数据分析与可视化(源码+文档).zip”的压缩包摆在桌面,很多人第一反应是解压、跑一遍代码,看到柱状图和地图都出来了就长舒一口气。我见过不少同学拿着几乎同样一套代码,最后分数却差出两档。问题很少出在图表好不好看,而是出在数据口径和能不能把图讲清楚。这个项目真正要交付的,是一套能复现、能解释、能扛住答辩追问的分析流程,不是几张png。
下面按我自己做这类作业的路线拆开讲:名单怎么拿、字段怎么定、数据怎么洗、图怎么选、哪些坑必踩,以及文档怎么写才不被扣分。新手可以直接照着做,熟手可以重点看合并去重和地图匹配那两个环节。
如果你正准备开始这个方向,又或者代码已经写完但报告被退回来,这六章内容可以直接照着调整。我自己在编码和出图上栽过好几回,这里写的都是事后觉得最值得复盘的内容。
2. 数据准备:把全国高校名单变成可分析的二维表
做全国高校数据分析,第一件事不是写爬虫,而是把“全国高校”四个字定义清楚。公开渠道最常见的口径是由教育主管部门发布的《全国高等学校名单》,每年更新,包含学校名称、标识码、办学层次、所在省份等基础字段。另一类常见来源是第三方信息平台,字段更丰富但质量参差不齐。我的习惯是官方名单打底、第三方数据只做补充,顺序不能反。
这样选有三个理由。第一,官方名单有明确的发布周期和统计口径,答辩时被问“数据从哪来”,你能给出可查证出处;第二,官方名单缺少的标签类字段,比如双一流、985/211标记,从第三方平台按标识码合并进来,误差可控;第三,依赖爬虫脚本的话,页面改版、反爬、字段错位都会变成消耗时间的“黑匣子”,大作业阶段不值得冒这个险。
实际操作里,我会把名单页面在浏览器里全选复制到excel,再另存为csv放进项目data目录。这个方式看起来笨,却比写爬虫稳定得多。名单页表格结构每年微调,爬虫维护成本远高于手工保存。源码里我会放一小段爬虫示例,但只讲思路和失败策略,不追求全自动。
2.1 数据源怎么选:官方名单打底,第三方数据只做补充
常见的爬虫写法是用requests抓页面,再做BeautifulSoup解析:
import requests from bs4 import BeautifulSoup import pandas as pd url = "https://example.edu.cn/final_list" # 替换成目标名单页 headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} resp = requests.get(url, headers=headers, timeout=10) resp.encoding = resp.apparent_encoding soup = BeautifulSoup(resp.text, "html.parser") table = soup.select_one("table") rows = [ [td.get_text(strip=True) for td in tr.find_all(["th", "td"])] for tr in table.select("tr") ] df_raw = pd.DataFrame(rows[1:], columns=rows[0]) print(df_raw.head())代码里resp.encoding = resp.apparent_encoding是为了防止页面声明的编码和实际内容不一致导致中文乱码;headers放User-Agent是因为不少站点对没有浏览器标识的请求直接返回403。select_one("table")取的是页面第一个表格,实际使用时要把选择器改成目标页面里真正承载名单的那个table。
这段跑通之后,我建议立刻把df_raw保存成csv,后续所有清洗都从csv继续,避免每次运行都重复请求。如果页面把本科和专科拆成两个表格,就要各解析一次再纵向合并,否则学校数量会少一截。保存完成后顺手看一眼df_raw.shape[0],和官方公布数量做初步核对。数量对不上就先补数据,不要急着往下跑。
另一种常见做法是调用第三方开放接口,一次拿到含全部字段的json。这确实快,但第三方平台的数据更新频率不一,有的还把职业本科、普通本科、异地校区揉成同一种标签。用它们做补充字段可以,做基准数据就太重风险了。
2.2 字段设计:一个能扛住地图和透视表的最小结构
原始名单字段通常很多,但大作业里真正用到的字段很少。我一般把数据收敛成下面这个最小结构,后面的绘图和透视都只依赖这些列:
| 字段名 | 说明 | 示例 | 是否必须 |
|---|---|---|---|
| school_name | 学校全称 | 某高校 | 必须 |
| school_id | 学校标识码 | 123456789012 | 必须,合并主键 |
| level | 办学层次 | 本科 / 专科 | 必须 |
| province | 省份(规范化) | 某省 / 某自治区 | 必须 |
| city | 城市 | 普通地级市 | 看需求 |
| category | 院校类型 | 综合 / 理工 / 师范 | 建议 |
| tag_shuangyiliu | 标签标记 | 1 / 0 | 看分析选题 |
province是坑最多的位置。原始数据里可能同时出现“某省市辖区”“某省某市”“某自治区”三种写法,pandas透视表里它们都能用,但地图组件只会按固定的省名去匹配。所以我在清洗阶段统一去掉“省”“市”“自治区”等后缀,只留省份主体名称,这一步做早了能避免大量返工。
school_id则是合并和去重的关键。学校更名在高校里很常见,只按学校名称关联会把同一所学校算成两所;只按标识码关联又容易因为excel把长数字变成科学计数法而丢失尾部精度。我的做法是读取时就把school_id指定为字符串,合并时名称和标识码两个键同时使用。
字段定好之后,第一次探查我只看df["province"].unique()的输出。这一步能立刻暴露不同写法并存的问题,也决定了后面归一化规则要覆盖多少种情况。我习惯先探查再写清洗函数,而不是闭着眼睛写规则猜数据长什么样,这个顺序能省掉大量试错时间。
3. 用pandas清洗高校数据:合并、去重、类型推断一条线
字段定好之后,清洗阶段是整个项目最花时间但最不出彩的部分。我习惯把处理顺序固定成“读取探查、字段归一化、合并去重、输出分析表”四个步骤,每步都写成可独立运行的最小代码块。这样调试时不用重头跑,答辩时也能直接把清洗链路讲清楚。
3.1 读取和初步探查:先看shape和info,别急着画图
数据进pandas的第一步是确认形状和类型。不少同学上来就df.groupby(...).plot(),等到报错才发现某些列全是空值,白白浪费半天,所以先探查最划算:
import pandas as pd df = pd.read_csv("data/colleges_raw.csv", encoding="utf-8-sig") print("shape:", df.shape) print(df.dtypes) print(df.isna().sum()) print(df.sample(5, random_state=42))encoding="utf-8-sig"是从excel另存的csv里读中文的关键参数,普通utf-8会把第一列列名读成带\ufeff前缀的脏值。sample(5, random_state=42)固定随机种子,让你每次抽查结果一致,方便和别人对齐问题。
做完探查,我顺手把列名统一改成小写加下划线的风格。原始数据里常见的“学校名称”“主管部门”等中文列名,后续代码写起来容易乱码,透视表引用也麻烦。改名之后merge时两个dataframe列名不一致会立刻暴露,不会拖到最后一刻才报错。
空值也要在这个阶段记录一次。比如办学地址在境外、或者多校区办学导致省份字段缺失,这些记录我会先保留,等画分布图时再决定归到“其他”还是剔除。直接在探查阶段删空值,很容易把边缘案例误伤。
3.2 合并多份名单:横向补字段,纵向去重复
大作业里最常见的清洗场景,是把官方名单和第三方补充信息做左连接。官方名单是base,补充信息提供双一流、985等标签:
base = pd.read_csv("data/base_list.csv", encoding="utf-8-sig") extra = pd.read_csv("data/extra_tags.csv", encoding="utf-8-sig") # merge前先去重,否则重复行会让结果行数膨胀 extra = extra.drop_duplicates(subset=["school_id"]) merged = pd.merge( base, extra[["school_id", "school_name", "tag_shuangyiliu"]], on=["school_id", "school_name"], how="left", )on同时使用两个字段,是为了防止学校更名导致关联丢失。how="left"保证base里所有记录都保留,缺失的标签变成NaN,后面用fillna(0)补上。合并前的drop_duplicates必须执行,否则要么左连接时多张表出现同一所学校,结果行数会比真实学校数量多出好几页。
纵向去重用于拼接多年名单做变化分析,比如把两个年份名单堆在一起,用school_id找出新增学校:
df_all = pd.concat([df_2020, df_2024], ignore_index=True) df_new = df_all.drop_duplicates(subset=["school_id"], keep=False)但keep=False有个隐藏陷阱:一所学校从专科升本科后,两个年份名单里都有它,这个方案会把它当成“无变化”漏掉。正确做法是先按年份排序,保留每个学校最新记录,再比较办学层次变化。做高校数量变化类选题时,这个细节特别容易被忽略。
3.3 字段归一化:省份、层次、类型的统一写法
归一化的核心目标是让同一含义只有一种写法。省份去掉“省/市/自治区”后缀时,我用长词优先的正则:
import re def normalize_province(x): s = str(x).strip() # 如果名单里出现带民族前缀的自治区全称, # 要把较长名称放在最前面,防止先被“省|市”切坏。 s = re.sub(r"(民族自治区|自治区|省|市)", "", s) return s df["province"] = df["province"].map(normalize_province)顺序很重要。如果先匹配省|市,再匹配更长的区划后缀,结果就会把长名称拆成残缺字,最后得到一批乱码省名。正则的匹配规则要求长词优先,这个坑我在早期项目里踩过,后来才明白为什么规则必须这样排。
办学层次也经常出现“本科”“本科(公办)”“高职(专科)”混杂写法。我的归一化函数先把全角括号转半角,再按关键词判断:
def normalize_level(x): s = str(x).replace("(", "(").replace(")", ")") if "本科" in s: return "本科" if "专科" in s or "高职" in s: return "专科" return "其他" df["level"] = df["level"].map(normalize_level)这个函数之后,pivot_table里的level列只剩本科、专科、其他三个取值,做占比计算时不会出现十几种变体。院校类型同理,只需要判断是否包含综合、理工、师范等关键词,不需要追求和第三方平台完全一致。
清洗收尾我习惯再做一次分组复查:df.groupby(["province", "level"]).size()。如果某个省份的专科数量突然变成0,大概率是归一化规则误伤了它的省名。多花一分钟做复查,比画完图再返工高效得多。
4. 可视化实现:从静态图表到可交互地图
数据干净之后,可视化反而是最让人放松的部分。大作业图表不需要炫技,关键在每张图都支撑一个结论。我通常的组合是一张静态柱状图、一张静态比例图、一张可交互地图,再加一两个对比小图,形成主图和辅图的配合。
4.1 matplotlib看分布,seaborn看排名:先给结论找证据
第一张主图通常是“高校数量排名靠前的省份”。下面用groupby统计每个省份高校数量,取前20画横向柱状图:
import matplotlib.pyplot as plt counts = df.groupby("province")["school_name"].count().sort_values(ascending=False).head(20) fig, ax = plt.subplots(figsize=(10, 6)) counts.plot.barh(color="#2F7F7F", ax=ax) ax.set_title("高校数量排名靠前的省份") ax.set_xlabel("高校数量") plt.tight_layout() plt.savefig("figures/top20_bar.png", dpi=200)用barh横向柱状图是因为省份名较长,纵向柱状图旋转坐标轴后仍然会互相遮挡。dpi=200是为报告印刷准备的清晰度,屏幕上和72dpi看不出差别,放进word打印就很明显。
第二张图用seaborn做本科和专科占比对比。这张图的结论通常指向某类省份的高教结构偏本科还是偏专科:
import seaborn as sns pivot = pd.pivot_table( df, index="province", columns="level", values="school_name", aggfunc="count", ).fillna(0) pivot["专科占比"] = pivot["专科"] / (pivot["本科"] + pivot["专科"]) top = pivot.sort_values("专科占比", ascending=False).head(10) sns.barplot(x=top["专科占比"], y=top.index, palette="flare") plt.xlabel("专科占比") plt.tight_layout() plt.savefig("figures/zhuanke_ratio.png", dpi=200)pivot_table把省份变成索引、办学层次变成列,是省份×层次的透视标准写法。如果level列还残留“其他”值,分母就会失真,所以上一章的归一化必须做干净,这个比例才有解释力。
4.2 pyecharts地图:把省份计数映射到全国地图
地图是这个项目的亮点,也是坑最多的地方。常见做法是用pyecharts 1.x的Map组件,把省份—高校数量渲染成区域着色图:
from pyecharts.charts import Map from pyecharts import options as opts province_counts = df["province"].value_counts() data_pair = list(zip(province_counts.index, province_counts.values)) map_chart = ( Map() .add( series_name="高校数量", data_pair=data_pair, maptype="china", ) .set_global_opts( title_opts=opts.TitleOpts(title="全国高校数量分布"), visualmap_opts=opts.VisualMapOpts(max_=200), ) .set_series_opts(label_opts=opts.LabelOpts(is_show=False)) ) map_chart.render("figures/college_map.html")data_pair必须是[(省名, 数值), ...]的二维结构,pyecharts内部拿省名去匹配中国地图json。visualmap_opts里的max_=200是颜色映射上限,如果最大值远高于200,整张图会大部分都是最深色,看不出梯度差,要根据实际数据调整。
跑这段代码前要确认本地已经安装了地图资源包。pyecharts 1.x不会自动下载地图数据,缺资源包时页面不报错,但渲染出来是白色地图,属于最让人头疼的“玄学问题”。另一个细节:render生成的是html,引用本地js资源,只交html不交资源文件,别人打开也很容易白屏。我一般同时截一张png放进文档,html只在答辩现场演示用。
4.3 多图排版和导出:报告配图的清晰度控制
报告里建议控制在四到六张图,不追求数量。需要对比时可以用subplots把两张图并排,比如本科Top10和专科Top10左右排列,一张图就能支撑一个对比论断:
fig, axes = plt.subplots(1, 2, figsize=(14, 5)) for i, level in enumerate(["本科", "专科"]): sub = ( df[df["level"] == level] .groupby("province") .size() .sort_values(ascending=False) .head(10) ) sub.plot.bar(ax=axes[i], color="#4C72B0") axes[i].set_title(f"{level}高校数量Top10") plt.tight_layout() plt.savefig("figures/level_compare.png", dpi=200)这张对比图背后要有一个可讲解的结论:某个省份在本科和专科排名上的差异,说明它的高教结构偏向哪一边。图表本身不产生结论,结论要由你写进报告。
导出时,所有报告配图统一存到figures/目录,文件名带序号,比如图1-省份分布.png。这样写文档时引用路径不容易出错,答辩前检查也能一眼看出有没有漏图。
5. 避坑:高校数据可视化项目里最常见的五个翻车现场
这一章写我在同类项目里反复见过的问题,每条按“现象、原因、解决”展开,覆盖大作业里最消耗时间的五个坑。
5.1 地图上部分省份渲染不出来
现象:地图html打开后,大部分区域有着色,但个别省级区域显示浅色或空白,鼠标悬停也没有数值提示。
原因:data_pair里的省名和地图json里的name对不上。原始数据写的是“某自治区”全称,而地图数据用的是不带后缀的主名;甚至清洗正则在长名称上顺序写错,把省名切成了残缺字。
解决:清洗阶段用长词优先的规范化正则处理,再跑一段省名校验。把pyecharts地图实际使用的省名写进一个文本文件,读取后和df里的province做差集:
with open("data/standard_provinces.txt", encoding="utf-8") as f: standard = [line.strip() for line in f if line.strip()] missing = set(df["province"].unique()) - set(standard) print("无法匹配的省名:", missing)这段校验代码执行后,任何拼写不一致都会直接列出来,不需要对着地图一点点找哪个省没显示。
5.2 用Excel打开保存的CSV,中文乱码
现象:pandas里读写csv都正常,但把csv发给别人用excel打开,中文全部乱码,第一列列名还多出一个奇怪字符。
原因:pandas默认以utf-8写csv,excel在中文系统里默认按gbk读取。utf-8文件里如果带BOM头,第一列列名还可能显示\ufeff。
解决:保存csv时显式指定编码:
df.to_csv("data/cleaned.csv", index=False, encoding="utf-8-sig")utf-8-sig会写入BOM头,excel识别到BOM就能正确解码。这个参数用普通utf-8代替,交上去的分析表就会乱码,属典型的“血泪经验”级教训。
5.3 合并后高校总数和官方名单差了几十所
现象:按school_id去重后的学校总数,比官方公布数量多了或少了十几所,怎么核对都对不上。
原因:三种典型情况。一是excel打开过名单后,长标识码被转成科学计数法,读取时末尾几位变成0,不同学校被当成同一所;二是第三方补充表里有重复行,左连接后行数膨胀;三是一些异地校区、筹办高校在名单里单独出现,导致额外多出特殊记录。
解决:读取时把school_id强制设为字符串:
df = pd.read_csv("data/base_list.csv", dtype={"school_id": str})合并前对extra先去重,然后把不一致记录导出成列表,人工看几十条差异。人工逐条比对看起来原始,但比写复杂模糊匹配有效得多。我曾在一次项目里查了很久,最后发现是“异地校区”和“校本部”被当成了两所独立学校。
5.4 matplotlib图片里中文全部变成方块
现象:坐标轴、标题的中文全变成小方块,英文数字正常,保存出来的图片没法放进报告。
原因:matplotlib默认字库里没有中文字形,需要显式指定系统中已安装的中文字体。不同操作系统下字体名不同,单独指定一个字体名在另一台电脑上可能失效。
解决:把候选字体写成列表:
import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = [ "Microsoft YaHei", "SimHei", "PingFang SC", "Noto Sans CJK SC", ] plt.rcParams["axes.unicode_minus"] = Falsematplotlib会从列表中选第一个可用的中文字体,换电脑也不会挂。第二行axes.unicode_minus用于修复负号显示成方块的问题,因为默认ASCII减号在很多中文字体下渲染异常。
5.5 报告里的数字和图表对不上
现象:报告正文写高校总数2868所,但柱状图各列数量明显大于总数,或者图里用的数据版本比正文旧。
原因:图表用去重后的dataframe生成,正文数字则手打自清洗前的旧数据;或者报告配图是早期版本,数据更新后没有重新导图。
解决:文中关键数字全部由代码输出,禁止手打。在清洗收尾阶段集中计算一次:
total_schools = df["school_id"].nunique() province_count = df["province"].nunique() print(f"高校总数: {total_schools}, 覆盖省份: {province_count}")写文档时直接用这组输出粘进正文。答辩时老师问任何一个数字,你都能指出它在代码里的出处,这个细节本身就是加分项。
6. 文档与答辩:把图表变成能讲清楚的分析故事
6.1 报告骨架:每个结论都配一张图,每张图都有三句话
大作业文档不需要面面俱到,但结构要完整。我常用的骨架是:一、研究背景,半页;二、数据说明,写清楚数据来源、字段和清洗摘要,一页;三、分析方法,半页到一页;四、结果分析,四到六张图,每张配解读,三到五页;五、结论与不足,半页。
每张图配三句话:图表呈现什么现象;现象产生的原因;现象反映的结构性特征。不要写“如图所示”,要写“图3显示,高校数量最多的省份集中在教育资源密集区域,这与当地经济总量和人口规模存在明显对应”。这三句话练熟了,答辩时顺着讲就能撑起大部分时间。
6.2 可复现环境与代码注释:让老师能一键跑通
源码包里除了代码和文档,还应该有requirements.txt和运行说明。我习惯固定依赖版本:
pandas==1.5.3 matplotlib==3.7.1 seaborn==0.12.2 pyecharts==1.9.1锁定版本是为了避免换台机器后大版本升级导致接口不兼容。代码里每个关键步骤加一行注释,说明这一步在做什么、为什么这么做。评分尺度里最容易被忽略的就是可复现性:老师拿到代码跑不通,图表做得再好都要打折。
我的教训是,早期做项目时把清洗和可视化写在同一个notebook里,前面改了变量后面就崩,答辩前夜全用来调缓存。现在我习惯让清洗、可视化、分析三个notebook相互独立,入口脚本从data/cleaned.csv开始读,任何人从入口跑都能得到同样结果。这个习惯已经帮我避免过好几次临时翻车。希望帮到你。
本文还有配套的精品资源,点击获取