← 返回
未分类

data-mining-course

Teaching-oriented data mining workflow: environment setup, data inspection, cleaning, EDA, visualization, and modeling. Invoke when student or teacher asks to analyze a dataset, perform data mining tasks, or learn data analysis step by step.
本 Skill 为《数据挖掘与商务智能》课程设计,每一步都标注了对应知识点和解析,知其然还有知其所以然,让学生在操作中学习。Teaching-oriented data mining workflow: environment setup, data inspection, cleaning, EDA, visualization, and modeling. Invoke when student or teacher asks to analyze a dataset, perform data mining tasks, or learn data analysis step by step.
user_072425e5
未分类 community v1.0.0 1 版本 99259.3 Key: 无需
★ 1
Stars
📥 114
下载
💾 0
安装
1
版本
#latest

概述

数据挖掘教学 Skill(NUFE)

本 Skill 为《数据挖掘与商务智能》课程设计,每一步都标注了对应知识点,让学生在操作中学习。


⚠️ 核心执行规范(教师/Agent 必须遵守)

  1. 逐节执行,不可跳跃:每节(§1→§2→…→§9)必须独立执行,执行完一节后暂停,先讲解该节的「💡 教学要点」,再进入下一节。禁止把多节合并成一个脚本。
  2. 知识点先于代码结果:每节的输出中,先呈现「教学要点」(概念、原理、方法选择原因),再呈现数据结果。数据特征是素材,不是知识本身。
  3. 每步讲清楚「为什么」:学生不仅要看到「怎么跑代码」,更要理解「为什么用这个方法」「为什么不选那个方法」。
  4. 数据分析方法须写入报告:§9 生成的 HTML 报告中,必须包含所用分析方法的简要介绍及选择理由。

教学知识点地图

| 步骤 | 知识点 | 对应章节 |

| :--: | -------------------------------------------------------- | :------: |

| 1 | Python 环境管理 — pip、虚拟环境、清华源镜像 | §1 |

| 2 | 数据加载 — CSV/Excel 读取、编码问题、BOM 头 | §2 |

| 3 | 数据探查 — 形状、数据类型、缺失值、描述性统计 | §3 |

| 4 | 数据清洗 — 缺失值/重复值/异常值处理、类型转换 | §4 |

| 5 | 探索性分析(EDA) — 分布、相关性、分组聚合 | §5 |

| 6 | 特征工程 — 派生变量、编码、标准化 | §6 |

| 7 | 可视化 — matplotlib/seaborn 基础图表 | §7 |

| 8 | 建模入门 — 回归/分类/聚类基础 | §8 |

| 9 | 结果解释 — 如何读懂输出、撰写结论 | §9 |


§1 环境检查(知识点:Python 环境管理)

教学目标

学生需要理解:跑代码之前先检查环境,避免"代码没问题但跑不起来"。

执行流程

# 1. 检查 Python 版本(≥ 3.11 即可)
python --version

# 2. 检查核心包是否已安装(;分隔,PowerShell 兼容)
python -c "import pandas; print('pandas', pandas.__version__)"
python -c "import matplotlib; print('matplotlib', matplotlib.__version__)"
python -c "import seaborn; print('seaborn', seaborn.__version__)"
python -c "import scipy; print('scipy', scipy.__version__)"
python -c "import sklearn; print('sklearn', sklearn.__version__)"

缺失包安装(清华源)

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas matplotlib seaborn scipy scikit-learn

💡 教学要点

  • 为什么用清华源? 默认 PyPI 在国外,下载慢且容易断
  • 为什么分步检查? 只有缺的才装,节约时间
  • PowerShell 不支持 &&,所以用分步执行
  • pip:Python 官方 包管理工具 ,专门用来安装 / 卸载 / 更新第三方库,是环境配置的核心命令
  • 执行 python --version提示不是内部命令:Python 未添加系统环境变量,重新安装 Python 时勾选「Add Python to PATH」
  • 库pandas 数据处理核心工具:负责读取 Excel/CSV 数据、处理缺失值、筛选数据(数据预处理模块)
  • matplotlib 基础绘图工具:绘制柱状图、折线图、散点图,做数据可视化展示
  • seaborn 高级可视化工具:比 matplotlib 更美观,用于绘制聚类热力图、相关性图等
  • scipy 科学计算工具:支撑算法运行,处理数值计算、统计分析任务
  • scikit-learn(sklearn) 机器学习核心库:实现分类、聚类、关联规则等所有数据挖掘算法

§2 数据加载(知识点:文件 I/O 与编码)

教学目标

理解编码(UTF-8 vs GBK)对中文数据处理的影响,学会用 pathlib 管理路径。

代码模板

# -*- coding: utf-8 -*-
import sys
sys.stdout.reconfigure(encoding='utf-8')

from pathlib import Path
import pandas as pd

# 使用 pathlib 管理路径(禁止硬编码绝对路径)
data_dir = Path('data')   # 数据放在项目 data/ 目录下
file_path = data_dir / 'your_file.csv'

# 尝试不同编码读取
try:
    df = pd.read_csv(file_path, encoding='utf-8')
except UnicodeDecodeError:
    df = pd.read_csv(file_path, encoding='gbk')

