先导内容

0. 数据分析工作流

基本流程

数据分析通常不是“直接画图”,而是:明确问题 → 获取数据 → 清洗数据 → 探索分析 → 建模或统计 → 可视化表达 → 输出结论。

业务问题
  -> 数据读取
  -> 数据质量检查
  -> 缺失值/异常值处理
  -> 特征构造
  -> 分组聚合/统计检验/建模
  -> 图表展示
  -> 可复现报告

0.1 常用库

库作用
NumPy高性能数组、数学计算、线性代数
Pandas表格数据处理、清洗、聚合、时间序列
Matplotlib基础绘图库,控制能力强
Seaborn基于 Matplotlib 的统计图表
SciPy科学计算、统计检验、优化
scikit-learn机器学习预处理、建模、评估
Jupyter交互式分析和报告

0.2 环境准备

python -m venv .venv
source .venv/bin/activate
python -m pip install numpy pandas matplotlib seaborn scipy scikit-learn jupyter

1. NumPy

介绍

NumPy(Numerical Python)是 Python 科学计算的基础库,核心是 ndarray 多维数组。它用连续内存和 C 级循环提升性能,是 Pandas、SciPy、scikit-learn 等库的底层基础之一。

1.1 NumPy 与 Python 列表对比

维度Python 列表NumPy 数组
数据类型可混合类型通常同一类型
运算方式Python 层循环向量化计算
内存布局存储对象引用连续内存块
适合场景通用容器大规模数值计算
import numpy as np
 
nums = [1, 2, 3]
arr = np.array([1, 2, 3])
 
print([x * 2 for x in nums])  # 列表需要显式循环
print(arr * 2)                # NumPy 自动逐元素计算

1.2 ndarray

核心属性

import numpy as np
 
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64)
 
print(a.ndim)    # 维度数量:2
print(a.shape)   # 每个维度的长度:(2, 3)
print(a.size)    # 元素总数:6
print(a.dtype)   # 元素类型:int64
print(a.itemsize) # 单个元素字节数

创建数组

np.array([1, 2, 3])           # 从列表创建
np.zeros((2, 3))              # 全 0 数组
np.ones((2, 3))               # 全 1 数组
np.full((2, 3), 7)            # 指定填充值
np.eye(3)                     # 单位矩阵
np.arange(0, 10, 2)           # [0, 2, 4, 6, 8]
np.linspace(0, 1, 5)          # 生成等间距数
np.random.default_rng(42).normal(size=3) # 推荐的新随机数生成器写法

常见数据类型

常见 dtype

  • 布尔:bool_
  • 整数:int8、int16、int32、int64、uint8、uint16、uint32、uint64
  • 浮点:float16、float32、float64
  • 复数:complex64、complex128
  • 字符串/对象:str_、object_
a = np.array([1, 2, 3], dtype=np.float64)
b = a.astype(np.int32)  # 类型转换会返回新数组

1.3 索引、切片和布尔筛选

切片边界

Python 和 NumPy 的切片都是左闭右开:[start:end] 包含 start,不包含 end。

a = np.array([10, 20, 30, 40, 50])
 
print(a[0])       # 10
print(a[-1])      # 50
print(a[1:4])     # [20, 30, 40]
print(a[::2])     # [10, 30, 50]
 
mask = a >= 30
print(a[mask])    # [30, 40, 50]
m = np.array([[1, 2, 3], [4, 5, 6]])
 
print(m[0, 1])    # 第 0 行第 1 列:2
print(m[:, 1])    # 取所有行的第 1 列
print(m[1, :])    # 取第 1 行所有列

1.4 广播机制

广播

广播让不同形状的数组在兼容时自动扩展维度,避免手动循环。核心规则是从右向左比较维度:相等或其中一个为 1 即兼容。

scores = np.array([[80, 90, 100], [70, 85, 95]])
bonus = np.array([1, 2, 3])
 
print(scores + bonus)
# 每一行都会加上 bonus,对列进行广播

1.5 形状变换和拼接

a = np.arange(12)
 
print(a.reshape(3, 4))    # 改为 3 行 4 列
print(a.reshape(2, -1))   # -1 表示自动推断该维度
 
x = np.array([[1, 2], [3, 4]])
y = np.array([[5, 6]])
 
np.vstack([x, y])         # 按行拼接
np.hstack([x, y.T])       # 按列拼接

1.6 常用数学和统计函数

a = np.array([1, 2, 3, 4, 5])
 
print(np.mean(a))       # 均值
print(np.median(a))     # 中位数
print(np.std(a))        # 标准差
print(np.min(a))        # 最小值
print(np.max(a))        # 最大值
print(np.argmax(a))     # 最大值索引
print(np.cumsum(a))     # 累计和
m = np.array([[1, 2, 3], [4, 5, 6]])
 
