数据清理

Python
研究方法
作者

yangjh

发布于

2022年11月27日

为了提高数据分析的可靠性,我们必须数据清理,数据清理的主要任务包括:处理缺失值、识别和处理异常值、去除重复数据、检查数据类别、以及逻辑一致性检查。

空白值处理

许多数据分析和统计方法假设输入数据是完整的。空行或者空白值可能导致这些方法无法正确运行,或产生误导性的结果。

查找空白值

使用df[df.isnull().T.any()]可列出数据框df中所有包含空值的行。其中isnull()能够判断数据中元素是否为空值;T为行列转置;any()判断该行是否有空值。

import pandas as pd
from tabulate import tabulate
# 创建一个示例 DataFrame
data = {
    'Column1': [1, None, 3, None],
    'Column2': [4, None, None, None],
    'Column3': [7, 8, 9, None],
    'Column4': [7, 8, 9, None]
}
df = pd.DataFrame(data)
print("原始 DataFrame:")
print(tabulate(df))
# 列出所有空白值
print("列出所有空白值:")
print(tabulate(df[df.isnull().T.any()]))
原始 DataFrame:
-  ---  ---  ---  ---
0    1    4    7    7
1  nan  nan    8    8
2    3  nan    9    9
3  nan  nan  nan  nan
-  ---  ---  ---  ---
列出所有空白值:
-  ---  ---  ---  ---
1  nan  nan    8    8
2    3  nan    9    9
3  nan  nan  nan  nan
-  ---  ---  ---  ---

删除空白行

在 Python 中,使用 Pandas 库查找并删除空行的代码相对简单。一个“空行”通常指的是该行的所有列都是空值(例如 NaN 或 None)。

# 删除所有列都是空值的行
df.dropna(how='all', inplace=True)

# 打印处理后的 DataFrame
print("\n处理后的 DataFrame:")
print(df)

处理后的 DataFrame:
   Column1  Column2  Column3  Column4
0      1.0      4.0      7.0      7.0
1      NaN      NaN      8.0      8.0
2      3.0      NaN      9.0      9.0

dropna 是 Pandas 库中用于删除 DataFrame 或 Series 中的缺失值(如 NaN 或 None)的一个非常有用的方法。它提供了多种选项来定制缺失值的处理方式。以下是 dropna 方法的一些关键特性和使用方式:

dropna基本用法

DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)

其中:

  • axis指定应用操作的轴。axis=0'index':删除包含缺失值的行(默认)。 axis=1'columns':删除包含缺失值的列。
  • how:指定何时删除行或列。'any'(默认):只要有任何 NaN 值存在,就删除整行或整列。'all':当且仅当所有值都是 NaN 时,才删除整行或整列。
  • thresh:设置一个阈值,只有当非 NaN 值的数量少于这个阈值时,才删除行或列。
  • subset:在某些列或行的子集中查找缺失值。只有当这些特定的列或行满足 howthresh 条件时,才删除行(axis=0) 或列(axis=1)。
  • inplace:布尔值,表示是否在原地修改对象。False(默认):返回一个新的对象,原 DataFrame 不变。True:直接在原 DataFrame 上进行修改,不返回新对象。

Pandas修改数据框数据时的注意事项

Pandas中对数据框的绝大多数操作(例如删除空值)在默认情况下,不修改数据框自身数据。如果要得到修改后的结果,可用一个变量来进行保存,或者查看函数有无inplace参数,将inplace设置为True,则会对数据框本身进行修改。建议使用变量接收修改后的数据框,而不是直接修改原有数据框。

填充缺失值

在有些时候,我们简单地将某行数据删除,是不太理想的选择。我们可以使用一些值填充空值,如我们使用未知其他填充变量城市的值,使用0填充未填的多选题选项等等。