print(f'数据加载完成: {df.shape[0]} 行 × {df.shape[1]} 列')

💡 教学要点

  • UTF-8 vs GBK:UTF-8 是国际标准,GBK 是中文 Windows 默认编码
  • BOM 头:某些 Windows 软件导出的 CSV 带 \ufeff,表现为  出现在第一列列名前
  • pathlib.Path:比字符串拼接更安全,跨平台兼容

§3 数据探查(知识点:认识你的数据)

教学目标

"先看数据,再动手" — 这是数据挖掘的第一铁律。

标准探查清单

# ===== 基本结构 =====
print('数据形状:', df.shape)
print('列名列表:', df.columns.tolist())

# ===== 数据类型 =====
print('\n各列数据类型:')
print(df.dtypes)

# ===== 缺失值 =====
print('\n缺失值统计:')
missing = df.isnull().sum()
missing_pct = (missing / len(df) * 100).round(2)
missing_df = pd.DataFrame({'缺失数': missing, '缺失率%': missing_pct})
print(missing_df[missing_df['缺失数'] > 0])

# ===== 重复值 =====
print(f'\n重复行数: {df.duplicated().sum()}')

# ===== 数值列统计 =====
print('\n数值列描述性统计:')
print(df.describe())

# ===== 分类列统计 =====
for col in df.select_dtypes(include='object').columns:
    print(f'\n{col} 唯一值数: {df[col].nunique()}, 示例: {df[col].dropna().unique()[:5]}')

💡 教学要点

  • describe():count(非空数)、mean(均值)、std(标准差)、min、25%、50%(中位数)、75%、max
  • 缺失率 > 50% 的列一般直接删除
  • 重复行 可能是数据采集 bug,需要溯源

§4 数据清洗(知识点:数据质量是分析的基石)

教学目标

理解 Garbage In, Garbage Out — 脏数据产出的分析结论不可信。

清洗流程

clean = df.copy()  # 保留原始数据

# ---- 4.1 列名规范化 ----
clean.columns = clean.columns.str.strip()  # 去除首尾空格

# ---- 4.2 类型转换 ----
# 日期列
clean['日期列'] = pd.to_datetime(clean['日期列'], errors='coerce')

# 数值列(如果是字符串格式,如 "1,234")
clean['数值列'] = clean['数值列'].str.replace(',', '').astype(float)

# 科学计数法 ID(如 1.1582E+14)
clean['id'] = clean['id'].astype(str)

# ---- 4.3 缺失值处理 ----
# 策略 A:删除缺失率过高的行
clean = clean.dropna(subset=['关键列'])

# 策略 B:填充(均值/中位数/众数/固定值)
clean['数字列'] = clean['数字列'].fillna(clean['数字列'].median())
clean['分类列'] = clean['分类列'].fillna('未知')

# 策略 C:前向/后向填充(时间序列)
clean['数值列'] = clean['数值列'].ffill()

# ---- 4.4 重复值 ----
clean = clean.drop_duplicates()

# ---- 4.5 异常值 ----
# IQR 方法:Q1 - 1.5*IQR ~ Q3 + 1.5*IQR 之外为异常
Q1 = clean['数值列'].quantile(0.25)
Q3 = clean['数值列'].quantile(0.75)
IQR = Q3 - Q1
outliers = clean[(clean['数值列'] < Q1 - 1.5*IQR) | (clean['数值列'] > Q3 + 1.5*IQR)]
print(f'检测到 {len(outliers)} 个异常值')

print(f'清洗完成: {len(clean)} 行 (原始 {len(df)} 行)')

💡 教学要点

为什么必须做数据清洗?

真实世界的数据永远有瑕疵 —— 传感器故障、人工录入错误、系统迁移丢失数据。跳过清洗直接建模,等同于用发霉的食材做菜:Garbage In, Garbage Out。清洗不是"可选的优化步骤",而是分析的前提条件

缺失值处理:三种策略的选择逻辑

| 缺失机制 | 定义 | 例子 | 推荐处理 |

| ------------------------------ | ------------------------------------------ | -------------------------------------------- | --------------------------------------- |

| MCAR(完全随机缺失) | 缺失与任何变量无关 | 问卷随机漏填 | 可删除,不会引入偏差 |

| MAR(随机缺失) | 缺失与其他变量有关,但与被缺失变量本身无关 | 高收入者更不愿填收入,但缺失只与收入高低无关 | 用均值/中位数/回归填充 |

| MNAR(非随机缺失) | 缺失与被缺失变量本身有关 | 收入越高越不愿填收入 | 需专门建模处理,单纯删除/填充会引入偏差 |

  • 为什么有异常值时用中位数不用均值? 假设 10 个员工月薪 [5k, 6k, 6k, 6.5k, 7k, 7k, 7k, 8k, 8k, 50k(老板)],均值=11.2k,中位数=7k。用均值填充会把所有人的薪资"拉高",这就是异常值的破坏力。中位数对极值不敏感,是更稳健的选择。
  • IQR 为什么比 "均值±3σ" 好? "均值±3σ" 假设数据是正态分布 —— 而真实数据几乎从不正态。IQR 基于四分位数排序,不做分布假设,对偏态数据、长尾数据都能用。