print(m.sum(axis=0))    # 按列求和
print(m.sum(axis=1))    # 按行求和

1.7 线性代数

a = np.array([[1, 2], [3, 4]])
b = np.array([[10], [20]])
 
print(a @ b)               # 矩阵乘法
print(np.linalg.det(a))    # 行列式
print(np.linalg.inv(a))    # 逆矩阵
print(np.linalg.solve(a, b)) # 解线性方程组 ax=b

1.8 性能注意点

性能建议

  • 优先使用向量化运算,避免 Python 层 for 循环。
  • 能预分配数组就不要在循环中反复 append。
  • 大数组切片通常是视图,修改切片可能影响原数组;需要独立副本时使用 .copy()。
a = np.array([1, 2, 3, 4])
b = a[1:3]      # b 是视图
b[0] = 99
print(a)        # [1, 99, 3, 4]
 
c = a[1:3].copy()
c[0] = 100
print(a)        # 原数组不再受影响

2. Pandas

概述

Pandas 是基于 NumPy 的表格数据分析库,核心数据结构是 Series 和 DataFrame。它适合处理 CSV、Excel、SQL 查询结果、日志表、业务报表和时间序列。

2.1 核心数据结构

Series

import pandas as pd
 
s = pd.Series([80, 90, 95], index=["Alice", "Bob", "Cindy"], name="score")
 
print(s["Alice"])       # 按标签访问
print(s.iloc[0])        # 按位置访问
print(s.mean())         # 均值

DataFrame

df = pd.DataFrame(
    {
        "name": ["Alice", "Bob", "Cindy"],
        "age": [20, 21, 20],
        "score": [88, 95, 76],
    }
)
 
print(df.head())        # 查看前 5 行
print(df.info())        # 查看字段、类型、缺失情况
print(df.describe())    # 数值列统计摘要

2.2 数据读取和写出

import pandas as pd
 
df = pd.read_csv("users.csv")
excel = pd.read_excel("users.xlsx", sheet_name="Sheet1")
 
df.to_csv("users_clean.csv", index=False)
df.to_excel("users_clean.xlsx", index=False)
from sqlalchemy import create_engine
 
engine = create_engine("mysql+pymysql://user:password@localhost:3306/db")
df = pd.read_sql("select id, name, score from users", engine)

2.3 选择数据

df = pd.DataFrame(
    {
        "name": ["Alice", "Bob", "Cindy"],
        "age": [20, 21, 20],
        "score": [88, 95, 76],
    }
)
 
print(df["name"])                  # 取一列,返回 Series
print(df[["name", "score"]])       # 取多列,返回 DataFrame
 
print(df.loc[0, "name"])           # 按标签定位
print(df.iloc[0, 0])               # 按位置定位
 
high = df[df["score"] >= 90]       # 布尔筛选
print(high)

2.4 数据清洗

缺失值

df = pd.DataFrame(
    {
        "name": ["Alice", "Bob", None],
        "score": [88, None, 76],
    }
)
 
print(df.isna().sum())             # 每列缺失数量
 
df1 = df.dropna()                  # 删除含缺失值的行
df2 = df.fillna({"name": "unknown", "score": 0})

重复值

df = df.drop_duplicates(subset=["name"], keep="first")

类型转换

df["score"] = pd.to_numeric(df["score"], errors="coerce")
df["created_at"] = pd.to_datetime(df["created_at"], errors="coerce")

字符串处理

df["name"] = df["name"].str.strip().str.lower()
df["domain"] = df["email"].str.extract(r"@(.+)$")

2.5 特征构造

df["passed"] = df["score"] >= 60
df["level"] = pd.cut(
    df["score"],
    bins=[0, 60, 80, 100],
    labels=["不及格", "良好", "优秀"],
    include_lowest=True,
)
 
df["score_rank"] = df["score"].rank(ascending=False, method="dense")

2.6 分组聚合

result = (
    df.groupby("age")
    .agg(
        count=("name", "count"),
        avg_score=("score", "mean"),
        max_score=("score", "max"),
    )
    .reset_index()
)
 
print(result)

2.7 排序、合并和透视表

df_sorted = df.sort_values(["score", "age"], ascending=[False, True])
orders = pd.DataFrame({"user_id": [1, 1, 2], "amount": [10, 20, 30]})
users = pd.DataFrame({"user_id": [1, 2], "name": ["Alice", "Bob"]})
 
merged = orders.merge(users, on="user_id", how="left")
pivot = pd.pivot_table(
    merged,
    values="amount",
    index="name",
    aggfunc="sum",
)

2.8 时间序列