Pandas 提供了fillna方法填充缺失值,具体参数如下:

  • value:填充值,可以是标量,也可以是字典、系列以及数据框。还可以是能返回上述值的函数。如果为字典、系列或者数据框,则会替换字典、系列或者数据框中指定列或者单元格中的空值。
  • method:填充方式,其取值为{‘backfill’, ‘bfill’, ‘pad’, ‘ffill’, None}, 默认为Nonepad / ffill用上一个有效值填充,backfill / bfill用下一个有效值填充。
  • limit:指定填充空值的上限,默认为None
  • inplace:表示是否直接在原DataFrame修改。默认为False,即不修改原数据框。
  • axis:用于指定操作行还是列。0代表行,1代表列,默认为0
# 将 NaN 值替换为 0
df_filled = df.fillna(0)
print("\n填充后的 DataFrame:")
print(df_filled)

填充后的 DataFrame:
   Column1  Column2  Column3  Column4
0      1.0      4.0      7.0      7.0
1      0.0      0.0      8.0      8.0
2      3.0      0.0      9.0      9.0

识别和处理异常值

异常值可能是由于数据录入错误、测量错误或数据传输错误等原因造成的,处理这些值可以提高数据集的整体质量。异常值可能会扭曲统计分析的结果,如影响均值、标准差等统计量的计算,导致分析结果不准确。

类别变量异常值的识别

对于类别变量,可以通过描述统计,计算出每个变量不同取值出现的次数,这样,就可以看到类别变量的取值,是否是类别变量应有的值。比如,性别,应该只有男性和女性,如果出现其它类别,可能就是输入时产生的错误。

import pandas as pd

# 示例数据
data = {'颜色': ['红', '蓝', '绿', '红', '黄', '紫', '红', '绿', '蓝', '黄', '蓝色']}
df = pd.DataFrame(data)

# 频率分析
value_counts = df['颜色'].value_counts()
print(value_counts)

# 假设“蓝色”是一个拼写错误,它应该是“蓝”
# 这里“蓝色”可能是一个异常值
颜色
红     3
蓝     2
绿     2
黄     2
紫     1
蓝色    1
Name: count, dtype: int64

使用replace方法,可以替换取值。

# 将“颜色”列中的“蓝色”替换为“蓝”
df['颜色'] = df['颜色'].replace('蓝色', '蓝')

# 频率分析
value_counts = df['颜色'].value_counts()
print(value_counts)
颜色
红    3
蓝    3
绿    2
黄    2
紫    1
Name: count, dtype: int64

数值变量异常值的识别

识别数值变量的异常值是数据清理过程中的一个重要步骤。异常值(也称为离群点)是那些显著偏离数据集中其他数据点的值。以下是识别连续数值变量异常值的几种常用方法:

  1. 使用数据的均值和标准差来识别异常值。常见的规则是判定那些落在均值上下三个标准差之外的值为异常值。异常值 = mean ± 3 * std
  2. 利用四分位数(Q1, Q3)和四分位数范围(IQR = Q3 - Q1)来识别异常值,尤其适用于非正态分布的数据。异常值 < Q1 - 1.5 * IQR异常值 > Q3 + 1.5 * IQR

还可利用箱线图可视化地识别异常值。箱线图基于四分位数和中位数,其中箱子的“须”通常定义为 1.5 * IQR。箱线图外的点通常视为异常值。使用箱线图可视化识别异常值的原理,还是利用四分位数(Q1, Q3)和四分位数范围(IQR = Q3 - Q1)来识别异常值。

import pandas as pd

# 示例数据
data = {'value': [10, 12, 12, 13, 12, 11, 10, 29, 30, 11, 10, 100]}
df = pd.DataFrame(data)

# 计算均值和标准差
mean = df['value'].mean()
std = df['value'].std()
condition1 = (df['value'] < mean - 3 * std) | (df['value'] > mean + 3 * std)
# 识别异常值
outliers1 = df[condition1]
print('使用标准差判断的异常值为:',outliers1)

# 计算 IQR
Q1 = df['value'].quantile(0.25)
Q3 = df['value'].quantile(0.75)
IQR = Q3 - Q1

# 识别异常值
outliers2 = df[(df['value'] < Q1 - 1.5 * IQR) | (df['value'] > Q3 + 1.5 * IQR)]
print('使用IQR判断的异常值为:',outliers2)
使用标准差判断的异常值为:     value
11    100
使用IQR判断的异常值为:     value
7      29
8      30
11    100

