简介 📊

  • 本章将介绍如何将使用 pandas 进行数据整理与使用 Python 专用库进行模型构建联系起来。
  • 我们将重点关注如何将 pandas 与 statsmodels 和 scikit-learn 等库连接起来。
  • 这是数据分析工作流程中的关键一步。

关键概念 🔑

让我们定义一些重要的术语:

  • 数据挖掘: 从大型数据集中发现模式、异常和洞察力。它通常涉及各种技术,包括机器学习。
  • 机器学习 (ML): 人工智能的一个子集,使系统能够在没有明确编程的情况下从数据中学习。机器学习算法从“训练数据”构建模型以进行预测。
  • 统计学习: 使用统计方法理解数据的框架。它与机器学习有重叠,但通常更强调推理和可解释性。
  • 特征工程: 利用领域知识从原始数据中选择、转换和创建相关特征(变量)。这可以提高模型性能,并且至关重要

数据挖掘、机器学习和统计学习的关系 🤝

数据挖掘、机器学习和统计学习是相关的:

graph LR
    A[数据挖掘] --> C(共同点)  # 数据挖掘指向共同点
    B[机器学习] --> C(共同点)  # 机器学习指向共同点
    D[统计学习] --> C(共同点)  # 统计学习指向共同点
    C --> E[洞察与预测]  # 共同点指向洞察与预测

它们都旨在提取洞察并进行预测。机器学习和统计学习提供了在更广泛的数据挖掘环境中使用的工具。

Python 在数据分析中的作用 🐍

Python 在数据分析中占据主导地位,原因如下:

  • 丰富的生态系统: pandas(数据处理)、NumPy(数值计算)、statsmodels(统计建模)、scikit-learn(机器学习)以及 Matplotlib/Seaborn(可视化)等库提供了完整的工具包。
  • 易用性: Python 清晰的语法和交互性(例如 Jupyter)使其易于学习,即使没有编程背景。
  • 社区支持: 庞大而活跃的社区开发库、提供支持并创建文档。

常见工作流程 🔄

典型的模型开发工作流程:

graph LR
    A[数据加载 (pandas)] --> B[数据清洗 (pandas)]  # 数据加载指向数据清洗
    B --> C[特征工程 (pandas, 其他工具)]  # 数据清洗指向特征工程
    C --> D[模型构建 (statsmodels, scikit-learn)]  # 特征工程指向模型构建
    D --> E[模型评估 (statsmodels, scikit-learn)]  # 模型构建指向模型评估
    E --> F[预测/推理]  # 模型评估指向预测/推理

  1. 数据加载: pandas 读取数据(CSV、Excel、数据库等)。
  2. 数据清洗: pandas 处理缺失值、错误并转换数据。
  3. 特征工程: 创建/转换特征以获得更好的模型。
  4. 模型构建: statsmodels/scikit-learn 训练模型。
  5. 模型评估: 评估性能。
  6. 预测/推理: 进行预测或得出推论。

pandas 和模型之间的接口 ↔︎️

主要的接口通常是 NumPy 数组。pandas DataFrame 构建在 NumPy 之上,因此转换很容易。

DataFrame 到 NumPy 数组 ➡️

使用 .to_numpy() 将 DataFrame 转换为 NumPy 数组:

重要提示: .to_numpy() 优于 .values

示例:

import pandas as pd  # 导入 pandas 库
import numpy as np  # 导入 numpy 库

# 创建一个 DataFrame
data = pd.DataFrame({
    'x0': [1, 2, 3, 4, 5],  # 列 'x0'
    'x1': [0.01, -0.01, 0.25, -4.1, 0.],  # 列 'x1'
    'y': [-1.5, 0., 3.6, 1.3, -2.]  # 列 'y'
})

print(data)  # 打印 DataFrame
data_array = data.to_numpy()  # 将 DataFrame 转换为 NumPy 数组
print("\nNumPy 数组:")  # 打印提示信息
print(data_array)  # 打印 NumPy 数组

NumPy 数组到 DataFrame ⬅️

从 NumPy 数组创建 DataFrame:

# 使用 NumPy 数组和列名创建 DataFrame
df2 = pd.DataFrame(data_array, columns=['one', 'two', 'three'])
print(df2)  # 打印 DataFrame

我们在创建 DataFrame 时提供了列名。

同构数据与异构数据 🤔

  • 同构数据: 相同类型(例如,所有数字)-> 具有该类型的 NumPy 数组。
  • 异构数据: 混合类型(数字和字符串)-> dtype=object 数组(对于数值计算效率较低)。