df["created_at"] = pd.to_datetime(df["created_at"])
df = df.set_index("created_at").sort_index()
 
daily = df["score"].resample("D").mean()      # 按天重采样
rolling = df["score"].rolling(window=7).mean() # 7 日滚动均值

2.9 常见坑

Pandas 注意事项

  • 链式赋值可能导致修改未生效,优先使用 .loc[row, col] = value。
  • NaN != NaN,判断缺失要用 isna()。
  • 大文件不要一次性读入内存,可以用 chunksize 分块处理。
  • object 类型列可能混有字符串、数字和缺失值,建模前要清理类型。
# 推荐写法:明确指定行列
df.loc[df["score"] < 60, "passed"] = False

2.10 大文件处理

total = 0
for chunk in pd.read_csv("big.csv", chunksize=100_000):
    # 每次只处理一部分数据,降低内存压力
    total += chunk["amount"].sum()
 
print(total)

3. Matplotlib

概述

Matplotlib 是 Python 最基础的绘图库。复杂控制用 Matplotlib,快速统计图可用 Seaborn。

3.1 基本折线图

import matplotlib.pyplot as plt
 
x = [1, 2, 3, 4]
y = [10, 20, 25, 30]
 
plt.figure(figsize=(6, 4))
plt.plot(x, y, marker="o", label="score")
plt.title("Score Trend")
plt.xlabel("Day")
plt.ylabel("Score")
plt.legend()
plt.tight_layout()
plt.show()

3.2 常用图表

plt.bar(["A", "B", "C"], [10, 20, 15])      # 柱状图
plt.hist([1, 1, 2, 3, 3, 3], bins=3)        # 直方图
plt.scatter([1, 2, 3], [3, 2, 5])           # 散点图
plt.boxplot([[1, 2, 3], [2, 3, 10]])        # 箱线图

3.3 与 Pandas 结合

df.groupby("age")["score"].mean().plot(kind="bar")
plt.ylabel("Average Score")
plt.tight_layout()
plt.show()

4. Seaborn

import seaborn as sns
import matplotlib.pyplot as plt
 
sns.set_theme(style="whitegrid")
 
tips = sns.load_dataset("tips")
 
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time")
plt.show()
 
sns.boxplot(data=tips, x="day", y="total_bill")
plt.show()
 
sns.heatmap(tips[["total_bill", "tip", "size"]].corr(), annot=True, cmap="Blues")
plt.show()

图表选择

  • 趋势:折线图
  • 排名/对比:柱状图
  • 分布:直方图、密度图、箱线图
  • 关系:散点图、热力图
  • 组成:堆叠柱状图、饼图谨慎使用

5. 机器学习预处理

5.1 训练集和测试集

from sklearn.model_selection import train_test_split
 
X = df[["age", "study_hours"]]
y = df["passed"]
 
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y,          # 分类任务中保持标签比例
)

5.2 缺失值、标准化和独热编码

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
 
numeric_features = ["age", "study_hours"]
category_features = ["city"]
 
numeric_pipeline = Pipeline(
    steps=[
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler()),
    ]
)
 
category_pipeline = Pipeline(
    steps=[
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("onehot", OneHotEncoder(handle_unknown="ignore")),
    ]
)
 
preprocessor = ColumnTransformer(
    transformers=[
        ("num", numeric_pipeline, numeric_features),
        ("cat", category_pipeline, category_features),
    ]
)

5.3 建模示例

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
 
model = Pipeline(
    steps=[
        ("preprocess", preprocessor),
        ("classifier", LogisticRegression(max_iter=1000)),
    ]
)
 
model.fit(X_train, y_train)
pred = model.predict(X_test)
 
print(classification_report(y_test, pred))

数据泄漏

标准化、填补缺失值、特征选择等步骤必须只在训练集上 fit,再应用到测试集。使用 Pipeline 可以减少数据泄漏风险。


6. 分析报告和可复现性

6.1 Notebook 建议

Notebook 习惯

  • 一个 Notebook 只解决一个明确问题。
  • 重要参数放在开头,避免散落在各处。
  • 图表标题、坐标轴、单位要写清楚。
  • 最终结论单独成节,写明口径、样本范围和局限。

6.2 项目目录建议

analysis-project/
  data/
    raw/          # 原始数据,不手动修改
    processed/    # 清洗后的中间数据
  notebooks/
  reports/
  src/
  tests/
  pyproject.toml
  README.md

6.3 常用检查清单

  • 是否明确数据时间范围和来源。
  • 是否检查缺失值、重复值、异常值。
  • 是否保留清洗和转换逻辑。
  • 是否避免手动修改原始数据。
  • 是否标注图表单位和统计口径。
  • 是否说明结论的不确定性和适用边界。