模型开发的通常工作流是使用pandas进行数据加载和清洗,然后切换到建模库进行建模。开发模型的重要一环是机器学习中的“特征工程”。它可以描述从原始数据集中提取信息的任何数据转换或分析,这些数据集可能在建模中有用。本书中学习的数据聚合和GroupBy工具常用于特征工程中。
优秀的特征工程超出了本书的范围,我会尽量直白地介绍一些用于数据操作和建模切换的方法。
pandas与其它分析库通常是靠NumPy的数组联系起来的。将DataFrame转换为NumPy数组,可以使用.values属性:
In [10]:import pandas as pd
In [11]:import numpy as np
In [12]: 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.]})
In [13]: data
Out[13]:
x0 x1 y
010.01-1.512-0.010.0230.253.634-4.101.3450.00-2.0
In [14]: data.columns
Out[14]: Index(['x0','x1','y'], dtype='object')
In [15]: data.values
Out[15]:
array([[1.,0.01,-1.5],[2.,-0.01,0.],[3.,0.25,3.6],[4.,-4.1,1.3],[5.,0.,-2.]])
要转换回DataFrame,可以传递一个二维ndarray,可带有列名:
In [16]: df2 = pd.DataFrame(data.values, columns=['one','two','three'])
In [17]: df2
Out[17]:
one two three
01.00.01-1.512.0-0.010.023.00.253.634.0-4.101.345.00.00-2.0
笔记:最好当数据是均匀的时候使用.values属性。例如,全是数值类型。如果数据是不均匀的,结果会是Python对象的ndarray:
In [18]: df3 = data.copy()
In [19]: df3['strings']=['a','b','c','d','e']
In [20]: df3
Out[20]:
x0 x1 y strings
010.01-1.5 a
12-0.010.0 b
230.253.6 c
34-4.101.3 d
450.00-2.0 e
In [21]: df3.values
Out[21]:
array([[1,0.01,-1.5,'a'],[2,-0.01,0.0,'b'],[3,0.25,3.6,'c'],[4,-4.1,1.3,'d'],[5,0.0,-2.0,'e']], dtype=object)
对于一些模型,你可能只想使用列的子集。我建议你使用loc,用values作索引:
In [22]: model_cols =['x0','x1']
In [23]: data.loc[:, model_cols].values
Out[23]:
array([[1.,0.01],[2.,-0.01],[3.,0.25],[4.,-4.1],[5.,0.]])
一些库原生支持pandas,会自动完成工作:从DataFrame转换到NumPy,将模型的参数名添加到输出表的列或Series。其它情况,你可以手工进行“元数据管理”。
在第12章,我们学习了pandas的Categorical类型和pandas.get_dummies函数。假设数据集中有一个非数值列:
In [24]: data['category']= pd.Categorical(['a','b','a','a','b'],....: categories=['a','b'])
In [25]: data
Out[25]:
x0 x1 y category
010.01-1.5 a
12-0.010.0 b
230.253.6 a
34-4.101.3 a
450.00-2.0 b
如果我们想替换category列为虚变量,我们可以创建虚变量,删除category列,然后添加到结果:
In [26]: dummies = pd.get_dummies(data.category, prefix='category')
In [27]: data_with_dummies = data.drop('category', axis=1).join(dummies)
In [28]: data_with_dummies
Out[28]:
x0 x1 y category_a category_b
010.01-1.51012-0.010.001230.253.61034-4.101.310450.00-2.001
用虚变量拟合某些统计模型会有一些细微差别。当你不只有数字列时,使用Patsy(下一节的主题)可能更简单,更不容易出错。
13.2 用Patsy创建模型描述
Patsy是Python的一个库,使用简短的字符串“公式语法”描述统计模型(尤其是线性模型),可能是受到了R和S统计编程语言的公式语法的启发。
Patsy适合描述statsmodels的线性模型,因此我会关注于它的主要特点,让你尽快掌握。Patsy的公式是一个特殊的字符串语法,如下所示:
y ~ x0 + x1
a+b不是将a与b相加的意思,而是为模型创建的设计矩阵。patsy.dmatrices函数接收一个公式字符串和一个数据集(可以是DataFrame或数组的字典),为线性模型创建设计矩阵:
In [29]: 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.]})
In [30]: data
Out[30]:
x0 x1 y
010.01-1.512-0.010.0230.253.634-4.101.3450.00-2.0
In [31]:import patsy
In [32]: y, X = patsy.dmatrices('y ~ x0 + x1', data)
现在有:
In [33]: y
Out[33]:
DesignMatrix with shape (5,1)
y
-1.50.03.61.3-2.0
Terms:'y'(column 0)
In [34]: X
Out[34]:
DesignMatrix with shape (5,3)
Intercept x0 x1
110.0112-0.01130.2514-4.10150.00
Terms:'Intercept'(column 0)'x0'(column 1)'x1'(column 2)
这些Patsy的DesignMatrix实例是NumPy的ndarray,带有附加元数据:
In [35]: np.asarray(y)
Out[35]:
array([[-1.5],[0.],[3.6],[1.3],[-2.]])
In [36]: np.asarray(X)
Out[36]:
array([[1.,1.,0.01],[1.,2.,-0.01],[1.,3.,0.25],[1.,4.,-4.1],[1.,5.,0.]])
你可能想Intercept是哪里来的。这是线性模型(比如普通最小二乘回归)的惯例用法。添加 +0 到模型可以不显示intercept:
In [37]: patsy.dmatrices('y ~ x0 + x1 + 0', data)[1]
Out[37]:
DesignMatrix with shape (5,2)
x0 x1
10.012-0.0130.254-4.1050.00
Terms:'x0'(column 0)'x1'(column 1)
Patsy对象可以直接传递到算法(比如numpy.linalg.lstsq)中,它执行普通最小二乘回归:
In [38]: coef, resid, _, _ = np.linalg.lstsq(X, y)
模型的元数据保留在design_info属性中,因此你可以重新附加列名到拟合系数,以获得一个Series,例如:
In [39]: coef
Out[39]:
array([[0.3129],[-0.0791],[-0.2655]])
In [40]: coef = pd.Series(coef.squeeze(), index=X.design_info.column_names)
In [41]: coef
Out[41]:
Intercept 0.312910
x0 -0.079106
x1 -0.265464
dtype: float64
用Patsy公式进行数据转换
你可以将Python代码与patsy公式结合。在评估公式时,库将尝试查找在封闭作用域内使用的函数:
In [42]: y, X = patsy.dmatrices('y ~ x0 + np.log(np.abs(x1) + 1)', data)
In [43]: X
Out[43]:
DesignMatrix with shape (5,3)
Intercept x0 np.log(np.abs(x1)+1)110.00995120.00995130.22314141.62924150.00000
Terms:'Intercept'(column 0)'x0'(column 1)'np.log(np.abs(x1) + 1)'(column 2)
常见的变量转换包括标准化(平均值为0,方差为1)和中心化(减去平均值)。Patsy有内置的函数进行这样的工作:
In [44]: y, X = patsy.dmatrices('y ~ standardize(x0) + center(x1)', data)
In [45]: X
Out[45]:
DesignMatrix with shape (5,3)
Intercept standardize(x0) center(x1)1-1.414210.781-0.707110.7610.000001.0210.70711-3.3311.414210.77
Terms:'Intercept'(column 0)'standardize(x0)'(column 1)'center(x1)'(column 2)
作为建模的一步,你可能拟合模型到一个数据集,然后用另一个数据集评估模型。另一个数据集可能是剩余的部分或是新数据。当执行中心化和标准化转变,用新数据进行预测要格外小心。因为你必须使用平均值或标准差转换新数据集,这也称作状态转换。
patsy.build_design_matrices函数可以使用原始样本数据集的保存信息,来转换新数据,:
In [46]: new_data = pd.DataFrame({....:'x0':[6,7,8,9],....:'x1':[3.1,-0.5,0,2.3],....:'y':[1,2,3,4]})
In [47]: new_X = patsy.build_design_matrices([X.design_info], new_data)
In [48]: new_X
Out[48]:[DesignMatrix with shape (4,3)
Intercept standardize(x0) center(x1)12.121323.8712.828430.2713.535530.7714.242643.07
Terms:'Intercept'(column 0)'standardize(x0)'(column 1)'center(x1)'(column 2)]
因为Patsy中的加号不是加法的意义,当你按照名称将数据集的列相加时,你必须用特殊I函数将它们封装起来:
In [49]: y, X = patsy.dmatrices('y ~ I(x0 + x1)', data)
In [50]: X
Out[50]:
DesignMatrix with shape (5,2)
Intercept I(x0 + x1)11.0111.9913.251-0.1015.00
Terms:'Intercept'(column 0)'I(x0 + x1)'(column 1)
Patsy的patsy.builtins模块还有一些其它的内置转换。请查看线上文档。
分类数据有一个特殊的转换类,下面进行讲解。
分类数据和Patsy
非数值数据可以用多种方式转换为模型设计矩阵。完整的讲解超出了本书范围,最好和统计课一起学习。
当你在Patsy公式中使用非数值数据,它们会默认转换为虚变量。如果有截距,会去掉一个,避免共线性:
In [51]: 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]....:})
In [52]: y, X = patsy.dmatrices('v2 ~ key1', data)
In [53]: X
Out[53]:
DesignMatrix with shape (8,2)
Intercept key1[T.b]1010111110111