示例

df3 = data.copy()  # 复制 DataFrame
df3['strings'] = ['a', 'b', 'c', 'd', 'e']  # 添加一个字符串列
print(df3)  # 打印 DataFrame

print("\n异构数组:")  # 打印提示信息
print(df3.to_numpy())  # 打印异构 NumPy 数组 (数据类型是object)

选择列子集 🤏

对于建模,在转换之前使用 .loc 选择列:

model_cols = ['x0', 'x1']  # 定义要选择的列
print(data.loc[:, model_cols].to_numpy())  # 选择列并转换为 NumPy 数组

分类数据和虚拟变量 🏷️

分类变量(例如,“male”、“female”)需要数值表示。使用虚拟变量(或独热编码)。

pandas get_dummies() 🐼

pd.get_dummies() 简化了此操作:

# 创建一个分类列
data['category'] = pd.Categorical(['a', 'b', 'a', 'a', 'b'], categories=['a', 'b'])
print(data)  # 打印 DataFrame

# 使用 get_dummies() 创建虚拟变量
dummies = pd.get_dummies(data.category, prefix='category')
print("\n虚拟变量:")  # 打印提示信息
print(dummies)  # 打印虚拟变量
# 删除原始分类列,并连接虚拟变量
data_with_dummies = data.drop('category', axis=1).join(dummies)
print("\n包含虚拟变量的 DataFrame:")  # 打印提示信息
print(data_with_dummies)  # 打印包含虚拟变量的 DataFrame

prefix 添加一个前缀(例如,category_a)。

Patsy:模型描述 📝

  • Patsy 使用公式语法(类似于 R)来指定模型,尤其是线性模型。

  • 它随 statsmodels 一起安装: conda install statsmodels

Patsy 公式 ➕

示例公式:

# y ~ x0 + x1
  • y:因变量(响应变量)。
  • x0, x1:自变量(预测变量)。
  • ~:分隔左侧(响应变量)和右侧(预测变量)。
  • +:包含项(不是数学加法!)。

patsy.dmatrices() 🧮

dmatrices() 接受一个公式和数据,返回设计矩阵:

import patsy  # 导入 patsy 库

# 使用 dmatrices() 创建设计矩阵
y, X = patsy.dmatrices('y ~ x0 + x1', data)

print("y (响应变量的设计矩阵):")  # 打印提示信息
print(y)  # 打印响应变量的设计矩阵
print("\nX (预测变量的设计矩阵):")  # 打印提示信息
print(X)  # 打印预测变量的设计矩阵

Patsy 默认包含一个截距(一列 1)——当预测变量为零时的基线响应。

抑制截距 ➖

使用 + 0 删除截距:

# 创建不包含截距的设计矩阵
X_no_intercept = patsy.dmatrices('y ~ x0 + x1 + 0', data)[1]  # [1] 仅获取 X
print(X_no_intercept)  # 打印不包含截距的 X

Patsy、NumPy 和 statsmodels 🤝

Patsy 中的设计矩阵可与 NumPy(例如 np.linalg.lstsq)或 statsmodels 一起使用:

import numpy as np  # 导入 numpy 库

# 使用 NumPy 的最小二乘法拟合线性模型
coef, resid, _, _ = np.linalg.lstsq(X, y, rcond=None) # 添加 rcond=None 以兼容
print(coef)  # 打印系数

# 将系数转换为 pandas Series
coef = pd.Series(coef.squeeze(), index=X.design_info.column_names)
print(coef)  # 打印带有索引的系数

Patsy:数据转换 ✨

在公式中包含 Python 代码以进行转换:

# 在公式中使用 Python 函数进行数据转换
y, X = patsy.dmatrices('y ~ x0 + np.log(np.abs(x1) + 1)', data)
print(X)  # 打印转换后的 X

Patsy 在作用域中查找 np.log 等函数。

内置函数:standardizecenter 🛠️

Patsy 具有内置函数:

  • standardize(x):标准化 x(均值为 0,标准差为 1)。
  • center(x):减去均值。
# 使用 Patsy 的内置函数进行数据转换
y, X = patsy.dmatrices('y ~ standardize(x0) + center(x1)', data)
print(X)  # 打印转换后的 X

有状态转换和 build_design_matrices 💾

当应用像centerstandardize这样的转换时,在转换新数据时使用原始数据集的统计数据。 patsy.build_design_matrices 可以提供帮助:

# 创建新数据
new_data = pd.DataFrame({
    'x0': [6, 7, 8, 9],
    'x1': [3.1, -0.5, 0, 2.3],
    'y' : [1, 2, 3, 4]
})