判断为异常值的数值变量取值,可以做删除处理。

# 删除异常值
df_cleaned = df[~condition1]

print("清理后:", df_cleaned.shape,'清理前:',df.shape)
清理后: (11, 1) 清理前: (12, 1)

condition1 是一个布尔系列,表示每行数据是否是异常值。~condition1 是对这个条件取反,意味着选择非异常值的行。df[~condition1]则过滤掉了异常值,只保留了正常的数据。

重复值处理

在数据录入或数据合并时,可能会产生重复值,有的重复值是完全相同,有的重复值是个别变量值相同。在数据清理时,应区别对待。

查找重复值

duplicated() 函数的功能是检查数据中是否有重复值,用于标记 Series 中的值、DataFrame 中的记录行是否重复,重复为 True,不重复为 False。每行数据都是和它前面的记录相比较。该函数返回一个由布尔值组成的系列(Series)。

pandas.Series.duplicated(subset=None, keep='first')

keep参数的3种取值:

  • first:将重复项标记True,第一次出现的除外。
  • last:将重复项标记True,最后一次除外。
  • False:将所有重复项标记为True

subset用来指定特定的列,默认所有列。

删除重复项

  • 删除重复行使用drop_duplicates(),其参数说明如下:
    1. subset用来指定特定的列,默认所有列
    2. keep可以为firstlast,表示是选择最前一项还是最后一项保留,默认first
    3. inplace是直接在原来数据上修改还是保留一个副本,默认为False
    4. ingoore_index:生成数据的索引是元数据的,还是从0,1,2…到n-1的自然数排列

例如:

df.drop_duplicates(subset=['A','B'],keep='first',inplace=True)

变量类型检查

统计分析时,变量的类别决定了能采用的统计分析方法。适用于数值变量的统计分析方法,多数不适用于类别变量。故在数据分析前,对数据中涉及到的变量,要按照变量本身的特征正确合理的划分类别。

不同变量类别对应的pandas数据类型

  1. 类别变量对应category
  2. 数值变量对应整数int或浮点数float
  3. 字符串对应string
  4. 日期对应datetime64

查看数据类型

DataFrame.dtypes属性将返回DataFrame中所有变量的类型。

import pandas as pd
df_movie = pd.read_csv(R'../../data/csv/movie.csv')
df_movie.dtypes
id              float64
average         float64
country          object
genre            object
language         object
release_date     object
title            object
votes           float64
dtype: object

设定变量类别

pandas提供了astype方法,能按照传入的要求,对列中变量类型进行设置。

下面的语句,将df中的id列设为int类型,将release_date设为datetime64类型,将title列设为string类型,vote列设为int64类型:

df_movie.dropna(how='any',inplace=True)
df_movie.astype({"id": "int", "release_date": "datetime64[ns]", "title": "string", "votes": "int64"}).dtypes
id                       int32
average                float64
country                 object
genre                   object
language                object
release_date    datetime64[ns]
title           string[python]
votes                    int64
dtype: object

category 类别详解

在统计学中,类别变量的值,只能进行分类或者排序,不能进行加减乘除等运算。类别变量在pandas的数据框中,应该以category的数据类型存储。

类别变量相关方法

  1. 设置已有变量为类别变量。方法为dataframe.astype('category')可以将选定的变量设定为类别变量。例如df2 = df.astype({"产地": "category"})
  2. 获取类别变量所有的类别取值。方法为series.cat.categories,例如:df2['产地'].cat.categories
  3. 增加某个类别变量的类别。方法为series.cat.add_categories ,例如:df2['产地'].cat.add_categories(['俄罗斯'],inplace=True)
  4. 删除某个类别变量的类别。方法为series.cat.romove_categories,例如:df2['产地'].cat.remove_categories(['俄罗斯'],inplace=True)
  5. 修改某个类别变量的类别。方法为series.cat.add_categories,例如:df2.产地.cat.rename_categories({'印度': 'India',},inplace=True)

顺序变量的设定

顺序变量可以排序,因此在指定变量为顺序变量时,需要提供排序信息。