类型转换的坑

  • 日期列:Excel 中的日期底层是数字(从 1900-01-01 开始的天数),pd.to_datetime(..., errors='coerce')coerce 的意思是"转不了的变成 NaT(Not a Time)",不要报错中断程序。
  • 科学计数法1.1582E+14 是浮点数,但在 Excel 中这是 ID(如 B站视频 ID),必须转成字符串 astype(str),否则后续匹配会失败。

§5 探索性数据分析 EDA(知识点:从数据中发现故事)

教学目标

学会用统计指标和分组聚合来发现数据中的规律。

EDA 第一准则:先识别 5 类商科常用数据类型,再匹配对应分析方法

先分型,再分析:掌握 5 类商科常用数据的分型规则,杜绝分析方法混用:

  • 学会针对数值、分类、文本、统计年鉴、面板数据,匹配对应的 EDA 分析方法
  • 结合商科/统计场景,将统计结果转化为可理解的业务/宏观结论(如统计年鉴的地区差异、面板数据的时间趋势)
  • 掌握基础文本数据、面板数据的 EDA 实操,适配课程后续实验与实训

> ⚠️ 以下代码为一个完整脚本,直接复制运行即可,无需手动改列名。

# -*- coding: utf-8 -*-
# ============================================================
# §5 探索性数据分析 EDA — 5 类商科数据分型分析
# ============================================================

# ---- 第一步:自动识别数据类型(零介入,适配所有数据) ----
print("=" * 60)
print("第一步:数据集字段类型一览")
print("=" * 60)
print(clean.dtypes)
print()

# 自动筛选各类字段(学生无需手动修改列名)
numeric_cols = clean.select_dtypes(include=['int64', 'float64']).columns.tolist()
category_cols = clean.select_dtypes(include=['object', 'category']).columns.tolist()
time_cols = clean.select_dtypes(include=['datetime64']).columns.tolist()

# 文本型:从分类列中筛选唯一值多的列(>10种 → 更像文本而非分类)
text_cols = []
for col in category_cols:
    try:
        if clean[col].astype(str).nunique() > 10:
            text_cols.append(col)
    except Exception:
        pass

print(f"✅ 数值型字段({len(numeric_cols)} 个):{numeric_cols}")
print(f"✅ 分类型字段({len(category_cols)} 个):{category_cols}")
print(f"✅ 文本型字段({len(text_cols)} 个):{text_cols if text_cols else '无'}")
print(f"✅ 时间型字段({len(time_cols)} 个):{time_cols if time_cols else '无'}")
print()

# ---- 一、数值型:描述性统计 + 汇总(统计年鉴核心) ----
# 知识点:均值、中位数、偏度、标准差、极值
print("=" * 60)
print("一、数值型分析 — 描述性统计(适配统计年鉴/面板数值)")
print("=" * 60)
if numeric_cols:
    # 逐列展示关键指标(限前10列防止输出过长)
    for col in numeric_cols[:10]:
        vals = clean[col].dropna()
        if len(vals) > 0:
            print(f"  {col}: 均值={vals.mean():.2f}, 中位数={vals.median():.2f}, "
                  f"偏度={vals.skew():.2f}, 标准差={vals.std():.2f}, "
                  f"min={vals.min():.2f}, max={vals.max():.2f}")

    # 汇总统计表(宏观数据专属:GDP/营收等多指标汇总)
    print("\n  数值型指标汇总表(count/mean/sum/std):")
    print(clean[numeric_cols].agg(['count', 'mean', 'sum', 'std']).round(2))
else:
    print("  ⚠️ 未检测到数值型字段,跳过数值分析")

# ---- 二、数值型:相关性矩阵(统计年鉴常用) ----
# 知识点:皮尔逊相关系数、变量关联关系
print()
print("=" * 60)
print("二、相关性分析 — 高相关变量筛选 (|r| > 0.5)")
print("=" * 60)
if len(numeric_cols) >= 2:
    corr_matrix = clean[numeric_cols].corr()
    high_corr = corr_matrix.where(abs(corr_matrix) > 0.5).stack().dropna()
    found = False
    for (c1, c2), val in high_corr.items():
        if c1 < c2:
            direction = "正" if val > 0 else "负"
            print(f"  {c1} ↔ {c2}: r = {val:.3f}({direction}相关)")
            found = True
    if not found:
        print("  未发现 |r| > 0.5 的变量对")
else:
    print("  ⚠️ 数值型字段不足 2 个,无法计算相关性")

# ---- 三、分类型:分组聚合(等价 SQL GROUP BY) ----
# 知识点:分组统计、多维指标汇总
print()
print("=" * 60)
print("三、分类型分析 — 分组聚合统计(适配面板分类/统计年鉴分类)")
print("=" * 60)
if category_cols and numeric_cols:
    group_col = category_cols[0]
    num1 = numeric_cols[0]
    num2 = numeric_cols[1] if len(numeric_cols) >= 2 else numeric_cols[0]

    group_stats = clean.groupby(group_col).agg(
        计数=(num1, 'count'),
        均值=(num1, 'mean'),
        中位数=(num1, 'median'),
        总和=(num2, 'sum'),
        标准差=(num2, 'std')
    ).round(2)
    print(f"  按「{group_col}」分组统计:")
    print(group_stats)