# 使用原始 X 的 design_info 构建新数据的设计矩阵
new_X = patsy.build_design_matrices([X.design_info], new_data)
print(new_X)  # 打印新数据的设计矩阵

按名称添加列 ➕

要添加列,请将它们包装在 I() 中:

# 使用 I() 添加列
y, X = patsy.dmatrices('y ~ I(x0 + x1)', data)
print(X) # 打印设计矩阵, I(x0 + x1)将x0 + x1作为一个整体加入

分类数据和 Patsy 🏷️

Patsy 自动处理分类变量:

# 创建一个包含分类变量的 DataFrame
data = pd.DataFrame({
    'key1': ['a', 'a', 'b', 'b', 'a', 'b', 'a', 'b'],
    'key2': [0, 1, 0, 1, 0, 1, 0, 0],
    'v1': [1, 2, 3, 4, 5, 6, 7, 8],
    'v2': [-1, 0, 2.5, -0.5, 4.0, -1.2, 0.2, -1.7]
    })

# 使用 Patsy 处理分类变量
y, X = patsy.dmatrices('v2 ~ key1', data)
print(X)  # 打印设计矩阵

Patsy 省略了一个级别以避免共线性(与截距)。

无截距,分类变量 ➖🏷️

没有截距时,将包含所有类别列:

# 在没有截距的情况下处理分类变量
y, X = patsy.dmatrices('v2 ~ key1 + 0', data)
print(X)  # 打印设计矩阵

将数值视为分类变量 🔢➡️🏷️

使用 C() 将数字视为分类变量:

# 使用 C() 将数值视为分类变量
y, X = patsy.dmatrices('v2 ~ C(key2)', data)
print(X) # 打印设计矩阵

交互项 🤝

交互项对组合效应进行建模。使用 :

# 将 key2 映射为字符串
data['key2'] = data['key2'].map({0: 'zero', 1: 'one'})
print(data)  # 打印 DataFrame

# 创建包含交互项的设计矩阵
y, X = patsy.dmatrices('v2 ~ key1 + key2 + key1:key2', data)
print(X)  # 打印设计矩阵

statsmodels 简介 📈

statsmodels 用于统计建模、假设检验和数据探索。它侧重于统计推断,与 scikit-learn 互补。

statsmodels 中的模型 🧮

statsmodels 包括:

  • 线性模型: OLS、GLS 等。
  • 广义线性模型 (GLM): 适用于各种响应类型(二元、计数)。
  • 时间序列分析: ARIMA、VAR 等。
  • 以及更多…

使用 statsmodels 的线性模型 📏

使用数组和公式 API 拟合线性模型。

创建示例数据

import statsmodels.api as sm  # 导入 statsmodels.api 模块
import statsmodels.formula.api as smf  # 导入 statsmodels.formula.api 模块

# 可重现的示例
rng = np.random.default_rng(seed=12345)  # 设置随机数种子以实现可重复性

# 定义一个生成正态分布随机数的函数
def dnorm(mean, variance, size=1):
    if isinstance(size, int):  # 如果 size 是整数
        size = size,  # 将其转换为元组
    return mean + np.sqrt(variance) * rng.standard_normal(*size)  # 生成正态分布随机数

N = 100  # 样本数量
# 生成自变量 X
X = np.c_[dnorm(0, 0.4, size=N),
          dnorm(0, 0.6, size=N),
          dnorm(0, 0.2, size=N)]
eps = dnorm(0, 0.1, size=N)  # 生成随机误差项
beta = [0.1, 0.3, 0.5]  # 系数
y = np.dot(X, beta) + eps  # 生成因变量 y

基于数组的 API ➕

X_model = sm.add_constant(X)  # 添加截距项
model = sm.OLS(y, X_model)  # 创建普通最小二乘 (OLS) 模型
results = model.fit()  # 拟合模型
print(results.params)  # 打印模型参数
print(results.summary())  # 打印模型摘要

sm.add_constant(X) 添加一列 1 以表示截距。

基于公式的 API

# 创建包含自变量和因变量的 DataFrame
data = pd.DataFrame(X, columns=['col0', 'col1', 'col2'])
data['y'] = y

# 使用公式 API 拟合 OLS 模型
results = smf.ols('y ~ col0 + col1 + col2', data=data).fit()
print(results.params)  # 打印模型参数
print(results.tvalues)  # 打印 t 值
print(results.predict(data[:5]))  # 对前 5 个样本进行预测

公式 API 处理截距并与 DataFrame 一起使用。