将已有变量设定为顺序变量

from pandas.api.types import CategoricalDtype
cat_type = CategoricalDtype(categories=['一星', '二星', '三星', '四星', '五星'], ordered=True)
df3['星级'].astype(cat_type)

通过as_ordered方法将已有变量设定为顺序变量

df2 = df.astype({"星级": "category"})
df2['星级'].cat.as_ordered()

使用cat.reorder_categories设定顺序变量

df[column].cat.reorder_categories(['Low', 'Medium', 'High'])

使用cut方法生成顺序变量

使用cut方法,能将数值变量按照设定的范围,转化为有序类别变量。

df_movie['星级'] = pd.cut(df['average'],
           bins=[0, 2, 4, 6, 8, 10],
           labels=['一星', '二星', '三星', '四星', '五星'])
df_movie['星级'].dtype
CategoricalDtype(categories=['一星', '二星', '三星', '四星', '五星'], ordered=True)

查看类别变量的编码值

使用cat对象中的codes属性,可以获取类别变量的编码值。

# 查看类别变量的编码值
df_movie['星级'].cat.codes.head(3)
0    4
1    4
2    4
dtype: int8

逻辑一致性

逻辑一致性检查是指通过变量之间的逻辑关系是否合理进行数据有效性的检查。例如调查对象在填写问卷时,如果没有认真阅读题目及选项内容,可能会产生前后矛盾的回答,如在选择了没有子女的情况下,又填写了子女教育的支出估计范围。为了提高数据的质量,研究人员最好在设计问卷时就准备了一些逻辑上相关的冗余性的题目,用来筛选或排除存在内在逻辑错误的问卷。

逻辑一致性检查方法

逻辑一致性检查可以通过构造pandas查询条件或索引进行检查。

import pandas

# 打开SPSS文件
df_raw = pandas.read_spss(r'../../data/sav/identity.sav')

# 使用query
condition = '会以中国人自豪吗=="会" and 会隐瞒身份吗=="一定会"'

# 取得逻辑不一致数据的序号列表
result = df_raw.query(condition).index
print(result)
# 使用drop函数删除指定
df_raw.drop(index=result,inplace=True)
Index([ 54,  70,  75, 161, 191, 236, 240, 251, 323, 338, 406, 428, 525, 576,
       578, 579, 593, 595, 607, 665, 667, 679, 764, 770, 783, 784, 809, 839,
       869, 899],
      dtype='int64')
import pandas as pd

# 示例数据
data = {
    '年龄': [25, 30, -5, 40],
    '入职日期': ['2020-01-01', '2019-05-20', '2021-07-15', '2018-03-10'],
    '离职日期': ['2021-01-01', '2020-05-20', None, '2019-03-10']
}
df = pd.DataFrame(data)

# 范围检查:年龄应该大于 0
df['年龄检查'] = df['年龄'] > 0

# 格式检查:检查日期格式是否正确(这里简化为检查是否为 None)
df['入职日期检查'] = df['入职日期'].notna()
df['离职日期检查'] = df['离职日期'].notna()

# 逻辑关系检查:入职日期应该早于或等于离职日期
df['日期逻辑检查'] = pd.to_datetime(df['入职日期']) <= pd.to_datetime(df['离职日期'])

print(tabulate(df, headers='keys'))
      年龄  入职日期    离职日期    年龄检查    入职日期检查    离职日期检查    日期逻辑检查
--  ------  ----------  ----------  ----------  --------------  --------------  --------------
 0      25  2020-01-01  2021-01-01  True        True            True            True
 1      30  2019-05-20  2020-05-20  True        True            True            True
 2      -5  2021-07-15              False       True            False           False
 3      40  2018-03-10  2019-03-10  True        True            True            True

参考文献

  1. pandas.DataFrame.duplicated — pandas 1.4.3 documentation (pydata.org)
  2. Categorical data — pandas 1.4.3 documentation (pydata.org)
  3. pandas.DataFrame.drop — pandas 1.4.3 documentation (pydata.org)
  4. Indexing and selecting data — pandas 1.5.1 documentation (pydata.org)