else:
    print("  ⚠️ 缺少分类列或数值列,无法分组")

# ---- 四、数值型:Top N 排名(统计年鉴/面板常用) ----
# 知识点:数据排序、极值分析
print()
print("=" * 60)
print("四、Top N 排名分析 — 极值发现")
print("=" * 60)
if numeric_cols:
    top_col = numeric_cols[0]
    key_col = category_cols[0] if category_cols else numeric_cols[0]
    top10 = clean.nlargest(10, top_col)[[key_col, top_col]]
    print(f"  「{top_col}」Top 10:")
    print(top10.to_string(index=False))
else:
    print("  ⚠️ 缺少数值列,无法生成 Top N")

# ---- 五、文本型:基础频数分析(统计年鉴文本/调研文本) ----
# 知识点:文本分类统计、关键词频次(零基础入门)
print()
print("=" * 60)
print("五、文本数据分析 — 内容频次统计(适配统计年鉴说明/调研文本)")
print("=" * 60)
if text_cols:
    text_col = text_cols[0]
    text_freq = clean[text_col].astype(str).value_counts().head(10)
    print(f"  「{text_col}」文本内容 Top 10 频次:")
    print(text_freq)
else:
    print("  ⚠️ 未检测到文本型字段,跳过文本分析")

# ---- 六、面板数据:时间趋势分析 ----
# 知识点:时间序列基础、多主体趋势对比(面板数据核心)
print()
print("=" * 60)
print("六、面板数据 — 时间趋势分析(多主体 × 多时间点)")
print("=" * 60)
if time_cols and numeric_cols and category_cols:
    time_col = time_cols[0]
    num_col = numeric_cols[0]
    entity_col = category_cols[0]

    panel_trend = clean.groupby([time_col, entity_col])[num_col].mean().unstack().round(2)
    print(f"  「{entity_col}」在「{num_col}」上的时间趋势:")
    print(panel_trend)
else:
    print("  ⚠️ 缺少时间列/数值列/分类列,无法进行面板数据趋势分析")

print()
print("✅ §5 EDA 分析完成")

💡 教学要点

EDA 的本质:生成假设,不是验证结论

EDA 的目标不是"找到正确答案",而是发现值得深挖的方向。两者有本质区别:

  • 验证性分析(Confirmatory):先有假设("学历高的薪资一定高"),再用数据检验 → 统计学检验(t 检验、ANOVA)
  • 探索性分析(Exploratory):先看数据、再形成假设("咦,城市之间薪资差异好大,是什么原因?") → EDA

学生常犯的错误:把 EDA 中发现的模式直接当结论 —— 比如看到"大城市薪资高"就说"去大城市就能高薪",忽略了选择偏差(大城市本身就吸引了更多高学历人才)。EDA 是起点,不是终点

为什么必须先分数据类型再分析?

| 数据类型 | 用错方法的后果 |

| -------------------- | ----------------------------------------------------------- |

| 把分类列当数值列 | df['城市'].mean() → 报错,或算出无意义的"平均城市编码" |

| 把数值列当分类列 | 每个不同的数值被当成一个类别,导致分组数 = 样本数 |

| 把文本列当分类列 | 每行一个独特值,分组无意义 |

| 把截面数据当面板数据 | 错误地强加"时间趋势"解释,得出虚假结论 |

这就是本 Skill 在 EDA 开头强制执行 clean.dtypes 的原因 —— 先知道每一列是什么类型,才能决定用什么分析方法。

为什么用偏度而不是只看均值和中位数?

| 指标 | 告诉你的信息 | 局限 |

| -------------- | ------------------ | ------------------------- |

| 均值 | 整体水平 | 被极值严重拉偏 |

| 中位数 | "中间那个人"的水平 | 无法反映极端情况 |

| 偏度 | 数据往哪边"甩尾" | 需要配合均值/中位数一起看 |

薪资数据几乎总是正偏(右偏):大多数人拿中等薪资,少数人拿极高薪资。如果只汇报"平均薪资",学生会高估自己的收入预期。汇报中位数比汇报均值更接近"普通人能拿到的"

为什么用皮尔逊相关系数而不是随便"看图说话"?

相关系数把"这两个变量看起来有关系"变成了一个可比较的数字。但要注意:

  • |r| > 0.7 → 强相关,但不等于因果关系。人均巧克力消费量与诺奖得主数量 r≈0.8,但吃巧克力不会让你得诺奖 —— 混淆变量是国民富裕度。
  • r 只能衡量线性关系,如果你的数据是 U 型曲线(如年龄-消费能力),r 可能接近 0 但实际关系很强。
  • 零相关不等于没关系

分组聚合与 5 类数据分型规则(核心口诀)

  • 分组聚合:SQL 的 GROUP BY 在 pandas 中的等价操作 —— 本质是将数据按某一列的类别切分,然后对每个切块做统计运算(计数、求和、均值等)

| 数据类型 | 能做什么 | 不能做什么 |