使用 statsmodels 进行时间序列分析 ⌚

statsmodels 具有时间序列工具。示例:自回归 (AR) 模型:

from statsmodels.tsa.ar_model import AutoReg  # 导入 AutoReg 类

init_x = 4  # 初始值
values = [init_x, init_x]  # 初始值列表
N = 1000  # 时间序列长度
b0 = 0.8  # AR(1) 系数
b1 = -0.4  # AR(2) 系数
noise = dnorm(0, 0.1, N)  # 生成噪声

# 生成 AR(2) 时间序列
for i in range(N):
    new_x = values[-1] * b0 + values[-2] * b1 + noise[i]
    values.append(new_x)

MAXLAGS = 5  # 最大滞后阶数
model = AutoReg(values, MAXLAGS)  # 创建 AutoReg 模型
results = model.fit()  # 拟合模型
print(results.params)  # 打印模型参数

scikit-learn 简介 🤖

scikit-learn 是一个强大、广泛使用的机器学习库。它具有用于分类、回归、聚类等的算法。

scikit-learn 特性 ✨

  • 简单、一致的 API: 用户友好且一致。
  • 许多算法: 涵盖常见的机器学习任务。
  • 模型选择/评估: 交叉验证、超参数调整、性能指标。
  • 数据预处理: 缩放、特征选择等。

泰坦尼克号示例 🚢

使用泰坦尼克号数据集展示 scikit-learn 工作流程。

# 读取训练集和测试集
train = pd.read_csv('datasets/titanic/train.csv')
test = pd.read_csv('datasets/titanic/test.csv')
train.head(4)  # 显示训练集的前 4 行

数据预处理 🧹

# 检查训练集和测试集中的缺失值数量
print(train.isna().sum())
print(test.isna().sum())

我们有缺失数据:

impute_value = train['Age'].median()  # 计算 'Age' 列的中位数
train['Age'] = train['Age'].fillna(impute_value)  # 使用中位数填充 'Age' 列中的缺失值
test['Age'] = test['Age'].fillna(impute_value)  # 使用中位数填充 'Age' 列中的缺失值

# 创建一个新特征 'IsFemale'
train['IsFemale'] = (train['Sex'] == 'female').astype(int)
test['IsFemale'] = (test['Sex'] == 'female').astype(int)

# 定义预测变量
predictors = ['Pclass', 'IsFemale', 'Age']
X_train = train[predictors].to_numpy()  # 获取训练集的特征
X_test = test[predictors].to_numpy()  # 获取测试集的特征
y_train = train['Survived'].to_numpy()  # 获取训练集的标签
print(X_train[:5])  # 打印前 5 个训练样本的特征
print(y_train[:5])  # 打印前 5 个训练样本的标签

模型训练和预测 🏋️‍♀️

from sklearn.linear_model import LogisticRegression  # 导入 LogisticRegression 类

model = LogisticRegression()  # 创建 LogisticRegression 模型
model.fit(X_train, y_train)  # 使用训练数据训练模型
y_predict = model.predict(X_test)  # 使用测试数据进行预测
print(y_predict[:10])  # 打印前 10 个预测结果

交叉验证 🔄

from sklearn.linear_model import LogisticRegressionCV  # 导入 LogisticRegressionCV 类
from sklearn.model_selection import cross_val_score  # 导入 cross_val_score 函数

# 使用交叉验证创建 LogisticRegression 模型
model_cv = LogisticRegressionCV(Cs=10)
model_cv.fit(X_train, y_train)  # 使用训练数据训练模型

# 使用交叉验证评估模型
model = LogisticRegression(C=10)
scores = cross_val_score(model, X_train, y_train, cv=4)  # 进行 4 折交叉验证
print(scores)  # 打印交叉验证分数

交叉验证有助于避免过拟合,并提供更稳健的模型。

总结 📝

  • 我们使用 statsmodelsscikit-learn 将 pandas 数据整理和模型构建联系起来。
  • 使用 .to_numpy() 将 DataFrame 转换为 NumPy 数组。
  • Patsy 为模型提供了一种公式语法。
  • statsmodels 擅长统计推断。
  • scikit-learn 对于许多机器学习任务具有通用性。

思考与讨论 🗣️

  • 如何在 statsmodelsscikit-learn 之间进行选择?考虑推断与预测。
  • 如何在泰坦尼克号示例中改进特征?
  • 其他 Python 建模库(TensorFlow、PyTorch、XGBoost)?
  • 这与您的项目有何关系?
  • 数据分析将如何发展?