好的,我们进入:
🧠 第5周 · 第7天
🎯 主题:测试复盘 + 项目封装实战
✅ 今日目标
- 回顾第5周数据分析与可视化核心知识
- 对整个“学生成绩分析系统”进行项目封装与模块化拆分
- 增加命令行参数支持,提升可复用性与实用性
- 打包输出完整分析报告
📚 一、本周复盘重点
模块 | 核心技能 | 工具库 |
---|---|---|
数据清洗 | 缺失值处理、类型转换 | pandas |
数据筛选 | 条件过滤、排序 | pandas |
分组聚合 | groupby、agg、pivot_table | pandas |
可视化 | 折线图、直方图、箱线图等 | matplotlib / seaborn |
报告整合 | Markdown 输出、图表嵌入 | 文件系统操作 |
🛠 二、项目封装建议(结构)
student_report_project/
├── data/
│ └── students_cleaned.csv
├── charts/
│ └── (生成的图表)
├── scripts/
│ ├── clean_data.py
│ ├── filter_and_sort.py
│ ├── groupby_agg_analysis.py
│ ├── visualize_students.py
│ ├── generate_report.py
│ └── main.py 👈 综合入口(命令行参数)
├── README.md
🧩 三、命令行支持示例:main.py
import argparse
import subprocess
parser = argparse.ArgumentParser(description="学生成绩分析工具")
parser.add_argument('--step', type=str, help="选择执行步骤,如 clean | visualize | report | all")
args = parser.parse_args()
if args.step == "clean":
subprocess.run(["python", "scripts/clean_data.py"])
elif args.step == "visualize":
subprocess.run(["python", "scripts/visualize_students.py"])
elif args.step == "report":
subprocess.run(["python", "scripts/generate_report.py"])
elif args.step == "all":
subprocess.run(["python", "scripts/clean_data.py"])
subprocess.run(["python", "scripts/visualize_students.py"])
subprocess.run(["python", "scripts/generate_report.py"])
else:
print("❗ 请输入有效的步骤参数:clean / visualize / report / all")
运行方式:
python scripts/main.py --step all
🧪 今日练习任务
- 封装你的所有分析脚本到
scripts/
文件夹中 - 将
generate_report.py
的报告输出路径与图表目录一致管理 - 在命令行运行
main.py --step all
一键完成数据分析 → 可视化 → 报告输出
代码输出:
clean_data.py
# clean_data.py placeholder script import pandas as pd import os def clean_data(): # 原始数据路径(假设为 data/raw_students.csv) raw_path = "./data/students_dirty.csv" cleaned_path = "./data/students_cleaned.csv" # 检查文件是否存在 if not os.path.exists(raw_path): raise FileNotFoundError(f"❌ 找不到原始数据文件:{raw_path}") # 读取原始数据 df = pd.read_csv(raw_path) print("✅ 已加载原始数据:") print(df.head()) # ---------------- 清洗步骤 ---------------- # 1. 删除空行或全部为 NaN 的行 df.dropna(how="all", inplace=True) # 2. 填充缺失值(如成绩) df["成绩"] = pd.to_numeric(df["成绩"], errors="coerce") # 转换为数字 df.fillna({"成绩": df["成绩"].mean()}, inplace=True) # 填充姓名和性别的缺失值 # 3. 创建新列:是否及格 df["是否及格"] = df["成绩"] >= 60 # 4. 去除重复值(如姓名+性别+成绩完全重复) df.drop_duplicates(subset=["姓名", "性别", "成绩"], inplace=True) # 5. 标准化性别字段 df["性别"] = df["性别"].str.strip().replace({"男生": "男", "女生": "女"}) # 6. 按姓名升序排序 df.sort_values(by="姓名", inplace=True) # 7. 重置索引 df.reset_index(drop=True, inplace=True) # ---------------- 保存清洗后的数据 ---------------- os.makedirs("data", exist_ok=True) df.to_csv(cleaned_path, index=False, encoding="utf-8") print(f"\n✅ 清洗完成,保存至:{cleaned_path}") print(f"✅ 数据清理完成,已保存到 {cleaned_path}") if __name__ == "__main__": clean_data()
filter_and_sort.py
# filter_and_sort.py placeholder script import pandas as pd import os def filter_and_sort(): # 输入路径 input_path = "./data/students_cleaned.csv" # 检查数据文件是否存在 if not os.path.exists(input_path): raise FileNotFoundError("❌ 找不到 students_cleaned.csv,请先运行 clean_data.py") # 加载清洗后的数据 df = pd.read_csv(input_path) print("✅ 已加载数据:") print(df.head()) # ------------------- 筛选操作 ------------------- # 1. 筛选:成绩大于等于 80 的学生 print("\n🎯 成绩 >= 80 的学生:") high_scores = df[df["成绩"] >= 80] print(high_scores) # 2. 筛选:未及格的学生(是否及格为 False) print("\n🚨 未及格的学生:") failed_students = df[df["是否及格"] == False] print(failed_students) # 3. 筛选:性别为“女”且成绩大于 85 print("\n👩 女生中成绩 > 85 的学生:") excellent_girls = df[(df["性别"] == "女") & (df["成绩"] > 85)] print(excellent_girls) # ------------------- 排序操作 ------------------- # 4. 成绩从高到低排序 print("\n🏆 学生成绩从高到低排序:") sorted_scores = df.sort_values(by="成绩", ascending=False) print(sorted_scores) # 5. 多列排序:先按是否及格,再按成绩降序 print("\n🔍 先按是否及格,再按成绩排序:") multi_sorted = df.sort_values(by=["是否及格", "成绩"], ascending=[False, False]) print(multi_sorted) # (可选)保存筛选结果 os.makedirs("./data", exist_ok=True) high_scores.to_csv("./data/high_scores.csv", index=False) failed_students.to_csv("./data/failed_students.csv", index=False) print("\n✅ 高分/不及格学生已分别保存至 data/ 目录下。") if __name__ == "__main__": filter_and_sort() print("✅ 筛选和排序操作完成!")
generate_report.py
# generate_report.py placeholder script import pandas as pd import os def generate_report(): # 路径配置 input_path = "./data/students_cleaned.csv" charts_dir = "./charts" report_path = os.path.join(charts_dir, "学生成绩可视化报告.md") # 检查必要文件是否存在 if not os.path.exists(input_path): raise FileNotFoundError("❌ 缺少清洗后的数据文件:students_cleaned.csv") if not os.path.exists(charts_dir): raise FileNotFoundError("❌ 图表目录不存在,请先运行 visualize_students.py") # 加载数据 df = pd.read_csv(input_path) # 数据统计 total = len(df) avg_score = df["成绩"].mean() max_score = df["成绩"].max() min_score = df["成绩"].min() pass_rate = df["是否及格"].mean() * 100 score_diff = df.groupby("性别")["成绩"].mean().diff().abs().values[-1] # Markdown 报告模板 report_md = f"""# 📝 学生成绩数据分析与可视化报告 ## 1. 数据概况 - 总人数:**{total} 人** - 平均成绩:**{avg_score:.2f} 分** - 成绩范围:**{min_score} - {max_score} 分** - 及格率:**{pass_rate:.1f}%** - 男女生成绩差异约:**{score_diff:.2f} 分** --- ## 2. 成绩趋势折线图  ## 3. 成绩柱状图  ## 4. 性别平均成绩柱状图  ## 5. 成绩分布直方图  ## 6. 成绩箱线图(按性别)  --- ## 7. 分析结论与建议 - 成绩整体集中在 **XX ~ XX** 分之间(可从直方图查看) - 存在少量低分/高分的异常值(见箱线图) - 性别差异不明显,但女生略高 / 男生略高 - 建议关注及格率波动、低分段学生的提升空间 --- *由 Python + Pandas + Matplotlib + Seaborn 自动生成* ✅ """ # 保存报告 with open(report_path, "w", encoding="utf-8") as f: f.write(report_md) print(f"✅ 分析报告已生成:{report_path}") if __name__ == "__main__": generate_report() print("✅ 学生成绩分析报告生成完毕!请查看 charts/ 目录下的学生成绩可视化报告.md") print("🎉 感谢使用学生成绩分析工具!")
groupby_agg_analysis.py
# groupby_agg_analysis.py placeholder script import pandas as pd import os def groupby_agg_analysis(): # 数据路径 input_path = "./data/students_cleaned.csv" # 检查数据文件是否存在 if not os.path.exists(input_path): raise FileNotFoundError("❌ 缺少 students_cleaned.csv,请先运行 clean_data.py") # 读取数据 df = pd.read_csv(input_path) print("✅ 已加载学生数据:") print(df.head()) # ------------------- 一、按性别分组统计 ------------------- print("\n👥 按性别分组的成绩统计:") gender_stats = df.groupby("性别")["成绩"].agg(["count", "mean", "max", "min"]).rename( columns={"count": "人数", "mean": "平均成绩", "max": "最高分", "min": "最低分"} ) print(gender_stats) # ------------------- 二、按是否及格统计人数 ------------------- print("\n🎯 统计及格 / 不及格人数:") pass_count = df["是否及格"].value_counts().rename(index={True: "及格", False: "不及格"}) print(pass_count) # ------------------- 三、性别 + 是否及格的交叉分析 ------------------- print("\n📊 性别与是否及格交叉表:") cross_tab = pd.crosstab(df["性别"], df["是否及格"]) print(cross_tab) # ------------------- 四、按性别分组后计算及格率 ------------------- print("\n✅ 按性别分组的及格率(%):") pass_rate_by_gender = ( df.groupby("性别")["是否及格"] .mean() .multiply(100) .round(2) .rename("及格率") ) print(pass_rate_by_gender) # (可选)保存分析结果 os.makedirs("./data", exist_ok=True) gender_stats.to_csv("./data/gender_score_summary.csv") cross_tab.to_csv("./data/crosstab_gender_pass.csv") pass_rate_by_gender.to_csv("./data/pass_rate_by_gender.csv") print("\n📁 分组聚合分析结果已保存至 data/ 目录。") if __name__ == "__main__": groupby_agg_analysis() print("✅ 分组聚合分析完成!")
visualize_students.py
# visualize_students.py placeholder script import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import os def visualize_students(): # 中文字体适配(根据系统配置可选) plt.rcParams['font.family'] = 'Arial Unicode MS' # macOS # plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows 中文支持 plt.rcParams['axes.unicode_minus'] = False # 路径配置 input_path = "./data/students_cleaned.csv" output_dir = "./charts" os.makedirs(output_dir, exist_ok=True) # 加载数据 df = pd.read_csv(input_path) print("✅ 已加载数据:") print(df.head()) # ================= 图表 1:成绩折线图 ================= plt.figure(figsize=(8, 5)) plt.plot(df["姓名"], df["成绩"], marker='o') plt.title("学生成绩折线图") plt.xlabel("姓名") plt.ylabel("成绩") plt.grid(True) plt.xticks(rotation=45) plt.tight_layout() plt.savefig(f"{output_dir}/成绩折线图.png") plt.close() # ================= 图表 2:成绩柱状图 ================= plt.figure(figsize=(8, 5)) plt.bar(df["姓名"], df["成绩"], color="skyblue") plt.title("学生成绩柱状图") plt.xlabel("姓名") plt.ylabel("成绩") plt.xticks(rotation=45) plt.tight_layout() plt.savefig(f"{output_dir}/成绩柱状图.png") plt.close() # ================= 图表 3:性别平均成绩柱状图 ================= plt.figure(figsize=(6, 4)) sns.barplot(data=df, x="性别", y="成绩", estimator="mean", palette="Set2") plt.title("性别平均成绩柱状图") plt.tight_layout() plt.savefig(f"{output_dir}/性别平均成绩柱状图.png") plt.close() # ================= 图表 4:成绩分布直方图 ================= plt.figure(figsize=(6, 4)) sns.histplot(df["成绩"], bins=5, kde=True, color="orange") plt.title("成绩分布直方图") plt.xlabel("成绩") plt.tight_layout() plt.savefig(f"{output_dir}/成绩分布直方图.png") plt.close() # ================= 图表 5:成绩箱线图(按性别) ================= plt.figure(figsize=(6, 4)) sns.boxplot(data=df, x="性别", y="成绩", palette="Pastel1") plt.title("成绩箱线图(按性别)") plt.tight_layout() plt.savefig(f"{output_dir}/成绩箱线图_按性别.png") plt.close() print(f"\n✅ 所有图表已保存至:{output_dir}/") if __name__ == "__main__": visualize_students() print("✅ 学生成绩数据可视化完成!")
🧾 今日总结
- 对第5周内容完成了全面回顾与实战项目封装
- 掌握了命令行工具脚本的基本设计方式
- 具备将数据分析过程“自动化、可配置、可交付”的能力