| ---------------------- | ------------------------------- | ---------------------- |

| 数值型(统计年鉴核心) | 均值、相关、TopN、趋势 | 不能用来分组分类 |

| 分类型(面板分类项) | 分组、计数 | 不能计算均值、相关系数 |

| 文本型(统计年鉴文本) | 频数统计(入门阶段) | 不做复杂文本挖掘 |

| 统计年鉴数据 | 汇总统计、地区/行业对比、相关性 | 照搬数值/分类代码 |

| 面板数据 | 时间趋势 + 主体对比 | 忽略时间维度单独分析 |

  • 关键准则:所有 EDA 操作必须先看数据类型(df.dtypes),再选择对应代码

§6 特征工程(知识点:让模型更好地理解数据)

教学目标

理解"同样的数据,不同的表达方式会直接影响模型效果"。

# ---- 6.1 时间特征提取 ----
clean['年'] = clean['日期列'].dt.year
clean['月'] = clean['日期列'].dt.month
clean['星期'] = clean['日期列'].dt.dayofweek      # 0=周一
clean['是否周末'] = (clean['星期'] >= 5).astype(int)
clean['季度'] = clean['日期列'].dt.quarter

# ---- 6.2 文本特征 —— 提取关键词/长度 ----
clean['文本长度'] = clean['文本列'].str.len()
clean['含关键词'] = clean['文本列'].str.contains('关键词', na=False).astype(int)

# ---- 6.3 比率特征 —— 两个数值列的比例 ----
clean['比率'] = (clean['分子列'] / clean['分母列']).replace([float('inf'), -float('inf')], 0)
# ⚠️ 除以零会产生 inf,必须处理

# ---- 6.4 分类变量编码 ----
# One-Hot 编码(适用于无序分类,如颜色:红/蓝/绿)
df_encoded = pd.get_dummies(clean, columns=['分类列'], prefix='cat')

# Label 编码(适用于有序分类,如等级:低/中/高)
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
clean['等级_编码'] = le.fit_transform(clean['等级列'])

💡 教学要点

特征工程的本质:让数据说模型能懂的语言

模型的数学引擎只能处理数字矩阵。而原始数据往往是混杂的 —— 日期、文本、类别、缺失值。特征工程就是把"杂乱的原始数据"翻译成"模型能理解的数值特征"。同样的原始数据,不同的特征表达方式,模型效果可以差 3~5 倍

为什么需要时间特征拆分?

模型不认识"2022-03-15"这个字符串,也不理解"12月之后是1月"的周期性。把日期拆成年/月/星期/季度这些独立数字后,模型就能捕捉到季节性规律(如冬季招聘职位少)。

  • 进阶:如果用 sin(月份/122π)cos(月份/122π) 编码月份,就能完美体现"12月和1月距离近"的周期关系。但课程入门阶段用拆分即可。

One-Hot 编码 vs Label 编码:选错会让模型"学错"

| 编码方式 | 原理 | 什么时候用 | 用错的后果 |

| ----------------- | ------------------------- | ---------------------------- | --------------------------------------------- |

| One-Hot | 每个类别变成一列 0/1 | 无序分类(城市、颜色、品牌) | 如果类别太多(>100种),特征矩阵爆炸 |

| Label | 把类别替换成数字 1,2,3... | 有序分类(学历、等级、星级) | 用在无序分类上,模型会以为"上海=3" > "北京=1" |

标准化 vs 归一化:不是一回事

  • 标准化(StandardScaler):使数据均值为 0、标准差为 1。保留原始分布形状。
  • 归一化(MinMaxScaler):将数据缩放到 [0, 1]。对异常值敏感。
  • 树模型(随机森林、XGBoost)不需要标准化/归一化 —— 它们只关心分裂点的相对大小,不关心绝对数值。
  • 距离类模型(KNN、K-Means、SVM、神经网络)必须标准化 —— 否则"薪资(单位万)"和"年龄(单位年)"的巨大量纲差异会让距离计算完全被薪资主导。

§7 可视化(知识点:一张好图胜过千言万语)

教学目标

掌握数据挖掘中最常用的 6 种图表及各自适用场景。

import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
matplotlib.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
import seaborn as sns

fig, axes = plt.subplots(2, 3, figsize=(18, 12))

# ① 直方图 — 看分布形态
axes[0, 0].hist(clean['数值列'], bins=30, color='#3498db', edgecolor='white')
axes[0, 0].set_title('数值列分布直方图')
axes[0, 0].set_xlabel('值')
axes[0, 0].set_ylabel('频数')

# ② 箱线图 — 看异常值与四分位数
sns.boxplot(data=clean, x='分类列', y='数值列', ax=axes[0, 1])
axes[0, 1].set_title('各类别的数值分布(箱线图)')

# ③ 柱状图 — 类别间比较
cat_counts = clean['分类列'].value_counts().head(10)
axes[0, 2].bar(range(len(cat_counts)), cat_counts.values, color='#e74c3c')
axes[0, 2].set_xticks(range(len(cat_counts)))
axes[0, 2].set_xticklabels(cat_counts.index, rotation=45, ha='right')
axes[0, 2].set_title('分类列 Top 10 频数')

