好的,我們進入:
🧠 第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 subprocessparser = 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 osdef 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. 篩選:性別為“女”且成績大于 85print("\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 osdef 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() * 100score_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 osdef 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 scriptimport pandas as pd import matplotlib.pyplot as plt import seaborn as sns import osdef 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周內容完成了全面回顧與實戰項目封裝
- 掌握了命令行工具腳本的基本設計方式
- 具備將數據分析過程“自動化、可配置、可交付”的能力