ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python的学生成绩数据分析与可视化项目实战

基于Python的学生成绩数据分析与可视化项目实战 简介这份资源面向Python数据分析初学者与教学实践者围绕学生成绩数据集提供一套完整的数据预处理、统计分析与可视化实现方案帮助读者掌握从原始CSV到图表输出的全流程操作。压缩包共31个文件约76KB以13个txt说明、7个html可视化结果、3个md文档、3个csv数据文件及yml环境配置为主另含核心main.py脚本与requirements依赖清单结构清晰便于按模块查阅。项目先备份原始数据用中位数填充数学、阅读、写作成绩缺失值并汉化列名再按种族、性别、父母教育水平、午餐类型与备考课程等维度计算平均成绩最后通过箱型图、饼图、直方图、散点图等展示特征并借助mpld3将图表导出为HTML便于分享。已有101人学习适合作为课程作业、数据分析入门练手或可视化案例参考。1. 从一份成绩单到一套分析系统学生成绩数据分析与可视化到底在做什么期末考完教务处丢过来一个 Excel几千行、十几个列班主任要班级排名年级组长要分数段分布教研组还想看各科之间的相关性。手工透视表能做一次但下次月考又得重来一遍。基于 Python 的学生成绩数据分析与可视化项目解决的就是把这种重复劳动固化成一条可复用的流水线读原始成绩表、清洗异常值、算统计指标、出图表、导出报告。它适合两类人——一类是刚学完 pandas 想找个真实数据集练手的入门者另一类是需要给学校或机构做成绩看板的开发者。整套东西的核心不是算法多高深而是数据清洗的边界和图表选型的合理性这两点决定了你的分析结果能不能被教研组认可。下面按「数据怎么进来、指标怎么算、图怎么画、坑在哪」的顺序拆开讲。2. 数据读取与清洗成绩表进 pandas 之前要做的四件事2.1 先看清成绩表的真实结构再写代码拿到一份成绩表别急着pd.read_excel。先用 Excel 或 LibreOffice 打开确认三件事表头在第几行、有没有合并单元格、学号列是不是文本格式。很多学校导出的成绩表前两行是标题和考试信息真正的表头在第三行直接读会得到Unnamed: 0这种列名。常见做法是先用headerNone读进来看看前几行再决定header参数。import pandas as pd # 先不指定表头看看原始结构长什么样 raw pd.read_excel(scores.xlsx, headerNone, nrows5) print(raw) # 确认表头在第 3 行索引 2后正式读取 df pd.read_excel(scores.xlsx, header2) print(df.columns.tolist()) print(df.shape)header2表示用第 3 行作为列名nrows5只读前 5 行用于探查避免大文件反复加载。如果学号被读成了数字前导零会丢失需要加dtype{学号: str}。这一步看起来琐碎但列名错位是后面所有统计出错的根源血泪经验是宁可多花两分钟确认结构也别在报错时回头猜。2.2 缺失值、异常值和重复学号的三种处理策略成绩数据里最常见的脏数据有三类缺考导致的空值、录入错误导致的超范围分数比如 150 分制里出现 200、以及同一个学号出现两次。处理策略取决于业务含义不能一刀切。# 1. 查看缺失情况 print(df.isnull().sum()) # 2. 缺考标记为 -1 还是保留 NaN看后续是否参与均分计算 # 如果缺考不应拉低班级均分保留 NaN用 skipna 控制 df[数学] pd.to_numeric(df[数学], errorscoerce) # 3. 异常值超出 0-150 的分数标记出来人工核对 mask (df[数学] 0) | (df[数学] 150) print(df[mask][[学号, 姓名, 数学]]) # 4. 重复学号检查 dup df[df.duplicated(学号, keepFalse)] print(dup)errorscoerce会把无法转成数字的值变成 NaN避免字符串混入导致后续计算出错。异常值不要直接删先打印出来核对因为 200 分可能是满分 200 的科目也可能是录入多打了一个零。重复学号要区分是同一人多次考试还是纯重复录入前者应保留为多行后者去重。这里的关键判断是清洗规则要能解释给教研组听说不清规则的处理方式出了偏差没人担责。2.3 用 describe 和分组统计做第一轮体检清洗完先跑一遍描述性统计对数据分布有个整体感知。这一步能提前发现很多问题比如某科均分只有 30 分大概率是分数制搞错了。subjects [语文, 数学, 英语, 物理, 化学] # 整体描述 print(df[subjects].describe().round(2)) # 按班级分组看均分和人数 class_stat df.groupby(班级)[subjects].agg([mean, count]) print(class_stat.round(2)) # 总分与排名 df[总分] df[subjects].sum(axis1, skipnaTrue) df[班级排名] df.groupby(班级)[总分].rank(ascendingFalse, methodmin)skipnaTrue让缺考科目不参与总分计算但要注意这会让缺考学生的总分天然偏低是否合理取决于学校规则。rank(methodmin)表示并列时取最小名次比如两人并列第 3下一个是第 5。这些参数看着小但排名规则是家长最敏感的地方必须和教务确认清楚再定。3. 统计指标计算均分、标准差、分数段和相关性怎么算才靠谱3.1 均分之外标准差和及格率才是教研组真正关心的只报均分会被追问「这个班是不是两极分化」。标准差反映离散程度及格率、优秀率反映分布形态这三个指标一起看才能判断一个班是整体薄弱还是尾部拖累。import numpy as np def subject_metrics(series, full150, pass_line0.6, excellent_line0.85): valid series.dropna() return pd.Series({ 均分: valid.mean(), 标准差: valid.std(), 及格率: (valid full * pass_line).mean(), 优秀率: (valid full * excellent_line).mean(), 参考人数: len(valid), }) result df.groupby(班级)[数学].apply(subject_metrics).unstack() print(result.round(3))full是满分pass_line和excellent_line用比例表示方便适配 100 分制和 150 分制。dropna()确保缺考不计入分母否则及格率会被稀释。这里有个容易翻车的地方不同科目满分不同如果统一用 100 分制算及格线150 分制的科目及格率会虚高。参数必须按科目配置不能全局写死。3.2 分数段划分等距分段还是按业务阈值分段分数段有两种划法等距每 10 分一段和按业务阈值不及格、及格、良好、优秀。前者适合看整体分布形态后者适合直接对应教学评价。两种都要能出。# 等距分段 bins list(range(0, 151, 10)) labels [f{i}-{i10} for i in range(0, 150, 10)] df[数学段] pd.cut(df[数学], binsbins, labelslabels, rightFalse) seg_dist df[数学段].value_counts().sort_index() print(seg_dist) # 业务阈值分段 def grade_level(score): if pd.isna(score): return 缺考 if score 90: return 不及格 if score 120: return 及格 if score 135: return 良好 return 优秀 df[数学等级] df[数学].apply(grade_level) print(df[数学等级].value_counts())pd.cut的rightFalse表示左闭右开即 90 分归入 90-100 段而不是 80-90 段这个边界必须明确否则同一份数据两个人算出不同结果。业务阈值用函数映射更直观但阈值要写进配置文件而不是散落在代码里方便教研组调整。3.3 科目相关性用热力图之前先确认样本量够不够教研组常问「数学好的学生物理是不是也好」这需要算相关系数。但相关系数对样本量敏感一个班三四十人算出来的相关性波动很大不能当作因果结论。corr df[subjects].corr(methodpearson) print(corr.round(3)) # 样本量提示 print(有效样本量:, df[subjects].dropna().shape[0])method可选pearson线性、spearman秩相关对异常值更稳健。成绩数据常有偏态我一般两个都算差异大就说明有异常值影响。相关系数在 0.3 以下基本可以认为关系弱0.6 以上才值得在报告里强调。样本量低于 30 时建议只描述趋势不下结论这是避免被质疑的基本功。4. 可视化落地用 matplotlib 和 pyecharts 出四类成绩图表4.1 班级均分对比用柱状图但排序和误差线不能省柱状图是最常用的但直接画会显得粗糙。加上标准差误差线和按均分排序信息量立刻不一样。import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False class_mean df.groupby(班级)[总分].mean().sort_values(ascendingFalse) class_std df.groupby(班级)[总分].std().reindex(class_mean.index) fig, ax plt.subplots(figsize(10, 5)) ax.bar(class_mean.index, class_mean.values, yerrclass_std.values, capsize4, color#4C72B0, alpha0.85) ax.set_title(各班总分均分对比含标准差) ax.set_ylabel(总分均分) plt.tight_layout() plt.savefig(class_mean.png, dpi150)yerr传标准差数组画出误差线capsize控制误差线端帽大小。sort_values让柱子从高到低排列读者一眼看出差距。中文字体必须设置否则标题和标签会变成方块这是新手最常遇到的翻车点。dpi150保证导出图片在报告里不糊。4.2 分数段分布用堆叠柱状图比饼图更适合多班级对比饼图只能看一个班的构成多个班对比时堆叠柱状图更清晰。每个班一根柱子不同分数段用颜色区分。seg_by_class pd.crosstab(df[班级], df[数学等级], normalizeindex) seg_by_class seg_by_class[[优秀, 良好, 及格, 不及格, 缺考]] seg_by_class.plot(kindbar, stackedTrue, figsize(10, 5), color[#55A868, #4C72B0, #C44E52, #8172B3, #CCCCCC]) plt.title(各班数学成绩等级构成百分比) plt.ylabel(占比) plt.legend(bbox_to_anchor(1.02, 1), locupper left) plt.tight_layout() plt.savefig(grade_stack.png, dpi150)normalizeindex把每班转成百分比避免人数差异干扰对比。crosstab自动完成分组计数比手写循环干净。图例放到图外避免遮挡柱子。如果某个等级在某班占比为 0堆叠图里会缺一块这是正常的但要在报告里说明。4.3 用 pyecharts 做可交互的班级成绩看板静态图适合打印交互图适合在浏览器里给老师自己筛选。pyecharts 生成的 HTML 可以嵌入校园看板鼠标悬停看具体数值。from pyecharts.charts import Bar, Pie, Grid from pyecharts import options as opts bar ( Bar() .add_xaxis(class_mean.index.tolist()) .add_yaxis(总分均分, class_mean.round(1).tolist()) .set_global_opts( title_optsopts.TitleOpts(title班级均分看板), yaxis_optsopts.AxisOpts(name均分), xaxis_optsopts.AxisOpts(name班级), ) ) bar.render(class_dashboard.html)add_yaxis的第二个参数是数据列表round(1)保留一位小数让标签更整洁。render输出独立 HTML不依赖服务器。如果要多个图拼在一起用Grid组合。pyecharts 的版本差异较大options模块的导入路径在不同版本间有变化建议锁定版本后再写否则升级时容易报ImportError。4.4 相关性热力图颜色映射和数值标注要同时有热力图只给颜色不给数值读者没法精确比较。用annotTrue把系数标在格子里。import seaborn as sns fig, ax plt.subplots(figsize(8, 6)) sns.heatmap(corr, annotTrue, fmt.2f, cmapRdBu_r, vmin-1, vmax1, center0, squareTrue, axax) ax.set_title(各科成绩相关性) plt.tight_layout() plt.savefig(corr_heatmap.png, dpi150)fmt.2f控制标注两位小数vmin/vmax固定色阶范围让不同批次图表可比center0让零相关对应中间色。cmapRdBu_r是红蓝发散色正相关偏红负相关偏蓝符合直觉。如果科目多导致格子太密可以只画下三角但成绩科目一般不超过十门全画也看得清。5. 避坑与排查成绩分析项目里最容易翻车的五个地方5.1 中文乱码图表标题全是方块现象matplotlib 生成的图片里中文显示为方框。原因默认字体不含中文字形。解决在绘图前设置rcParams[font.sans-serif]Linux 服务器上如果没有 SimHei换成WenQuanYi Micro Hei或Noto Sans CJK SC并用fc-list :langzh确认系统已装中文字体。5.2 学号前导零丢失导致匹配失败现象成绩表和学籍表按学号合并时大量匹配不上。原因pandas 把学号列推断成了整数00123变成123。解决读取时显式指定dtype{学号: str}合并前统一两边的类型和去空格。这个坑在跨表关联时几乎必现提前指定类型是最省事的后悔药。5.3 缺考被当成 0 分拉低均分现象某班均分异常低排查发现缺考学生被填了 0。原因数据源用 0 表示缺考代码没区分。解决读取后先把 0 替换为 NaN前提是确认没有真实考 0 分的情况或者用单独的缺考标记列。如果无法区分必须在报告里注明均分口径避免误读。5.4 排名并列规则不一致引发争议现象两个学生总分相同一个显示第 3 一个显示第 4家长质疑。原因rank默认methodaverage并列取平均名次。解决和教务确认规则methodmin是并列取最小名次3,3,5methoddense是连续名次3,3,4。规则写进文档不要每次临时决定。5.5 pyecharts 版本升级后导入报错现象昨天能跑的脚本今天ImportError。原因pyecharts 1.x 和 0.5.x 的模块结构不同options的导入路径变了。解决用pip freeze锁定版本或在虚拟环境里固定pyecharts1.9.1这类明确版本。生产环境不要用不带版本约束的pip install pyecharts。6. 把分析脚本变成可复用工具的两个进阶技巧第一个技巧是把科目配置和阈值抽成外部 JSON。成绩分析最烦的是每所学校满分不同、科目不同、等级线不同硬编码在代码里改一次要动好几处。抽成配置文件后换一所学校只改 JSON 不改代码。import json with open(config.json, r, encodingutf-8) as f: cfg json.load(f) # config.json 示例结构 # { # subjects: {语文: 150, 数学: 150, 英语: 150}, # pass_ratio: 0.6, # excellent_ratio: 0.85, # grade_lines: {不及格: 90, 及格: 120, 良好: 135} # } def build_metrics(series, full): valid series.dropna() return { 均分: round(valid.mean(), 2), 及格率: round((valid full * cfg[pass_ratio]).mean(), 3), 优秀率: round((valid full * cfg[excellent_ratio]).mean(), 3), }这样教研组要调及格线改 JSON 就行不用找你改代码。配置和逻辑分离是这类工具能不能被别人接手的关键。第二个技巧是用一次考试的数据验证整条流水线再批量跑历史数据。很多人一上来就想把三年的成绩全跑一遍结果中间某次考试表头格式不同整批失败还找不到是哪次出的问题。我的习惯是先拿最近一次考试跑通全流程确认输出图表和指标都对再写循环批量处理每次处理完打印文件名和行数。import glob for path in sorted(glob.glob(exams/*.xlsx)): try: df pd.read_excel(path, header2, dtype{学号: str}) # ... 清洗、统计、出图 ... print(fOK {path} rows{len(df)}) except Exception as e: print(fFAIL {path} reason{e})try/except让单次失败不中断整批print记录成功和失败的文件跑完一眼看出哪次有问题。这个习惯帮我省过很多次通宵排查的时间。成绩分析项目的价值不在于图表多花哨而在于换一份数据、换一所学校改几行配置就能跑出可信的结果。把清洗规则、统计口径、排名规则都写清楚别让下一个人靠猜。希望帮到你。本文还有配套的精品资源点击获取
返回列表