# ④ 散点图 — 两变量关系
axes[1, 0].scatter(clean['数值列1'], clean['数值列2'], alpha=0.5, s=20)
axes[1, 0].set_xlabel('数值列1')
axes[1, 0].set_ylabel('数值列2')
axes[1, 0].set_title('数值列1 vs 数值列2')

# ⑤ 折线图 — 时间趋势
time_data = clean.groupby('日期列')['数值列'].mean()
axes[1, 1].plot(time_data.index, time_data.values, color='#2ecc71')
axes[1, 1].set_title('时间趋势')
axes[1, 1].tick_params(axis='x', rotation=45)

# ⑥ 热力图 — 相关性矩阵
num_cols = clean.select_dtypes(include='number').columns[:8]
sns.heatmap(clean[num_cols].corr(), annot=True, fmt='.2f', cmap='RdBu_r',
            center=0, ax=axes[1, 2])
axes[1, 2].set_title('相关性热力图')

plt.tight_layout()
plt.savefig('eda_charts.png', dpi=150, bbox_inches='tight')
print('图表已保存至 eda_charts.png')

💡 教学要点 — 图表选择指南

为什么必须先可视化再做建模?

人眼是人类最强大的模式识别系统。肉眼扫一遍散点图,能瞬间发现:线性/曲线趋势、离群点、聚类结构、数据缺口。而直接丢进模型跑,这些信息全部丢失。可视化是建模前的"体检报告"

6 种图表的底层选择逻辑(视觉编码理论)

图表本质上是用视觉属性(位置、长度、颜色、形状)来编码数据特征。选错图表 = 用错视觉属性,读者无法正确解码信息:

| 想看什么 | 用什么图 | 视觉编码 | 为什么选它 | 注意 |

| --------------- | ------------ | ---------------------------- | -------------------------------------------------- | ----------------- |

| 分布形态 | 直方图 / KDE | 柱高/面积 = 频数 | 单变量、看集中与分散 | bin 数量影响观感 |

| 异常值 + 四分位 | 箱线图 | 位置 = 四分位数,须外 = 异常 | 最适合多组比较,一张图能看多组的分布差异 | 点超出须线即异常 |

| 类别比较 | 柱状图 | 柱高 = 数量 | 分类 x 数值,简单直接 | 类别太多时取 TopN |

| 两变量关系 | 散点图 | 点的 x/y 位置 | 发现相关性、聚类、离群、非线性 | 加透明度避免重叠 |

| 时间趋势 | 折线图 | 斜率 = 变化率 | 强调先后顺序和变化速率 | 时间轴要排序 |

| 相关性 | 热力图 | 颜色深浅 = 相关强弱 | 同时展示几十个变量的两两关系 | 颜色深浅 = 强弱 |

常见错误

  • 用饼图展示 10+ 个类别:饼图人眼只能区分 3-5 个扇区,多了完全看不清。用柱状图替代。
  • 用 3D 图展示 2D 数据:3D 透视变形会严重歪曲数值,学术论文中禁止。
  • 散点图不加透明度:大量重叠点会被隐藏(overplotting),加 alpha=0.5 解决。
  • 颜色用红绿配:红绿色盲人群占 8%(男性 1/12),用蓝橙/蓝红替代。

§8 建模入门(知识点:从描述走向预测)

教学目标

理解监督学习(回归/分类)和无监督学习(聚类)的基本概念。

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import r2_score, accuracy_score, classification_report

# ====== 准备特征和标签 ======
features = ['特征1', '特征2', '特征3', '特征4']
target = '目标列'

X = clean[features].copy()
y = clean[target].copy()

# 处理特征中的缺失值
X = X.fillna(X.median())

# ====== 划分训练集/测试集 (7:3) ======
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(f'训练集: {len(X_train)} 条, 测试集: {len(X_test)} 条')

# ====== 标准化 ======
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# ⚠️ fit_transform 用在训练集,transform 用在测试集

# ====== 场景 A:回归(预测连续值,如价格、播放量) ======
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor

model_reg = RandomForestRegressor(n_estimators=100, random_state=42)
model_reg.fit(X_train, y_train)
y_pred_reg = model_reg.predict(X_test)
print(f'回归 R² 分数: {r2_score(y_test, y_pred_reg):.3f}')

# 特征重要性
importance = pd.DataFrame({'特征': features, '重要性': model_reg.feature_importances_})
print(importance.sort_values('重要性', ascending=False))

# ====== 场景 B:分类(预测类别,如是/否、高/中/低)=======
from sklearn.ensemble import RandomForestClassifier

# 如果目标是连续值,需要先分箱
if y.nunique() > 10:
    y = pd.cut(y, bins=3, labels=['低', '中', '高'])

X_train_c, X_test_c, y_train_c, y_test_c = train_test_split(
    X, y, test_size=0.3, random_state=42
)
model_clf = RandomForestClassifier(n_estimators=100, random_state=42)
model_clf.fit(X_train_c, y_train_c)
y_pred_clf = model_clf.predict(X_test_c)
print(f'分类准确率: {accuracy_score(y_test_c, y_pred_clf):.3f}')
print(classification_report(y_test_c, y_pred_clf))

