对于有抱负的数据科学家来说,有时很难找到正确的预处理技术途径。Sklearn的预处理库为指导您完成数据科学管道中的这项重要任务奠定了坚实的基础。
本文打算用sklearn v0.20.0作为预处理的完整指南。它包括sklearn中可用的所有实用函数和转换器类,并补充了来自其他公共库的一些有用函数。
将处理以下主题:
- 缺失值
- 多项式特征
- 分类特征
- 数值特征
- 自定义的转换
- 特征缩放
- 归一化
注意,步骤3和4可以互换地执行,因为这些转换应该彼此独立地执行。
缺失值
处理缺失值是一项必不可少的预处理任务,如果不充分注意,可能会严重影响您的机器学习模型。处理缺失值时应该提出几个问题:
我有缺失值吗?它们如何在数据中表达呢?我应该保留缺失值的样本吗?或者我应该更换它们吗?如果是这样,它们应该替换为哪些值呢?
在开始处理缺失值之前,必须确定缺失值并知道它们被替换的值。您应该能够通过将元数据信息与探索性分析相结合来找到它。
一旦您对丢失的数据有了更多的了解,您就必须决定是否要保留丢失数据的条目。Chris Albon (Python Cookbook机器学习)认为,这个决策应该部分取决于缺失值的随机性。
如果它们完全是随机的,它们不会提供任何额外信息,可以省略。另一方面,如果它们不是随机的,则缺失值的事实本身就是信息,并且可以表示为额外的二进制特征。
还要记住,因为一个缺失值而删除整个观察,可能是一个糟糕的决策,并导致信息丢失。
让我们使用sklearn的MissingIndicator通过一些Python编码示例来实现这个理论。为了让代码更有意义,我们将创建一个包含三个特性和五个示例的非常小的数据集。数据中有明显的缺失值,表示为not-a-number或999。
快速查看数据,以便了解缺失值的位置。可以使用pandas的dropna函数从您的数据中删除具有许多无意义缺失值的行或列。让我们来看看最重要的参数:
- axis:0表示行,1表示列
- tresh:保留非NaN行或列的数量
- inplace:更新frame
我们通过删除只有缺少值的所有行(axis = 0)来更新数据集。请注意,在这种情况下,您可以将how参数设置为“all”,而不是将tresh设置为1 。结果我们的第二个样本被删除,因为它只包含缺失值。请注意,为了将来的方便,我们重置索引并删除旧索引列。
X.dropna(axis=0, thresh=1, inplace=True) X.reset_index(inplace=True) X.drop(['index'], axis=1, inplace=True)
我们还创建一些额外的布尔特征,告诉我们样本是否具有特定特征的缺失值。通过从sklearn.impute导入MissingIndicator(注意,需要0.20.0版本)。
不幸的是,MissingIndicator不支持多种类型的缺失值。因此,我们必须将dataframe中的999值转换为NaN。接下来,我们创建、fit并转换一个MissingIndicator对象,该对象将检测数据中的所有NaN。
通过此指示器,我们可以创建一个新的dataframe,其中包含一个布尔值指示某个特征的实例是否具有缺失值。但是,为什么我们只有两个新列,而我们原来有三个的特征呢?删除第二个样本后,f2不再有缺失值。如果MissingIndicator没有检测到某个特性中有任何缺失值,它就不会从该特性中创建新特性。
我们稍后会将这些新特征添加到原始数据中,现在我们可以将它们存储在指示变量中。
from sklearn.impute import MissingIndicator X.replace({999.0 : np.NaN}, inplace=True) indicator = MissingIndicator(missing_values=np.NaN) indicator = indicator.fit_transform(X) indicator = pd.DataFrame(indicator, columns=['m1', 'm3'])
在决定保留(一些)缺失值并创建缺失值指示器之后,下一个问题是是否应该替换缺失值。当缺失值表达为非数字(np . nan)且需要某种形式的缺失值估算时,多数学习算法表现较差。注意,某些程序库和算法(如XGBoost)可以通过学习自动处理缺失值并自动估算这些值。
估算值
为了用常用的策略填充缺失的值,sklearn提供了一个简单的imputer。主要策略是mean、most _frequency、middle和constant(不要忘记设置fill_value参数)。在下面的例子中,我们用特性的平均值为dataframe X赋予缺失值。
from sklearn.impute import SimpleImputer imp = SimpleImputer(missing_values=np.nan, strategy='mean') imp.fit_transform(X)
请注意,返回的值将放入Numpy数组中,我们将失去所有元信息。由于所有这些策略都可以在pandas中实现,我们将使用pandas fillna方法来估算缺失值。对于' mean ',我们可以使用以下Python代码。这个pandas实现还提供了 fill forward(ffill)或fill backward(bfill)的选项,这在处理时间序列时很方便。
X.fillna(X.mean(), inplace=True)
其他常用的估算缺失数据的方法是用k近邻算法(KNN)对数据进行聚类,或者使用各种插值方法对数据进行插值。这两种技术都不是在sklearn的预处理库中实现的,这里不会讨论。
多项式特征
创建多项式特征是一种简单而常见的特征工程方法,它通过组合特征增加了数字输入数据的复杂性。
当我们希望包含特征与目标之间存在非线性关系的概念时,通常会创建多项式特征。它们主要用于增加具有少量特征的线性模型的复杂性,或者当我们怀疑一个特征的效果依赖于另一个特征时。
在处理缺失值之前,您需要决定是否要使用多项式特征。例如,如果您将所有缺失值替换为0,则使用此特征的所有交叉积将为0.此外,如果不替换缺失值(NaN),则创建多项式特征将在fit_transform阶段引发值错误。
在这方面,用中位数或均值替换缺失值似乎是一个合理的选择。
Sklearn提供了一个PolynomialFeatures类,可以从头开始创建多项式特征。degree参数确定多项式的最大程度。例如,当degree设置为2且X = x1,x2时,创建的特征将为1,x1,x2,x12,x1x2和x22。该interaction_only参数让函数知道我们只想要的互动特征,即1,X1,X2和X1X2。
在这里,我们创建了三次多项式特征,并且只创建了交互特征。结果我们得到了四个新特征:f1.f2,f1.f3,f2.f3和f1.f2.f3。请注意,我们的原始特征也包含在输出中,我们将新特征切片以便稍后添加到我们的数据中。
from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(degree=3, interaction_only=True) polynomials = pd.DataFrame(poly\ .fit_transform(X), columns=['0','1','2','3', 'p1', 'p2', 'p3', 'p4'])\ [['p1', 'p2', 'p3', 'p4']]
与任何其他形式的特征工程一样,在进行任何特征缩放之前创建多项式特征非常重要。
现在,让我们用pandas concat方法将我们新的缺失indicator特征和多项式特征连接到我们的数据。
X = pd.concat([X, indicator, polynomials], axis=1)
具有原始特征(f),缺失值指示器(m)和多项式特征(p)的Dataframe
分类特征
在数据预处理过程中,Munging分类数据是另一个基本过程。不幸的是,sklearn的机器学习库不支持处理分类数据。即使是基于树的模型,也需要将分类特征转换为数字表示。
在开始转换数据之前,重要的是要确定您正在处理的特征是否是序数(而不是标称值)。序数特征被描述为具有自然有序类别的特征,并且类别之间的距离未知。
一旦您知道正在处理的分类数据类型,就可以选择适合的转换工具。在sklearn这将是一个OrdinalEncoder为序数数据,以及OneHotEncoder标称数据。
让我们考虑一个简单的例子来演示两个类是如何工作的。创建一个包含五个条目和三个特征的数据框: sex, blood type 和 education level
X = pd.DataFrame( np.array(['M', 'O-', 'medium', 'M', 'O-', 'high', 'F', 'O+', 'high', 'F', 'AB', 'low', 'F', 'B+', np.NaN]) .reshape((5,3))) X.columns = ['sex', 'blood_type', 'edu_level']
查看数据框,您应该注意到education level是唯一的序数特征(可以对其进行排序,并且不知道类别之间的距离)。我们将首先使用OrdinalEncoder类对此特征进行编码。导入该类并创建一个新实例。然后通过将特征拟合并转换为编码器来更新education level特征。结果应如下所示。
注意,这里有一个相当恼人的问题:我们缺失值被编码为一个单独的类(3.0)。仔细查看文档就会发现这个问题还没有解决方案。sklearn开发人员正在讨论实现适合的解决方案的可能性,这是一个好迹象。
另一个问题是不遵守我们的数据顺序。幸运的是,可以通过将特征的惟一值的有序列表传递给categories参数来解决这个问题。
encoder = OrdinalEncoder(categories=['low', 'medium', 'high'])
为了解决第一个问题,我们必须求使用pandas。因数分解方法提供了一种替代方法,它可以处理缺失值,并尊重值的顺序。第一步是将该特征转换为有序的pandas分类。传递一个类别列表(包括一个缺失值的类别),并将有序参数设置为True。
cat = pd.Categorical(X.edu_level, categories=['missing', 'low', 'medium', 'high'], ordered=True)
用缺失的类别替换缺失值。
cat.fillna('missing')
然后,将sort参数设置为True,将Categorical分解,并将输出分配给education level特征。
labels, unique = pd.factorize(cat, sort=True) X.edu_level = labels
这次的结果更令人满意,因为数据是数值型的,仍然是有序的,缺失的值被替换为0。请注意,用最小值替换缺失值可能并不总是最佳选择。其他选项是将其放在最常用的类别中,或者在对特征进行排序时将其放在中值的类别中。
现在让我们转向另外两个 标称特征。记住,我们不能用数字来代替这些特征,因为这意味着这些特征是有顺序的,这在sex或blood type的情况下是不正确的。
编码标称特征的最流行的方法是one-hot-encoding。实质上,具有n个类别的每个分类特征被转换为n个二进制特征。
让我们来看看我们的例子,让事情变得清晰。首先导入OneHotEncoder类并创建一个输出数据类型设置为整数的新实例。这不会改变我们的数据解释方式,但会提高输出的可读性。
然后,拟合并转换我们的两个标称分类。这个转换的输出将是一个稀疏矩阵,这意味着我们必须先将矩阵转换为一个数组(.toarray()),然后才能将其转化为数据帧。在启动新的类实例时,可以通过将sparse参数设置为False来省略此步骤。分配列名称,输出已准备好添加到其他数据(edu_level特征)。
from sklearn.preprocessing import OneHotEncoder onehot = OneHotEncoder(dtype=np.int, sparse=True) nominals = pd.DataFrame( onehot.fit_transform(X[['sex', 'blood_type']])\ .toarray(), columns=['F', 'M', 'AB', 'B+','O+', 'O-']) nominals['edu_level'] = X.edu_level
将输出(标称)与原始数据进行比较,以确保所有内容都以正确的方式进行。
由于我们的数据中没有缺失值,因此请务必了解如何使用OneHotEncoder处理缺失值。缺失值可以轻松地作为额外特征处理。请注意,要执行此操作,您需要首先将缺失值替换为任意值,另一方面,如果您想要忽略缺失的值并创建一个实例,该实例的所有值都为0 (False),您可以将OneHotEncoder的handle_unkown参数设置为ignore。
数字特征
就像可以对分类数据进行编码一样,数字特征可以“解码”为分类特征。最常见的两种方法是离散化和二值化。
离散化
离散化(也称为量化)将连续特征划分为预先指定数量的类别(分类),从而使数据离散。
离散化的主要目标之一是显著减少连续属性的离散间隔的数量。为什么这种转换可以提高基于树的机器学习模型的性能呢?
Sklearn提供了一个KBinsDiscretizer类,可以解决这个问题。您需要指定的惟一一件事是每个特征的bins数量(n_bins)以及如何编码这些bins(ordinal, onehot or onehot-dense)。可选策略参数可以设置为三个值:
- uniform,每个特征中的所有bins具有相同的宽度。
- quantile(默认),每个特性中的所有bins具有相同数量的点。
- kmeans,其中每个bin中的所有值都具有离1D k-means聚类最近的中心相同的位置。
务必谨慎选择策略参数。例如,使用uniform 策略对异常值非常敏感,并且可以使您最终只使用几个数据点(即异常值)。
让我们转向我们的例子进行一些澄清。导入KBinsDiscretizer类并使用三个bin,ordinal编码和uniform 策略创建一个新实例(所有bin都具有相同的宽度)。然后,对所有原始的、缺失指示器和多项式数据进行拟合和转换。
from sklearn.preprocessing import KBinsDiscretizer disc = KBinsDiscretizer(n_bins=3, encode='uniform', strategy='uniform') disc.fit_transform(X)
如果输出对您没有意义,请调用discretizer (disk)上的bin_edges_属性,并查看bins是如何划分的。然后尝试另一种策略,看看bin边如何相应地变化。
二值化
特征二值化是对数值特征进行阈值处理以获得布尔值的过程。或者换句话说,根据阈值为每个样本分配一个布尔值(True或False)。请注意,二值化是双bin离散化的一种极端形式。
在一般情况下,二值化是一种有用的特征工程技术,用于创建新的特征,表明一些有意义的东西。正如上面提到的MissingIndicator是用来标记有意义的缺失值的。
sklearn中的Binarizer类以非常直观的方式实现了二值化。您需要指定的惟一参数是阈值和copy。低于或等于阈值的所有值都被替换为0,高于阈值的值替换为1。如果copy被设置为False,则执行就地二值化,否则创建副本。
考虑我们示例的特性3 (f3),让我们创建一个额外的二进制特征,正数为True,负数为False。导入Binarizer类,创建一个阈值设置为0的新实例,并将copy设置为True。然后,对二值化器进行拟合和变换,得到特征3。输出是一个新的布尔值数组。
from sklearn.preprocessing import Binarizer binarizer = Binarizer(threshold=0, copy=True) binarizer.fit_transform(X.f3.values.reshape(-1, 1))
自定义转换
如果要将现有函数转换为转换器以协助数据清理或处理,可以使用FunctionTransformer从任意函数实现转换器。如果您在sklearn中使用Pipeline,此类可能很有用,但可以通过将lambda函数应用于要转换的特征(如下所示)来轻松替换。
from sklearn.preprocessing import FunctionTransformer transformer = FunctionTransformer(np.log1p, validate=True) transformer.fit_transform(X.f2.values.reshape(-1, 1)) #same output X.f2.apply(lambda x : np.log1p(x)) #same output
特征缩放
我们的预处理管道中的下一个逻辑步骤是缩放我们的特征。在应用任何缩放变换之前,将数据拆分为训练集和测试集非常重要。如果您之前开始缩放,您的训练(和测试)数据可能最终会缩放到一个平均值(见下文),而这个值实际上并不是训练或测试数据的平均值。
标准化
标准化是一种转换,它通过去除每个特征的平均值来使数据居中,然后通过将(非常数)特征除以它们的标准偏差来对其进行缩放。标准化数据后,平均值为零,标准差为1。
标准化可以大大提高机器学习模型的性能。例如,在学习算法的目标函数中使用的许多特征(例如支持向量机的RBF kernel或线性模型的l1和l2正则化器)都假定所有特征都以0为中心,并且方差的顺序相同。如果某个特征的方差比其他要大一个数量级,那么它可能会主导目标函数并使估算工具无法按预期正确地学习其他特征。
根据您的需求和数据,sklearn提供了一系列缩放器:StandardScaler,MinMaxScaler,MaxAbsScaler和RobustScaler。
标准缩放
Sklearn的主要缩放器StandardScaler使用严格的标准化定义来标准化数据。它纯粹使用以下公式对数据进行居中,其中u是平均值,s是标准偏差。
x_scaled =(x - u)/ s
让我们来看看我们的例子,看看这在实践中。在我们开始编码之前,我们应该记住,我们的第四个实例的值已经缺失了,我们用均值替换它。如果我们在上面的公式中输入均值,标准化后的结果应为零。我们来试试吧。
导入StandardScaler类并创建新实例。请注意,对于稀疏矩阵,您可以将with_mean参数设置为False,以便不将值置于零中心。然后,将缩放器拟合并转换为特征3。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() scaler.fit_transform(X.f3.values.reshape(-1, 1))
正如预期的那样,第四个实例的值为零。
MinMax Scaler
MinMaxScaler通过将每个特征按比例缩放到一个给定的范围来转换特征。这个范围可以通过指定feature_range参数(默认值为(0,1))来设置。当分布不是高斯分布或者标准差非常小时,这个scaler更适合。但是,它对离群值很敏感,所以如果数据中有离群值,您可能需要考虑另一个标量。
x_scaled =(x-min(x))/(max(x)-min(x))
导入和使用MinMaxScaler——就像下面所有的标量一样——与标准标量的工作方式完全相同。唯一的区别在于新实例启动时的参数。
这里,我们将feature 3 (f3)缩放到-3到3之间。正如预期的那样,最大值(25)被转换为3,最小值(-1)被转换为-3。所有其他值在这些值之间是线性缩放的。
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(-3,3)) scaler.fit_transform(X.f3.values.reshape(-1, 1))
MaxAbs Scaler
MaxAbsScaler的工作原理与MinMaxScaler非常相似,但它会根据绝对最大值自动将数据缩放到[-1,1]范围。这个scaler 适用于已经以零或稀疏数据为中心的数据。它不会shift/center数据,因此不会破坏任何稀疏性。
x_scaled = x / max(abs(x))
让我们再次使用MaxAbsScaler转换它来处理特征3,并将输出与原始数据进行比较。
from sklearn.preprocessing import MaxAbsScaler scaler = MaxAbsScaler() scaler.fit_transform(X.f3.values.reshape(-1, 1))
Feature 3 before and after applying the MaxAbsScaler
Robust Scaler
如果您的数据包含许多异常值,则使用数据的均值和标准差进行缩放可能不会很好。在这些情况下,您可以使用RobustScaler。它删除中值并根据分位数范围缩放数据。
默认情况下,scaler使用四分位间距(IQR),即第一个四分位数和第三个四分位数之间的范围。可以通过在启动RobustScaler的新实例时指定quantile_range参数来手动设置分位数范围。在这里,我们使用从10%到90%的分位数范围来变换特征3。
from sklearn.preprocessing import RobustScaler robust = RobustScaler(quantile_range = (0.1,0.9)) robust.fit_transform(X.f3.values.reshape(-1, 1))
归一化
归一化是将单个样本缩放为具有单位范数的过程。基本上,当算法根据数据点之间形成的加权关系进行预测时,需要对数据进行归一化。将输入缩放到unit norms是文本分类或聚类的常用操作。
缩放(例如标准化)和归一化之间的关键区别之一是归一化是 行操作,而缩放是列操作。
虽然还有许多其他方法来归一化数据,但sklearn提供了三个规范:l1,l2和max。在创建Normalizer类的新实例时,您可以在norm参数下指定所需的范数。
下面,在Python代码中讨论并实现可用规范的公式 - 其中结果是数据集X中 每个样本的分母列表。
“max”
max范数使用绝对最大值,它对样本的作用就像MaxAbsScaler对特征的作用一样。
x_normalized = x / max(x)
'L1'
l1范数使用所有值的和作为参数,从而对所有参数进行相等的惩罚,从而加强了稀疏性。
x_normalized = x / sum(X)
'L2'
l2范数使用所有平方值之和的平方根。这就产生了平滑度和旋转不变性。一些机器学习模型,如主成分分析,假设旋转不变性,所以l2会表现得更好。
x_normalized = x / sqrt(sum((i**2) for i in X))