数据清洗是任何数据分析过程中关键的一部分。这是你删除错误、处理丢失数据,并确保你的数据以可处理的格式存在的步骤。如果没有进行良好清理的数据集,任何后续的分析可能会出现偏差或错误。
本文将为您介绍在Python中进行数据清洗的几个关键技术,使用强大的库如pandas,numpy,seaborn和matplotlib。
了解数据清洗的重要性
在深入了解数据清洗的机制之前,让我们先了解其重要性。现实世界的数据往往是杂乱的。它可能包含重复条目、错误或不一致的数据类型、缺失值、不相关的特征和异常值。所有这些因素在分析数据时可能导致误导性的结论。这使得数据清洗成为数据科学生命周期中不可或缺的一部分。
我们将涵盖以下数据清洗任务。
在Python中设置数据清洗
在开始之前,让我们导入必要的库。我们将使用pandas进行数据操作,seaborn和matplotlib进行可视化。
我们还将导入datetime Python模块来处理日期。
import pandas as pd
import seaborn as sns
import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
加载和检查数据
首先,我们需要加载数据。在这个例子中,我们将使用pandas加载一个CSV文件。我们还添加了分隔符参数。
df = pd.read_csv('F:\\VoAGI\\KDN Mastering the Art of Data Cleaning in Python\\property.csv', delimiter= ';')
接下来,重要的是检查数据以了解其结构,我们正在处理何种类型的变量以及是否存在任何缺失值。由于导入的数据不是很大,让我们来看看整个数据集。
# 查看DataFrame的所有行display(df)
数据集如下所示。
您可以立即看到存在一些缺失值。另外,日期格式不一致。
现在,让我们使用info()方法查看DataFrame的摘要。
# 获取DataFrame的简洁摘要print(df.info())
以下是代码输出。
我们可以看到只有列square_feet没有任何NULL值,因此我们需要以某种方式处理它。此外,列advertisement_date和sale_date是对象数据类型,尽管应该是日期格式。
列location完全为空。我们需要它吗?
我们将向您展示如何处理这些问题。我们将从学习如何删除不必要的列开始。
删除不必要的列
数据集中有两列在我们的数据分析中不需要,所以我们将删除它们。
第一列是buyer。我们不需要它,因为购买者的姓名不影响分析。
我们使用drop()方法指定列名进行删除。我们将轴设置为1以指定删除列。另外,inplace参数设置为True,这样我们就可以修改现有的DataFrame,而不是创建一个没有删除列的新DataFrame。
df.drop('buyer', axis = 1, inplace = True)
我们想要删除的第二列是location。虽然可能有用使用这个信息,但这是一个完全为空的列,所以让我们将其删除。
我们采用与第一栏相同的方法。
df.drop('location', axis = 1, inplace = True)
当然,您也可以同时删除这两列。
df = df.drop(['buyer', 'location'], axis=1)
这两种方法都会返回以下数据框。
处理重复数据
数据集中可能存在重复数据,原因各有不同,会对分析结果产生偏差。
我们来检测数据集中的重复数据,下面是具体方法。
下面的代码使用duplicated()方法来检测整个数据集中的重复数据。其默认设置是把第一次出现的值视为唯一值,将随后的出现视为重复值。您可以使用keep参数来修改此行为。例如,df.duplicated(keep=False)将把所有重复值标记为True,包括第一次出现的值。
# 检测重复数据duplicates = df[df.duplicated()]duplicates
下面是输出结果。
索引为3的行被标记为重复值,因为其具有与第2行相同的值,而第2行是其首次出现。
现在我们需要删除重复值,可以使用以下代码实现。
# 删除重复值duplicates = df[df.duplicated()]duplicates
drop_duplicates()函数在识别重复值时会考虑所有列。如果您只想考虑特定的某些列,可以将其作为列表传递给该函数,如:df.drop_duplicates(subset=[‘column1’, ‘column2’])。
如您所见,重复的行已被删除。不过,索引保持不变,索引为3的行消失了。我们可以通过重新设置索引来整理数据。
df = df.reset_index(drop=True)
此任务可通过使用reset_index()函数来完成,设置drop=True参数可丢弃原有的索引。如果不包含此参数,旧索引将会作为一个新列添加到DataFrame中。通过设置drop=True,您告诉pandas要忘记旧索引,并将其重置为默认的整数索引。
为了练习,请尝试从此Microsoft数据集中删除重复值。
数据类型转换
有时候,数据类型可能设置错误。例如,日期列可能被解释为字符串。您需要将其转换为适当的类型。
在我们的数据集中,我们将为广告日期和销售日期这两列进行转换,因为它们显示为object数据类型。而且,这些日期在不同行之间的格式也有所差异。我们需要保持一致,并将其转换为日期类型。
最简单的方法是使用to_datetime()方法。同样地,您可以逐列进行转换,如下所示。
在进行转换时,我们将dayfirst参数设置为True,因为有些日期以日为开头。
# 将广告日期列转换为日期类型df['advertisement_date'] = pd.to_datetime(df['advertisement_date'], dayfirst = True)# 将销售日期列转换为日期类型df['sale_date'] = pd.to_datetime(df['sale_date'], dayfirst = True)
您也可以使用apply()方法与to_datetime()同时转换这两列。
# 将广告日期和销售日期两列同时转换为日期类型df[['advertisement_date', 'sale_date']] = df[['advertisement_date', 'sale_date']].apply(pd.to_datetime, dayfirst = True)
这两种方法都会给您相同的结果。
现在日期的格式是一致的。我们可以看到并不是所有的数据都已经转换。advertisement_date中有一个NaT值,sale_date中有两个NaT值。这意味着日期缺失。
我们可以使用info()方法检查这些列是否已经转换为日期格式。
# 获取DataFrame的简明摘要print(df.info())
如您所见,这两列都不是datetime64[ns]格式。
现在,请尝试将这个Airbnb数据集中的数据从文本转换为数字。
处理缺失数据
现实世界的数据集经常会有缺失值。处理缺失数据是至关重要的,因为某些算法无法处理这样的值。
我们的示例也有一些缺失值,因此让我们看看处理缺失数据的两种常见方法。
删除缺失值的行
如果缺失数据的行数相对于总观测数来说不重要,您可以考虑删除这些行。
在我们的示例中,最后一行除了square feet和advertisement date之外没有任何值。我们无法使用这样的数据,因此让我们删除这行。
下面是指示删除行的代码。
df = df.drop(8)
现在DataFrame的样子如下。
最后一行已被删除,我们的DataFrame现在看起来更好了。但是,仍然有一些缺失数据,我们将使用另一种方法处理。
填补缺失值
如果您有重要的缺失数据,与删除相比,更好的策略可能是填补缺失值。这个过程涉及使用其他数据填补缺失值。对于数值数据,常见的填补方法包括使用集中趋势测量(均值、中位数、模式)。
在我们已经修改过的DataFrame中,advertisement_date和sale_date列中有NaT(不是时间)值。我们将使用mean()方法来填补这些缺失值。
代码使用fillna()方法来查找并用平均值填补空值。
# 为数值列填充值df['advertisement_date'] = df['advertisement_date'].fillna(df['advertisement_date'].mean())df['sale_date'] = df['sale_date'].fillna(df['sale_date'].mean())
您也可以用一行代码完成相同的操作。我们使用apply()来应用通过lambda定义的函数。和上面一样,这个函数使用fillna()和mean()方法来填补缺失值。
# 为多个数值列填充值df[['advertisement_date', 'sale_date']] = df[['advertisement_date', 'sale_date']].apply(lambda x: x.fillna(x.mean()))
两种情况下的输出结果如下。
我们的sale_date列现在有了不需要的时间信息。让我们将它们删除。
我们将使用strftime()方法,将日期转换为其字符串表示和指定的格式。
df['sale_date'] = df['sale_date'].dt.strftime('%Y-%m-%d')
现在日期看起来都整洁了。
如果您需要在多列上使用strftime(),您可以再次使用lambda的方式。
df[['date1_formatted', 'date2_formatted']] = df[['date1', 'date2']].apply(lambda x: x.dt.strftime('%Y-%m-%d'))
现在,让我们看一下如何填补缺失的分类值。
分类数据是一种用于分组具有相似特征的信息的数据类型。这些组中的每个都是一个类别。分类数据可以采用数字值(例如“1”表示“男性”,“2”表示“女性”),但这些数字没有数学意义。例如,您不能将它们相加。
分类数据通常分为两个类别:
- 名义数据:这是指类别仅仅标记,不能按任何特定顺序排列。例如性别(男性、女性),血型(A、B、AB、O)或颜色(红色、绿色、蓝色)。
- 有序数据:这是指类别可以排序或排名。虽然类别之间的间隔不均匀,但类别的顺序具有意义。例如评级标尺(电影评级为1到5分),教育水平(高中、本科、研究生)或癌症阶段(第一阶段、第二阶段、第三阶段)。
对于填补缺失的分类数据,通常使用众数。在我们的示例中,列property_category是分类(名义)数据,并且有两行数据缺失。
让我们用众数替换缺失的值。
# 对于分类列df['property_category'] = df['property_category'].fillna(df['property_category'].mode()[0])
此代码使用fillna()函数来替换property_category列中的所有NaN值。它将其替换为众数。
此外,[0]部分用于从该Series中提取第一个值。如果有多个众数,则会选择第一个。如果只有一个众数,它仍然有效。
这是输出。
数据现在看起来相当不错了。唯一剩下的是检查是否存在异常值。
您可以在这个 Meta interview question 上练习处理空值,您需要用零替换NULL。
处理异常值
异常值是数据集中与其他观察值明显不同的数据点。它们可能远离数据集中的其他值,处于整体模式之外。由于其值明显较高或较低于其他数据,它们被认为是异常的。
异常值可能出现的原因包括:
- 测量或输入错误
- 数据损坏
- 真实的统计异常
异常值可能会对数据分析和统计建模的结果产生重大影响。它们可能导致分布偏斜、偏差或使底层统计假设失效、扭曲估计的模型适合度、降低预测模型的预测准确性,并导致错误的结论。
常用于检测异常值的一些方法包括Z分数、IQR(四分位数范围)、箱线图、散点图和数据可视化技术。在一些高级案例中,还使用机器学习方法。
可视化数据可以帮助识别异常值。Seaborn的boxplot很方便进行此操作。
plt.figure(figsize=(10, 6))sns.boxplot(data=df[['advertised_price', 'sale_price']])
我们使用plt.figure()来设置图形的宽度和高度(以英寸为单位)。
接下来,我们创建广告价格和销售价格的箱线图,如下所示。
通过将以下代码添加到上述代码中,可以改进绘图以更易使用。
plt.xlabel('价格')plt.ylabel('美元')plt.ticklabel_format(style='plain', axis='y')formatter = ticker.FuncFormatter(lambda x, p: format(x, ',.2f'))plt.gca().yaxis.set_major_formatter(formatter)
我们使用上述代码来设置两个轴的标签。我们还注意到,y轴上的值是科学计数法,而我们无法将其用于价格值。因此,我们使用plt.ticklabel_format()函数将其更改为普通样式。
然后,我们创建一个格式化程序,该格式化程序将使用逗号作为千位分隔符和小数点来显示y轴上的值。最后一行代码将此应用于轴。
现在的输出如下所示。
那么,我们如何识别并删除异常值?
其中一种方法是使用IQR方法。
IQR,即四分位距,是一种用于通过将数据集分为四分位数来衡量变异性的统计方法。四分位数将排序后的数据集分为四等份,第一四分位数(第25百分位数)和第三四分位数(第75百分位数)之间的值构成四分位距。
四分位距用于识别数据中的异常值。以下是它的工作原理:
- 首先,计算第一四分位数(Q1)、第三四分位数(Q3),然后确定IQR。IQR计算公式为Q3 – Q1。
- 任何小于Q1 – 1.5IQR或大于Q3 + 1.5IQR的值都被认为是异常值。
在我们的箱线图中,箱子实际上代表了IQR。箱子内部的线是中位数(或第二四分位数)。箱线图的“须”表示距离Q1和Q3的1.5*IQR范围内的值。
这些须之外的任何数据点都可以视为异常值。在我们的例子中,它是1200万美元的价值。如果您查看箱线图,您将清楚地看到这是如何表示的,这显示了为什么数据可视化在检测异常值中很重要。
现在,让我们通过使用Python代码中的IQR方法来删除异常值。首先,我们将删除广告价格的异常值。
Q1 = df['advertised_price'].quantile(0.25)Q3 = df['advertised_price'].quantile(0.75)IQR = Q3 - Q1df = df[~((df['advertised_price'] < (Q1 - 1.5 * IQR)) |(df['advertised_price'] > (Q3 + 1.5 * IQR)))]
我们首先使用quantile()函数计算第一四分位数(或第25百分位数)。对于第三四分位数或第75百分位数,我们也是一样的。
它们显示了数据中25%和75%以下的值,分别。
然后我们计算四分位数之间的差异。到目前为止,我们只是将IQR步骤转换为Python代码。
作为最后一步,我们去除异常值。换句话说,所有小于Q1 – 1.5 * IQR或大于Q3 + 1.5 * IQR的数据。
“~”操作符取反条件,因此我们只留下不是异常值的数据。
然后我们可以对销售价格进行同样的操作。
Q1 = df['sale_price'].quantile(0.25)Q3 = df['sale_price'].quantile(0.75)IQR = Q3 - Q1df = df[~((df['sale_price'] < (Q1 - 1.5 * IQR)) |(df['sale_price'] > (Q3 + 1.5 * IQR)))]
当然,您可以使用for循环以更简洁的方式来完成此操作。
对于['advertised_price', 'sale_price']列中的每一列:
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
df = df[~((df[column] < (Q1 - 1.5 * IQR)) |(df[column] > (Q3 + 1.5 * IQR)))]
循环迭代两列。对于每一列,它计算IQR,然后从DataFrame中删除行。
请注意,此操作按顺序执行,首先针对advertised_price,然后针对sale_price。因此,对于每个列,DataFrame在原地修改,可能会删除由于在任一列中为异常值而删除的行。因此,此操作可能导致比将广告价格和销售价格异常值分别删除并随后合并结果后的行数少。
在我们的示例中,两种情况下输出都是相同的。为了查看箱线图的变化,我们需要使用与之前相同的代码再次绘制它。
plt.figure(figsize=(10, 6))
sns.boxplot(data=df[['advertised_price', 'sale_price']])
plt.xlabel('价格')
plt.ylabel('美元')
plt.ticklabel_format(style='plain', axis='y')
formatter = ticker.FuncFormatter(lambda x, p: format(x, ',.2f'))
plt.gca().yaxis.set_major_formatter(formatter)
这是输出结果。
你可以通过解决"General Assembly面试问题"来练习在Python中计算百分位数。
结论
数据清洗是数据分析过程中的关键步骤。虽然可能耗费时间,但确保你的发现准确是非常重要的。
幸运的是,Python丰富的库生态系统使这个过程更加容易处理。我们学习了如何删除不必要的行和列,重新格式化数据,并处理缺失值和异常值。这些是在任何数据上都必须执行的常规步骤。但是,有时你也需要将两列组合成一列,验证现有数据,为其分配标签,或者消除空白。
所有这些都是数据清洗的一部分,它可以将混乱的现实数据转变为结构良好的数据集,让你可以自信地进行分析。只需比较我们开始的数据集和结束的数据集。
如果你对这个结果没有满意,并且干净的数据不能激发你的兴奋,你在做数据科学是为了什么?Nate Rosidi是一位数据科学家,负责产品战略。他还是一位教授分析学的兼职教师,并创立了StrataScratch,这是一个帮助数据科学家准备来自顶级公司的真实面试题的平台。与他在Twitter:StrataScratch上联系,或与他在LinkedIn上联系。