# ====== 场景 C:聚类(没有标签,发现自然分组)=======
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

# 肘部法则 —— 选最佳 K 值
inertias = []
K_range = range(1, 11)
for k in K_range:
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    km.fit(X_train_scaled)
    inertias.append(km.inertia_)
print(f'肘部法则 Inertia: {dict(zip(K_range, [round(i, 2) for i in inertias]))}')

# 聚类
k_best = 3
kmeans = KMeans(n_clusters=k_best, random_state=42, n_init=10)
clean['聚类标签'] = kmeans.fit_predict(X_train_scaled)
print(f'聚类轮廓系数: {silhouette_score(X_train_scaled, clean["聚类标签"]):.3f}')

💡 教学要点

机器学习三大任务:什么时候用什么?

| 任务 | 问题形式 | 典型场景 | 常用算法 | 评估指标 |

| -------------- | ---------------------- | ------------------ | ---------------------- | ---------------------- |

| 回归 | 预测一个连续数值 | 房价、薪资、销量 | 线性回归、随机森林回归 | R²(0~1,越高越好) |

| 分类 | 判断属于哪个类别 | 是否购买、信用评级 | 随机森林分类、逻辑回归 | 准确率、精确率、召回率 |

| 聚类 | 没有标签,自动发现分组 | 用户分群、岗位分层 | K-Means、DBSCAN | 轮廓系数、肘部法则 |

选择原则:如果你有 y(目标列)→ 监督学习(回归或分类);如果没有 y → 无监督学习(聚类)。

为什么必须划分训练集和测试集?

如果把所有数据都用来训练模型,模型会把数据"背下来"(包括噪声),但遇到新数据时表现极差 —— 这叫做过拟合。训练集/测试集分离的本质是模拟"期末考试":用 70% 的数据当练习题,用 30% 的未见过数据当考试题。考试分数才代表真实水平。

  • random_state=42 不是魔法数字,而是随机种子 —— 保证每次划分结果相同,实验可重复。
  • 为什么是 7:3? 经验值。数据量小时(<1000)可改用 8:2;数据量大时(>10000)可用 5:5 或交叉验证。

为什么 K-Means 聚类前需要标准化?

K-Means 的核心是欧氏距离。如果"薪资(万)"的量纲是 0.5~5,而"年龄"是 20~60,那么薪资对距离的贡献会被年龄完全淹没。StandardScaler 把所有特征拉到同一尺度上,让每个特征公平参与距离计算。

肘部法则的原理

随着 K 增大,Inertia(簇内平方和)一定会下降 —— 极端情况 K = 样本数时 Inertia = 0。肘部法则找的是"下降速度突然放缓"的转折点,即继续增加 K 带来的收益急剧减少的那个位置。

  • 轮廓系数(silhouette_score)是肘部法则的补充验证:> 0.3 尚可,> 0.5 良好,> 0.7 优秀。

§9 结果解释(知识点:得出可信结论)

教学目标

知道每个数字代表什么含义,并能用通俗语言写出来。

结论撰写模板

文档输出格式

生成一份可打印的 HTML 分析报告,排版行文按学术期刊论文格式(含标题、摘要、分析方法、图表、参考文献样式)。报告必须包含所用分析方法的简要介绍及选择理由

