摘要:数据预处理就是对数据预分析的一个过程,其目的是将数据转换为标准且规范的格式。一个好的数据预处理过程对后期的分析(如EDA)甚至是预测(建模),将数据转化为对业务的 insights,支持领导层决策等至关重要。
数据预处理通常涉及以下几个方面:
我将出一个教程系列,解释如何使用 Python 的 pandas 库进行数据预处理,总共分为6个部分。本文(Part 1)主要讲述缺失值(missing values)处理。
在该教程中,我们将使用一个肝炎数据集作为实验数据,获取方式见文末。
首先,我们使用 pandas 库导入数据集并将其转换为数据框,然后使用 head(10) 方法查看数据集的前10行:
import pandas as pd
df = pd.read_csv('datasets/hepatitis.csv')
df.head(10)
通过上面的输出,你可能会注意到数据集的问题,有些列在某些行的值为 NaN,这就表示缺失值,比如 protime 列在前10行中就存在多数的缺失值。在 pandas 中,你可以使用 isna() 方法识别缺失值,同时,加上 sum() 方法可以对整个数据集中每一列的缺失值进行计数:
df.isna().sum()
现在,你已经得到了每一列中缺失值的数量。另外,在该值的基础上简单除以数据集的长度再乘以数值100,就可以得到每一列所含缺失值的百分比:
df.isna().sum() / len(df) * 100
从上面的输出可以看出,protime 列含缺失值的百分比最高(高达43.23%)。
现在,你已经能够正确识别出缺失值了,是时候考虑该如何处理它们了。通常有以下几种处理方式:
接下来我们主要关注删除和替换缺失值两种处理方式。
你可以使用 dropna 方法删除缺失值,其语法形式如下:
DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
axis=0 表示删除行(缺省时默认按行删除),axis=1 表示删除列。比如,在我们的示例中,按列删除缺失值:df.dropna(axis=1)
最后只剩下5列,因为其他列中都至少存在一个缺失值。
how='any' 表示只要有缺失值就删除,how='all' 表示当所有值都是缺失值时才删除。比如,按列且全部为缺失值时才删除:df.dropna(axis=1, how='all')
通过输出可以看出,一列都没有被删除,因为不存在哪一列全部包含缺失值。
df.dropna(thresh=0.8*len(df), axis=1)
通过输出可以看出,有一列(protime)被删除了,因为从前面的缺失值百分比统计中我们知道,该列缺失值百分比超过40%,符合我们的删除条件。
subset 参数指定只考虑某些列。例如,假设我们只考虑“liver_big”的缺失值,且按行删除:df.dropna(subset=['liver_big'], axis=0)
从输出中我们可以看出有5行被删除了(示例数据集共150行,该操作之后还剩145行)。
inplace=True,则原 DataFrame 会被修改;如果 inplace=False(默认值),则会返回一个新的 DataFrame,原 DataFrame 不变。上面我们所做的所有删除都没有直接改变原始数据,因为 inplace 参数默认为 False。现在,假设我们就地修改原始 Dataframe:df.dropna(subset=['liver_big'], axis=0, inplace=True)
df.isna().sum() / len(df) * 100
从输出中可以看出,现在 liver_big 列的缺失值百分比为0。需要注意的是,inplace=False(默认值)时,dropna 方法会返回一个新的 Dataframe;而如果 inplace=True,则 dropna 方法不会返回任何值(实际返回 None),因为此时是对原始 Dataframe 进行更改,如果要看修改效果,直接看原始 Dataframe 即可。
在处理缺失值时,一个好的策略是用别的值替换它。以下是可以采用的替换策略:
要替换缺失值,可以使用以下三个函数:fillna()、replace()、interpolate()。对于 fillna() 函数,它会用传给它的参数替换所有的缺失值,比如,对于数值列,用平均值替换所有缺失值。要查看列的数据类型,我们可以用 Dataframe 的 dtypes 属性:
df.dtypes
Dataframe 支持按数据类型选择。比如,我们可以像下面这样选择所有数值列:
import numpy as np
numeric = df.select_dtypes(include=np.number)
numeric_columns = numeric.columns
numeric_columns
然后,我们可以使用平均值填充数值列的缺失值:
df[numeric_columns] = df[numeric_columns].fillna(df[numeric_columns].mean())
df.isna().sum() / len(df) * 100
从输出中可以看出,现在5个数值列的缺失值百分比均为0(因为我们已经用各自的均值填充了缺失值)。
类别列就是 dtypes 属性描述中为 object 的列。因此,我们可以选择所有对象列。这里,我们只考虑所有布尔列,但是从前面的数据展示中我们可以看出,class 列的类型也为 object,但是其实它的值是字符串类型。因此,我们可以先选择所有对象列,然后再从中删除 class 列,最后将所有结果类型转换为布尔类型:
boolean_columns = df.select_dtypes(include=object).columns.to_list()
boolean_columns.remove('class')
df[boolean_columns] = df[boolean_columns].astype('bool')
现在我们可以用出现频率最高的值来替换布尔列的缺失值,可以使用 mode() 函数计算每一列中出现频率最高的值。我们使用 fillna() 方法来替换缺失值,当然我们也可以使用 replace(old_value, new_value) 实现同样的效果:
df[boolean_columns].fillna(df.mode())
现在,我们再来统计每一列的缺失值百分比,应该都是0了:
df.isna().sum() / len(df) * 100
这里留一个小问题:如果使用
replace()方法,如何实现同样的替换效果?
替换缺失值的另一种解决方案涉及使用其他函数,例如线性插值。例如,在这个案例中,我们可以用前一列和下一列之间的插值替换中间列的缺失值。这可以通过使用 interpolate() 函数来实现。
由于我们前面已经替换了所有的缺失值,并且是直接对原始 Dataframe 进行更改,所以,现在我们需要重新加载数据集。
df = pd.read_csv('datasets/hepatitis.csv')
df.isna().sum() / len(df) * 100
同样地,我们只选择数值列:
numeric = df.select_dtypes(include=np.number)
numeric_columns = numeric.columns
df.head(10)
现在我们可以将 interpolate() 函数应用于数值列,同时设置限制方向为正向(forward)。这意味着线性插值从第一行开始应用,直到最后一行。
df[numeric_columns] = df[numeric_columns].interpolate(method='linear', limit_direction='forward')
例如,在第6行中,bilirubin 在插值之前是 NaN,填充后其值为0.95,这是在0.9(第5行)和1.0(第7行)之间的插值(即前后两行非空值的均值)。
df.head(10)
在这个部分中,我们看到了数据预处理的其中一个方面,即缺失值处理。缺失值可能会改变数据分析过程,因此必须对其进行管理。
处理缺失值通常有以下三个策略:
如果您想了解数据预处理的其他方面,如数据标准化和数据规范化,请持续关注我......如果您觉得我的文章对您有用,请点赞+分享,让更多的人加入进来,一起学习数据分析!
示例数据集获取:关注我,回复“肝炎数据集”关键字即可免费获取。实践出真知,赶快行动起来吧👏👏👏


原文作者:数据派探险家;如有侵权,请联系管理员删除。