文章目录
- 一、pandas与建模代码结合
- 二、用patsy创建模型描述
- Patsy公式中的数据转换
- 分类数据与Pastsy
- 三、statsmodels介绍
- 评估线性模型
- 评估时间序列处理
- 四、scikit-learn介绍
一、pandas与建模代码结合
用DataFrame.values属性将DataFrame转换为NumPy数组
import pandas as pd
import numpy as np
data = pd.DataFrame({
'x0': [1, 2, 3, 4, 5],
'x1': [0.01, -0.01, 0.25, -4.1, 0.],
'y': [-1.5, 0., 3.6, 1.3, -2.]})
data
x0 | x1 | y | |
0 | 1 | 0.01 | -1.5 |
1 | 2 | -0.01 | 0.0 |
2 | 3 | 0.25 | 3.6 |
3 | 4 | -4.10 | 1.3 |
4 | 5 | 0.00 | -2.0 |
data.columns
'''Index(['x0', 'x1', 'y'], dtype='object')'''
# 用DataFrame.values属性将DataFrame转换为NumPy数组
data.values
'''
array([[ 1. , 0.01, -1.5 ],
[ 2. , -0.01, 0. ],
[ 3. , 0.25, 3.6 ],
[ 4. , -4.1 , 1.3 ],
[ 5. , 0. , -2. ]])
'''
向pd.DataFrame()传递含有列名的二维ndarray,可将数组转换为DataFrame
# 向pd.DataFrame()传递含有列名的二维ndarray,可将数组转换为DataFrame
df2 = pd.DataFrame(data.values, columns=['one', 'two', 'three'])
df2
one | two | three | |
0 | 1.0 | 0.01 | -1.5 |
1 | 2.0 | -0.01 | 0.0 |
2 | 3.0 | 0.25 | 3.6 |
3 | 4.0 | -4.10 | 1.3 |
4 | 5.0 | 0.00 | -2.0 |
选取DataFrame列的子集转换为ndarray
# 用loc选取列子集
model_cols = ['x0', 'x1']
data.loc[:, model_cols].values
'''
array([[ 1. , 0.01],
[ 2. , -0.01],
[ 3. , 0.25],
[ 4. , -4.1 ],
[ 5. , 0. ]])
'''
# 用iloc选取列子集
data.iloc[:, :-2].values
# 直接选取列名
data[['x0', 'x1']].values
# 用reindex(),传递columns=[]
data.reindex(columns=['x0','x1']).values
用pd.Categorical([与行数相对应的每行的分类组成的列表], categories=[不同分类组成的列表])添加分类列
data['category'] = pd.Categorical(['a', 'b', 'a', 'a', 'b'], categories=['a', 'b'])
data
x0 | x1 | y | category | |
0 | 1 | 0.01 | -1.5 | a |
1 | 2 | -0.01 | 0.0 | b |
2 | 3 | 0.25 | 3.6 | a |
3 | 4 | -4.10 | 1.3 | a |
4 | 5 | 0.00 | -2.0 | b |
用虚拟变量替换category列,要先创建虚拟变量,在删除category列,最后连接结果
# 用pd.get_dummies(df.category列名, prefix='category列名')创建虚拟变量,prefix设置新列名前缀
dummies = pd.get_dummies(data.category, prefix='category')
# df.drop('列名', axis=1)删除原来的分类列
# join将删除分类列的数据与虚拟变量连接
data_with_dummies = data.drop('category', axis=1).join(dummies)
data_with_dummies
x0 | x1 | y | category_a | category_b | |
0 | 1 | 0.01 | -1.5 | 1 | 0 |
1 | 2 | -0.01 | 0.0 | 0 | 1 |
2 | 3 | 0.25 | 3.6 | 1 | 0 |
3 | 4 | -4.10 | 1.3 | 1 | 0 |
4 | 5 | 0.00 | -2.0 | 0 | 1 |
# the same as above
# pd.get_dummies(df)会将df中不是数值型的列全部转换为虚拟变量,并在结果中字段将数值型列放在虚拟变量前
data_with_dummies2 = pd.get_dummies(data)
data_with_dummies2
# 但是要注意,如果原df中的数据列类似于 【是否已婚,是用1表示,否用0表示】,则需要先将此列转换为字符串,再用上面的方法。涉及到的转换方法见:https://www.pypandas.cn/docs/getting_started/basics.html#数据类型
二、用patsy创建模型描述
Patsy是一用于描述统计模型(尤其是线性模型)的Python库,能够很好的支持statsmodels中特定的线性模型。
Patsy的公式是特殊字符串语法,如:y ~ x0 + x1,这里的相加表示为模型创建的设计矩阵。即y、x0、x1表示向量的意思。
data = pd.DataFrame({
'x0': [1, 2, 3, 4, 5],
'x1': [0.01, -0.01, 0.25, -4.1, 0.],
'y': [-1.5, 0., 3.6, 1.3, -2.]})
data
x0 | x1 | y | |
0 | 1 | 0.01 | -1.5 |
1 | 2 | -0.01 | 0.0 |
2 | 3 | 0.25 | 3.6 |
3 | 4 | -4.10 | 1.3 |
4 | 5 | 0.00 | -2.0 |
patsy.dmatrices()
# patsy.dmatrices()接收一个公式字符串和一个数据集(可是DataFrame或数组的字典),为线性模型创建设计矩阵
import patsy
y, X = patsy.dmatrices('y ~ x0 + x1', data)
# y, X也可直接替换为a,则a[0]表示y,a[1]表示X
# 默认X中加入了Intercept截距项
# 给模型添加名词列+0,即patsy.dmatrices('y ~ x0 + x1 + 0', data)结果中会剔除Intercept截距项
y
'''
DesignMatrix with shape (5, 1)
y
-1.5
0.0
3.6
1.3
-2.0
Terms:
'y' (column 0)
'''
X
'''
DesignMatrix with shape (5, 3)
Intercept x0 x1
1 1 0.01
1 2 -0.01
1 3 0.25
1 4 -4.10
1 5 0.00
Terms:
'Intercept' (column 0)
'x0' (column 1)
'x1' (column 2)
'''
# Patsy的DesignMatrix实例是带有附加元数据的NumPy ndarray
np.asarray(y)
'''s
array([[-1.5],
[ 0. ],
[ 3.6],
[ 1.3],
[-2. ]])
'''
np.asarray(X)
'''
array([[ 1. , 1. , 0.01],
[ 1. , 2. , -0.01],
[ 1. , 3. , 0.25],
[ 1. , 4. , -4.1 ],
[ 1. , 5. , 0. ]])
'''
Patsy对象可直接传递给一些算法(比如numpy.linalg.lstsq),这些算法都会执行普通最小二乘回归OLS
# np.linalg.lstsq(X, y)返回y和X进行OLS的系数的最小二乘估计OLSE、残差平方和、矩阵X的秩、X的奇异值
# 这里,因为不想返回矩阵X的秩、X的奇异值,故用 短下横线_ 表示
coef, resid, _, _ = np.linalg.lstsq(X, y, rcond=None)
coef
'''
array([[ 0.3129],
[-0.0791],
[-0.2655]])
'''
# coef.squeeze()从数组的形状中删除单维条目,即把shape中为1的维度去掉
# X.design_info属性保留了模型的元数据,可以将模型列名重新附加到y与X进行OLS的系数的OLSE
coef = pd.Series(coef.squeeze(), index=X.design_info.column_names)
coef
'''
Intercept 0.312910
x0 -0.079106
x1 -0.265464
dtype: float64
'''
Patsy公式中的数据转换
可将Python代码混合到Patsy公式中,这样,在执行公式时,库将尝试在封闭作用域内查找使用的函数:
y, X = patsy.dmatrices('y ~ x0 + np.log(np.abs(x1) + 1)', data)
X
'''
DesignMatrix with shape (5, 3)
Intercept x0 np.log(np.abs(x1) + 1)
1 1 0.00995
1 2 0.00995
1 3 0.22314
1 4 1.62924
1 5 0.00000
Terms:
'Intercept' (column 0)
'x0' (column 1)
'np.log(np.abs(x1) + 1)' (column 2)
'''
Patsy内置的函数standardize()实现标准化(均值为0、方差为1)、center()实现居中(减列均值)
y, X = patsy.dmatrices('y ~ standardize(x0) + center(x1)', data)
X
'''
DesignMatrix with shape (5, 3)
Intercept standardize(x0) center(x1)
1 -1.41421 0.78
1 -0.70711 0.76
1 0.00000 1.02
1 0.70711 -3.33
1 1.41421 0.77
Terms:
'Intercept' (column 0)
'standardize(x0)' (column 1)
'center(x1)' (column 2)
'''
**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]})
new_X = patsy.build_design_matrices([X.design_info], new_data)
new_X
'''
[DesignMatrix with shape (4, 3)
Intercept standardize(x0) center(x1)
1 2.12132 3.87
1 2.82843 0.27
1 3.53553 0.77
1 4.24264 3.07
Terms:
'Intercept' (column 0)
'standardize(x0)' (column 1)
'center(x1)' (column 2)]
'''
# 用I()将数据集中两列按列名相加
y, X = patsy.dmatrices('y ~ I(x0 + x1)', data)
X
'''
DesignMatrix with shape (5, 2)
Intercept I(x0 + x1)
1 1.01
1 1.99
1 3.25
1 -0.10
1 5.00
Terms:
'Intercept' (column 0)
'I(x0 + x1)' (column 1)
'''
分类数据与Pastsy
将非数字型数据转换为用于模型设计的矩阵的方式有很多。
在Patsy公式中用非数字名词列时,会默认将其转换为虚拟变量。
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]
})
y, X = patsy.dmatrices('v2 ~ key1', data)
X
# 结果中key1[T.b]表示原key1列中分类b为True,即1
'''
DesignMatrix with shape (8, 2)
Intercept key1[T.b]
1 0
1 0
1 1
1 1
1 0
1 1
1 0
1 1
Terms:
'Intercept' (column 0)
'key1' (column 1)
'''
# 若忽略了模型的截距(+0),每个类别值的列将被包含在模型的设计矩阵中
y, X = patsy.dmatrices('v2 ~ key1 + 0', data)
X
'''
DesignMatrix with shape (8, 2)
key1[a] key1[b]
1 0
1 0
0 1
0 1
1 0
0 1
1 0
0 1
Terms:
'key1' (columns 0:2)
'''
用C()函数可以将(其实时分类数据的)数字型列解释为分类类型
y, X = patsy.dmatrices('v2 ~ C(key2)', data)
X
'''
DesignMatrix with shape (8, 2)
Intercept C(key2)[T.1]
1 0
1 1
1 0
1 1
1 0
1 1
1 0
1 0
Terms:
'Intercept' (column 0)
'C(key2)' (column 1)
'''
当在模型中包含多列分类数据时,可以添加交互项key1:key2 (用于方差分析ANOVA 模型)
# 构造包含多列分类数据的数据集
data['key2'] = data['key2'].map({0: 'zero', 1: 'one'})
data
key1 | key2 | v1 | v2 | |
0 | a | zero | 1 | -1.0 |
1 | a | one | 2 | 0.0 |
2 | b | zero | 3 | 2.5 |
3 | b | one | 4 | -0.5 |
4 | a | zero | 5 | 4.0 |
5 | b | one | 6 | -1.2 |
6 | a | zero | 7 | 0.2 |
7 | b | zero | 8 | -1.7 |
y, X = patsy.dmatrices('v2 ~ key1 + key2', data)
X
'''
DesignMatrix with shape (8, 3)
Intercept key1[T.b] key2[T.zero]
1 0 1
1 0 0
1 1 1
1 1 0
1 0 1
1 1 0
1 0 1
1 1 1
Terms:
'Intercept' (column 0)
'key1' (column 1)
'key2' (column 2)
'''
# 添加交互项数据列,之后可用于分析判断交互作用对y的影响
y, X = patsy.dmatrices('v2 ~ key1 + key2 + key1:key2', data)
X
'''
DesignMatrix with shape (8, 4)
Intercept key1[T.b] key2[T.zero] key1[T.b]:key2[T.zero]
1 0 1 0
1 0 0 0
1 1 1 1
1 1 0 0
1 0 1 0
1 1 0 0
1 0 1 0
1 1 1 1
Terms:
'Intercept' (column 0)
'key1' (column 1)
'key2' (column 2)
'key1:key2' (column 3)
'''
三、statsmodels介绍
statsmodels是一个Python库,用于拟合多种统计模型,执行统计测试即数据探索和可视化;包含更多“经典”频率学派的统计方法,其中一些模型有:
- 线性模型、广义线性模型和鲁棒线性模型
- 线性混合效应模型
- 方差分析ANOVA分析
- 时间序列过程和状态空间模型
- 广义的矩量法
【注意】本节案例学习都是在已知模型参数的情况下模拟估计、预测,实际运用中模型参数是未知的、待估计的,注意区别。
评估线性模型
statsmodels中的线性模型有两个不同的注意接口:基于数组和基于公式。
以下是基于数组的线性回归举例:
import statsmodels.api as sm
import statsmodels.formula.api as smf
# 定义生成指定均值与方差的样本量为size的正态分布样本
def dnorm(mean, variance, size=1):
if isinstance(size, int): # 判断size是否为int型
size = size, # 这里会将size转换为tuple元组,下面的*size会将size的所有值取出
return mean + np.sqrt(variance) * np.random.randn(*size)
# 用于复现 reproducibility
np.random.seed(12345)
N = 100
# np.c_[ , , ]会将数据一列一列的合并起来
# np.r_[ , , ]会将数据一行一行的合并起来
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] # 真实模型的系数beta
y = np.dot(X, beta) + eps # np.dot()可将矩阵与向量相乘
X[:5]
'''
array([[-0.1295, -1.2128, 0.5042],
[ 0.3029, -0.4357, -0.2542],
[-0.3285, -0.0253, 0.1384],
[-0.3515, -0.7196, -0.2582],
[ 1.2433, -0.3738, -0.5226]])
'''
y[:5]
'''array([ 0.4279, -0.6735, -0.0909, -0.4895, -0.1289])'''
# sm.add_constant(X) 用于将截距项添加到现有矩阵X
X_model = sm.add_constant(X)
X_model[:5]
'''
array([[ 1. , -0.1295, -1.2128, 0.5042],
[ 1. , 0.3029, -0.4357, -0.2542],
[ 1. , -0.3285, -0.0253, 0.1384],
[ 1. , -0.3515, -0.7196, -0.2582],
[ 1. , 1.2433, -0.3738, -0.5226]])
'''
# sm.OLS(y,X)可拟合一个最小二乘线性回归模型
model = sm.OLS(y, X_model)
# 模型的fit()方法返回一个回归结果对象(包含估计的模型的参数和其他诊断)
results = model.fit()
results.params
'''array([0.0336, 0.1761, 0.2248, 0.5148])'''
# 结果对象的summary()方法可打印模型的诊断细节
print(results.summary())
'''
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.435
Model: OLS Adj. R-squared: 0.418
Method: Least Squares F-statistic: 24.68
Date: Mon, 28 Jun 2021 Prob (F-statistic): 6.37e-12
Time: 14:54:16 Log-Likelihood: -33.835
No. Observations: 100 AIC: 75.67
Df Residuals: 96 BIC: 86.09
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 0.0336 0.035 0.952 0.343 -0.036 0.104
x1 0.1761 0.053 3.320 0.001 0.071 0.281
x2 0.2248 0.046 4.851 0.000 0.133 0.317
x3 0.5148 0.082 6.304 0.000 0.353 0.677
==============================================================================
Omnibus: 4.504 Durbin-Watson: 2.223
Prob(Omnibus): 0.105 Jarque-Bera (JB): 3.957
Skew: 0.475 Prob(JB): 0.138
Kurtosis: 3.222 Cond. No. 2.38
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
'''
以下是基于公式的线性回归举例:
data = pd.DataFrame(X, columns=['col0', 'col1', 'col2'])
data['y'] = y
data[:5]
col0 | col1 | col2 | y | |
0 | -0.129468 | -1.212753 | 0.504225 | 0.427863 |
1 | 0.302910 | -0.435742 | -0.254180 | -0.673480 |
2 | -0.328522 | -0.025302 | 0.138351 | -0.090878 |
3 | -0.351475 | -0.719605 | -0.258215 | -0.489494 |
4 | 1.243269 | -0.373799 | -0.522629 | -0.128941 |
# smf.OLS('Patsy公式字符串', data=DataFrame)可拟合一个最小二乘线性回归模型
# 模型的fit()方法返回一个回归结果对象(包含估计的模型的参数和其他诊断)
results = smf.ols('y ~ col0 + col1 + col2', data=data).fit()
# 结果对象的params属性返回参数beta最小二乘估计OLSE
results.params
'''
Intercept 0.033559
col0 0.176149
col1 0.224826
col2 0.514808
dtype: float64
'''
# 结果对象的tvalues属性返回检验的t值大小
results.tvalues
'''
Intercept 0.952188
col0 3.319754
col1 4.850730
col2 6.303971
dtype: float64
'''
# 结果对象的predict(data)会根据估计的模型参数返回预测值
results.predict(data[:5])
'''
0 -0.002327
1 -0.141904
2 0.041226
3 -0.323070
4 -0.100535
dtype: float64
'''
评估时间序列处理
# 自回归结构和噪声noise(一般包括误差、残差...)
init_x = 4
import random
# 定义起始值
values = [init_x, init_x]
N = 1000
b0 = 0.8
b1 = -0.4
noise = dnorm(0, 0.1, N)
for i in range(N):
# 新值为昨天的值*b0+前天的值*b1+噪声
new_x = values[-1] * b0 + values[-2] * b1 + noise[i]
values.append(new_x)
# 上面构建了具有参数为0.8和-0.4的AR(2)结构(两个滞后数)。
# 实际拟合数据时,滞后项的数目时位置的
# 以下设置滞后数 MAXLAGS = 5
MAXLAGS = 5
# sm.tsa.AR()
model = sm.tsa.AR(values)
results = model.fit(MAXLAGS)
# 给出估计的参数
results.params
'''array([-0.0062, 0.7845, -0.4085, -0.0136, 0.015 , 0.0143])'''
四、scikit-learn介绍
scikit-learn是使用最广泛的通用Python机器学习库,包含广泛的标准监督和无监督的机器学习方法,包括用于模型选择和评估、数据转换、数据加载和模型持久化的工具。可用于分类、聚类、降维、预测和其他任务。
以下示例为Kaggle比赛中关于泰坦尼克号上生还乘客的经典数据集。
# 载入册数数据与训练数据集
train = pd.read_csv('datasets/titanic/train.csv')
test = pd.read_csv('datasets/titanic/test.csv')
train[:4]
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th… | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
# 查看各列是否包含缺失数据
train.isnull().sum()
'''
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 177
SibSp 0
Parch 0
Ticket 0
Fare 0
Cabin 687
Embarked 2
dtype: int64
'''
test.isnull().sum()
'''
PassengerId 0
Pclass 0
Name 0
Sex 0
Age 86
SibSp 0
Parch 0
Ticket 0
Fare 1
Cabin 327
Embarked 0
dtype: int64
'''
在像这样的统计和机器学习的例子中,一个典型的任务是:
根据数据中的特征来预测乘客是否能幸存。将模型拟合到训练数据集上,然后在样本外 测试数据集上进行评估。
impute_value = train['Age'].median()
# 对于Age列,用训练集的 中位数 填充训练集和测试数据集上的缺失值
train['Age'] = train['Age'].fillna(impute_value)
test['Age'] = test['Age'].fillna(impute_value)
# 对于Sex列,添加一列IsFemale作为Sex列的编码版本,即将分类数据转换为由0-1组成的数据列
# 将此列转为虚拟变量也可以
train['IsFemale'] = (train['Sex'] == 'female').astype(int)
test['IsFemale'] = (test['Sex'] == 'female').astype(int)
# 选取要分析对乘客生还有影响的列变量,并创建Numpy数组
predictors = ['Pclass', 'IsFemale', 'Age']
X_train = train[predictors].values
X_test = test[predictors].values
y_train = train['Survived'].values
X_train[:5]
'''
array([[ 3., 0., 22.],
[ 1., 1., 38.],
[ 3., 1., 26.],
[ 1., 1., 35.],
[ 3., 0., 35.]])
'''
y_train[:5]
'''array([0, 1, 1, 1, 0], dtype=int64)'''
# 用scikit-learn的LogisticRegression模型创建一个模型实例
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
# 用模型的fit()方法将模型拟合到训练数据集上
model.fit(X_train, y_train)
'''LogisticRegression()'''
# 用模型的predict()方法为测试数据集形成预测
y_predict = model.predict(X_test)
y_predict[:10]
'''array([0, 0, 0, 0, 1, 0, 1, 0, 1, 0], dtype=int64)'''
至此,如果拥有测试数据集的真实值,可以计算精度百分比或其他一些错误指标:
(y_true == y_predict).mean()
实际的模型训练中可能还需要调整参数,并且存在可用于参数调整的交叉验证等避免过度拟合训练数据。
交叉验证通过分隔训练数据集来模拟样本外预测。
# LogisticRegressionCV中CV即为交叉验证,参数cv默认为3,即K折交叉验证中的K为3,切成3份
# 这里传递的10,是Cs,惩罚项的系数,表示网格搜索在模型正则化参数C上的细致度;值越小,正则化越强
from sklearn.linear_model import LogisticRegressionCV
model_cv = LogisticRegressionCV(Cs=10)
model_cv.fit(X_train, y_train)
'''LogisticRegressionCV()'''
# 手动交叉验证,用cross_val_score()
from sklearn.model_selection import cross_val_score
# C=10为惩罚项系数
model = LogisticRegression(C=10)
# cv=4为K折交叉验证的K
# scores为准确率
scores = cross_val_score(model, X_train, y_train, cv=4)
scores
'''array([0.7758, 0.7982, 0.7758, 0.7883])'''
scores.mean()
'''0.7845160182604128'''