# 生成 HTML 报告
html_content = f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>数据分析报告</title>
    <style>
        body {{ font-family: "SimSun", "宋体", serif; max-width: 900px; margin: 0 auto; padding: 40px; }}
        h1 {{ text-align: center; font-size: 18pt; }}
        h2 {{ font-size: 14pt; border-bottom: 1px solid #333; padding-bottom: 4px; }}
        table {{ border-collapse: collapse; width: 100%; margin: 10px 0; }}
        th, td {{ border: 1px solid #666; padding: 6px 10px; font-size: 10pt; }}
        th {{ background: #f0f0f0; }}
        .abstract {{ background: #fafafa; padding: 12px 16px; border-left: 4px solid #3498db; }}
        .method {{ background: #fff8e1; padding: 10px 14px; border-left: 4px solid #f39c12; margin: 10px 0; font-size: 10pt; }}
    </style>
</head>
<body>
    <h1>{title}</h1>
    <div class="abstract"><b>摘要:</b>{abstract_text}</div>

    <h2>0. 分析方法</h2>
    <div class="method">
        {method_text}
    </div>

    <h2>1. 数据概况</h2>
    <p>{overview}</p>
    <h2>2. 核心发现</h2>
    <p>{findings}</p>
    <h2>3. 相关性分析</h2>
    <p>{correlation_text}</p>
    <h2>4. 模型效果</h2>
    <p>{model_text}</p>
    <h2>5. 局限与建议</h2>
    <p>{limitations}</p>
</body>
</html>
"""
with open(output_dir / '分析报告.html', 'w', encoding='utf-8') as f:
    f.write(html_content)
print(f'✅ HTML 报告已保存至: {output_dir / "分析报告.html"}')

分析方法介绍模板(必须填入 method_text

<b>本报告使用的分析方法及选择理由:</b>
<ul>
    <li><b>描述性统计分析</b>(均值、中位数、偏度):
        用于刻画数据集中趋势与离散程度。选择均值+中位数双指标汇报,
        因为薪资数据通常右偏,均值会被极端高薪拉高,中位数更能反映"普通人能拿到的"。</li>
    <li><b>皮尔逊相关系数</b>(|r| > 0.5 为强相关):
        用于量化变量之间的线性关联程度。选择皮尔逊而非斯皮尔曼,
        因为需要测量的是线性关系而非单调关系(如"经验增加→薪资稳步上升")。</li>
    <li><b>分组聚合分析</b>(GROUP BY 等价操作):
        用于按类别(城市/学历/行业)比较薪资差异。选择分组均值而非分组总数,
        因为目标是横向比较"哪个类别更高",而非"哪个类别岗位更多"。</li>
    <li><b>K-Means 聚类</b>(轮廓系数验证 + 肘部法则选 K):
        用于无监督地发现岗位的"自然分层"。选择 K-Means 而非 DBSCAN,
        因为我们预期的岗位分层是球状分布(高/中/低三个梯队),
        而非密度差异分布。标准化后再聚类是为了消除量纲差异。</li>
    <li><b>可视化方法</b>(直方图/箱线图/柱状图/散点图/热力图):
        每种图的选择基于视觉编码理论——用最合适的视觉属性(位置、长度、颜色)
        编码对应的数据特征,详见 §7 图表选择指南。</li>
</ul>

结论内容模板(填入 HTML 报告)

1. 数据概况:本分析使用 {len(clean)} 条数据,涵盖 {时间范围}
   的 {数据来源}。

2. 核心发现:
   - {最突出的发现1}(用数字支撑,如:A类占比xx%,是B类的x倍)
   - {最突出的发现2}(用数字支撑)
   - {最突出的发现3}(用数字支撑)

3. 相关性分析:{变量A} 与 {变量B} 的相关系数为 {r},
   表明 {强/中等/弱} {正/负}相关。

4. 模型效果:{模型名} 的 {指标名} 为 {值},
   说明模型的 {预测/分类/聚类} 能力 {优秀/良好/一般}。

5. 局限与建议:
   - 数据局限:{数据可能的问题(样本量、覆盖范围、时效性)}
   - 后续建议:{可以进一步分析的方向(加入新变量、扩展时间范围等)}

💡 教学要点

  • 结论要有数字,不能只说"A 和 B 有关系"
  • 要说明系数的大小和方向(正/负)
  • 要承认局限(样本量、数据质量、未考虑的因素)
  • HTML 报告用宋体排版,符合中文学术论文规范
  • 分析方法介绍是报告的必要组成部分 —— 读者需要知道"为什么用这个方法",才能判断结论的可信度

完整执行 Checklist(学生自查用)

  • [ ] □ Python 版本 ≥ 3.11
  • [ ] □ 必要包已安装(pandas, matplotlib, seaborn, scipy, sklearn)
  • [ ] □ 数据文件存在且可读
  • [ ] □ 已探查:形状、类型、缺失值、描述统计
  • [ ] □ 已清洗:缺失值、重复值、异常值、类型转换
  • [ ] □ 已做 EDA:分布、相关性、分组统计、Top N
  • [ ] □ 已可视化:至少包含 3 种图表
  • [ ] □ 已建模(如适用):划分训练/测试、训练、评估
  • [ ] □ 已撰写结论:有数字、有解释、有局限说明

常见错误与调试(FAQ)

| 错误 | 原因 | 解决方法 |

| --------------------------------- | ------------------ | ----------------------------------------- |

| KeyError: '列名' | 列名写错了 | 打印 df.columns 确认 |

| ValueError: could not convert | 类型不匹配 | 先 dtype 检查,用 errors='coerce' |

| 中文乱码 | 编码不对 | 试 gbkutf-8-sig |

| 图表中文显示方块 | 字体缺失 | 设 rcParams['font.sans-serif'] |

| 相关系数 NaN | 列中有 NaN | 先 dropna() 再算相关 |

| 回归 R² 为负 | 模型比"猜均值"还差 | 检查特征选择,可能需要更多特征 |

| SettingWithCopyWarning | 在切片上赋值 | 用 .copy() 创建独立副本 |

版本历史

共 1 个版本

  • v1.0.0 Initial release 当前
    2026-04-29 23:39 安全 安全

安全检测

腾讯云安全 (Keen)

安全,无风险
查看报告

腾讯云安全 (Sanbu)

安全,无风险
查看报告

🔗 相关推荐

security-compliance

Skill Vetter

spclaudehome
AI智能体技能安全预审工具。安装ClawdHub、GitHub等来源技能前,检查风险信号、权限范围及可疑模式。
★ 1,219 📥 266,817
ai-intelligence

self-improving agent

pskoett
捕获经验教训、错误和纠正,以实现持续改进。使用时机:(1)命令或操作意外失败;(2)用户纠正……
★ 4,062 📥 799,616
ai-intelligence

Self-Improving + Proactive Agent

ivangdavila
自我反思+自我批评+自我学习+自组织记忆。智能体评估自身工作、发现错误并持续改进。
★ 1,362 📥 318,989