查看原文
其他

数据治理 | 数据分析与清洗工具:Pandas 缺失值与重复值处理

企研数据 数据Seminar 2022-12-31
我们将在数据治理板块中推出一系列原创推文,帮助读者搭建一个完整的社科研究数据治理软硬件体系(详情见文末)。

目录:

一、前言
二、缺失值
        1. 缺失值处理方案
        2. 如何识别缺失值
        3. 使用 Pandas 处理缺失值
        (1)删除法处理缺失值
        (2)填充法处理缺失值
                ① 使用 df.fillna()
                ② 使用 df.replace()
三、重复值
        1. 如何检测重复值
        2. 删除重复值
四、总结


Part1前言

上期文章中,我们介绍了使用 Pandas 根据数据内容来筛选满足特定条件的数据,大家学习之后再也不用对着 Excel 一点一点手动筛选数据了。本期文章我们将学数据清洗非常重要的一步——缺失值和重复值的处理。缺失值和重复值对数据质量的影响非常大,本文将会向大家介绍如何使用 Pandas 处理缺失值和重复值,从此大家将免受缺失值,重复值的困扰!
本文中所有 Python 代码均在集成开发环境 Visual Studio Code (VScode) 中使用交互式开发环境 Jupyter Notebook 中编写。

Part2缺失值

1缺失值处理方案

提到缺失值,大家脑海中第一个出现的应该就是空值了。事实上空值的确是缺失值中的常客,我们也普遍认为缺失值一般泛指空值。虽然我们都知道什么是缺失值,但是处理缺失值却并没有那么简单,如何处理缺失值取决于缺失值的来源、数据的用途以及数据类型等各种因素。在 Pandas 中,缺失值的表现形式也并不唯一,例如 NaNNone 以及空字符''、空格' '、换行符 '\n'等等。
缺失数据千千万,对付他们要怎么办呢?缺失值处理的方案一般有以下三种:
① 删除法,删除含有一定量缺失值的行或列
② 填充法,使用其他值来替换缺失值
③ 不处理,当作没看见

对于不同的数据、不同的使用需求,我们一般使用不同的方法来处理缺失值。

删除法一般适用于稀疏数据(缺失数据占总数据的绝大多数)或关键数据缺失的数据;假设我们需要做一个 “某 APP 用户年龄与购买力的关系” 的数据分析,但是一些数据的用户年龄或者消费金额缺失了,那么这一条数据就失去了存在的意义,一般会选择删除。
填充法通常是使用最多的方案,这种方案适用于大多数使用场景。但同时填充法也是缺失值处理方案中最复杂的,我们将在下文介绍如何使用填充法填充缺失值。
不处理方案貌似是最鸡肋的处理方案了,但是有的时候,缺失值的出现是正常的甚至是必然的,比如一家存活的企业不会有注吊销日期,这种时候不处理或许就是最好的处理方法。

2如何识别缺失值

前面我们介绍了几种常见的缺失值,对于一个字段中的不同缺失值,我们处理的方式大多数时候是一样的,但是处理缺失值的前提是识别缺失值。对于常见缺失值 NaN (下文会介绍什么是 NaN)和 None,Pandas 提供了便利的方法来识别他们,df.iana() 及其别名 df.isnull() 是 Pandas 中判断缺失值的主要方法。下表是某年中国工业企业数据的一小部分,我们以此表为例,使用 Pandas 为大家展示如何处理缺失值。
使用 Pandas 读取数据并手动插入几个其他类型的缺失值。
import numpy as np
import pandas as pd
data = data = pd.read_excel('./数据/工业数据15条.xlsx')
data.loc[2, '行业大类名称'] = None   # 插入空值 None
data.loc[7, '企业名称'] = ''         # 插入空字符 ''
data.loc[0, '成立年份'] = '\n'       # 插入换行符 \n
data    # 输出查看 data
 
我们使用 df.iana() 检测以上数据中的缺失值,它可以让缺失值显示为 True,非缺失值显示为 False
data.isna()  # 或者使用 data.isnull(),pd.isna(data)
 
可以发现,缺失值 NaNNone 都被 Pandas 检测为缺失值,因为NaN 是 Numpy 模块的空值类型,表示为 np.nan,是 Not a Number 的简写,而 Pandas 是由 Numpy 开发而来的,所以保留了 NaN ,它在 Python 中是 float 类型(即小数型)数据;而 None 是 Python 中的空值对象,所以两者都会被判断为缺失值。但是 data 中的空字符,换行符却没有被判断为缺失值。这是因为他们都是字符型数据,Pandas 并不认为这些值是缺失值。但在实际的数据中,这些值又没有任何作用,可以被认为是缺失值。那我们怎么把这些值检测出来呢?
前面的文章中,我们介绍过字符处理大师——正则表达式。没错,对于这些字符型 “缺失值”,只需要一个简短的正则表达式就可以将他们识别出来。pd.isna() 可以判断一个值是否为空值或者判断一个 DataFrame 或 Series 中的值是否是缺失值,缺失值显示为 True,非缺失值显示为 False。下面我们分别使用 pd.isna() 和正则表达式来识别不同类型缺失值。
# 使用 pd.isna() 判断常规缺失值 NaN(np.nan)和 None
pd.isna(np.nan)  # 返回 True, NaN 被判断为缺失值
pd.isna(None)    # 返回 True, None 被判断为缺失值
pd.isna('')      # 返回 False,空字符不被判断为缺失值
pd.isna('\n')    # 返回 False,换行符不被判断为缺失值
pd.isna('\t')    # 返回 False,制表符不被判断为缺失值

# 使用正则匹配字符型缺失值(空值,无意义值)
# 有返回值,说明被判断为缺失值
re.search('^\s+$|(^$)''').group()   #返回 ''
re.search('^\s+$|(^$)''\n').group() #返回 '\n'
re.search('^\s+$|(^$)''\t').group() #返回 '\t'
# 无返回值,所以含非空数据的空字符不会被误判为缺失值
re.search('^\s+$|(^$)''ab cd\nef\tg')  # 无返回值
成功识别字符型缺失值后,我们就可以根据需要将他们替换为 Pandas 中的缺失值 NaN,然后就可以统一处理浮点型缺失值 NaN,也可以将 NaN 替换为 字符型缺失值空字符 ''。这样这些就可以参与到字符数据的运算中。

3使用 Pandas 处理缺失值

本节将会讲解 删除法处理缺失值填充法处理缺失值

(1)删除法处理缺失值

Pandas 提供了一个删除缺失值的方法 df.dropna(),他可以删除含有缺失值的行或列;可以删除全为缺失值的行或列;也可以删除缺失值数量大于某个阈值的行或列。
场景 1:删除含有缺失值的所有行
删除行需要指定参数 axis=0,删除列则指定参数axis=1;删除含有缺失值的数据需要指定参数 how='any',删除全为缺失值的数据则需要指定参数 how='all'。下面将不再多举例说明。
# 没有指定参数 inplace=True,所以该操作不会在原数据上生效,
# 而是返回一个处理后的新数据
data.dropna(how='any', axis=0)
 
注意,这里由于空字符'' 和 换行符等字符型缺失值不会被 Pandas 判断为缺失值,所以含有这些数据的行得以保留。

场景 2:删除缺失值数量大于 3 的所有列

df.dropna() 中的参数 thresh 可以指定非缺失值的数量(正整数),表示非缺失值的数量,当非缺失值数量小于这个整数值,这一行/列(需要根据 axis 参数来指定)会被删除。这个描述可能比较绕,我们通过这个使用场景来感受一下。
# 数据共有 15 行,缺失值数量大于 3 就等价于非缺失值数量不小于 12
data.dropna(axis=1, thresh=12)
删除前:
删除后:
可以看出,由于 “资产总计(万元)” 含有 4 个缺失值,故此列被删除。需要注意的是,当已经指定了 how 参数时,thresh 参数将不再生效,起作用的将会是 how 参数。
需要注意的是,df.dropna() 默认不会修改原始的数据,而是返回一个经过处理的新数据,所以即使刚刚删除了几行或者几列,数据 data 都没有发生变化。如果想要修改原数据,使删除操作在原始数据上生效,有以下两种方法。
# 第一种:设置该方法的 inplace 参数为 True
data.dropna(inplace=True)

# 第二种:将新生成的数据赋值给原始数据的变量名
data = data.dropna()
两种方法有着一样的效果,但是不能同时使用,否则不仅不能达到期待的效果,反而会误删数据。

(2)填充法处理缺失值

① 使用df.fillna() 

Pandas 专门为填充缺失值提供了一个方法 df.fillna(),他可以将缺失值替换为指定数据,也可以替换为缺失值附近的数据。替换为指定数据时,只需要传入一个要被替换为的数据即可;如果需要使用缺失值所在位置的前一个非缺失值来填充,只需要传入一个参数 method='ffill' 即可;使用后面一个非缺失值来填充时则需要传入参数 method='bfill'fillna()方法既可以在整个数据上应用,也可以指定数据区域应用,最常见使用场景的是填充某一列的缺失值。

场景  3:将数据中 "资产总计(万元)" 一列中的缺失值填充为 0

data['资产总计(万元)'].fillna(0)
这个结果是不是让人有一点小意外,虽然缺失值被填充为整数 0,但是填充后却是浮点型数值 0.0 ,而且返回的数据不是 DataFrame 类型,而是 Serise 类型。首先,整数 0 变为 浮点数 0.0 是因为字段 “资产总计(万元)” 的数据类型是 float 类型,其精度高于整数类型,当一列中的数据都是数值型时,Pandas 会自动将精度较低的数值类型转为精度更高的数值类型,即 float 型。其次,为什么只返回了一列数据,而且不是跟原始数据一样的 DataFrame 类型呢?因为 df.fillna() 方法与刚才介绍的 df.dropna() 方法一样,也和 Pandas 中绝大多数涉及到数据修改的方法一样,他们都不会直接修改原始的数据,而是返回一个新的数据。这就是为什么会返回填充后的数据,而返回 Serise 类型的原因是,调用 fillna() 方法的数据 data['资产总计(万元)'] 本身就是一个 Series ,所以会返回一个同样类型的数据。
同样地,如果想要使修改数据的操作在原始数据上生效,有以下两种方法。
# 第一种:设置该方法的 inplace 参数为 True
data['资产总计(万元)'].fillna(0, inplace=True)

# 第二种:将新生成的数据赋值给原始数据的变量名
data['资产总计(万元)'] = data['资产总计(万元)'].fillna(0)

场景 4:填充字段“资产总计(万元)”中的缺失值,要求是使用该字段中上一个非缺失值填充后面的缺失值

# 使用缺失值所在位置的前一个非缺失值来填充,需要传入参数 method='ffill'
data['资产总计(万元)'].fillna(method='ffill')
可以看到,该字段中所有缺失值都被前一个非缺失值填充。

② 使用df.replace() 

我们前面有讲到,缺失值不仅可以是 NaN 或者 None,还可以是空字符、空格以及换行符等字符型缺失值。但是缺失值填充方法 df.fillna() 只能识别和填充 NaN 和 None,对于字符型缺失值却束手无策。这个时候 Pandas 中数值替换方法 df.replace() 就可以很好地解决这个问题,该方法可以将 DataFrame 中几乎所有数据值(除 None 外)替换你想要的值, df.replace() 方法有许多种使用方法,这里只为大家介绍一种最常用,最简单易懂的使用方式。就像 Excel 中的查找替换功能一样,只需要先后传入替换前的值和替换后的值即可。

场景 5:将数据中所有的“采矿业”替换为“采矿产业”

# 将数据中的所有的“采矿业”替换为“采矿产业”
data.replace('采矿业''采矿产业')
  
有人可能会提问,将“矿业”替换为“矿产业”不是也能达到一样的效果吗?这样做在 Excel 中确实可行,但在 df.replace() 并不支持这种操作,因为该方法查找和替换的是数据值,而非数据值的某一部分。
使用 df.replace() 是可以填充一些特定的数据值了,可是字符型缺失值有很多种形式,这样一个一个查找、替换是不是效率太低了?请看下面这个例子。

场景 6:将数据中所有缺失数据替换为“数据缺失”

首先,使用 df.fillna() 填充缺失值。
# 填充缺失值 NaN 和 None, 设置参数 inplace=True,使操作生效
data.fillna('数据缺失', inplace=True)
# 由于设置了参数使替换生效,故不会有任何返回值,这里重新输出 data
data
  
可以看到,缺失值 NaN 和 None 已经成功被填充为指定值,但是空字符和换行符没有被填充。
使用 df.replace() 可以一次性将这些字符型缺失值处理掉。其原理是什么呢?很简单,df.replace() 方法是支持使用正则表达式的,我们将能够匹配所有字符型缺失值的正则表达式当作替换前的数据传入该方法,再设置参数 regex=True ,表示使用正则模式。就可以一次性替换字符型缺失值了。操作如下:
# 设置参数 regex=True ,表示使用正则模式
# 设置参数 inplace=True,使操作生效
# '^\s+$|(^$)' 是一个能够匹配所有字符型缺失值的正则表达式
data.replace('^\s+$|(^$)''数据缺失', regex=True, inplace=True)
data
  
字符型缺失值被成功替换。
通过以上几个缺失值填充案例,我们可以发现:df.fillna() 适合处理缺失值 NaN 和 None;而 df.replace() 的使用场景可以替换几乎所有值,尤其是字符型数据值。但是不能处理空值 None,所以这两个方法在缺失值填充方面是互补的。灵活使用他们,就可以填充或者修改所有缺失值。

Part3重复值

由于各种各样的原因,比如重复采集,误操作等,导致我们的数据可能存在数据重复的现象。包括行重复,列重复等情况,这其中即会出现完全重复,又会有部分字段重复的情况。大多时候这些数据并不是我们所需要的,需要将它们删除。下面将会介绍如何检测和删除这些重复值。
为了方便理解,我们使用较少的数据,如下图所示,该数据变量名为 score
如图,数据最后两行是完全重复的。

1如何检测重复值

Pandas 提供了一个用于检测重复值的方法:df.duplicated() 。该方法有两个主要的参数用来设置检测重复值的条件。用法为:
# 该方法返回一个 Series,重复的行会被标记为 True, 非重复行会被标记为 False
df.duplicated(subset=None, keep='first')
该方法返回一个 Series,重复的行会被标记为 True, 非重复行会被标记为 False。其中参数 subset 表示判断是否重复所需的列,例如当检测数据 score 的重复值时,传入参数 subset=['学号','姓名'] ,就表示使用数据的 “学号” 和 “姓名” 这两列来查重,这两列分别一样的数据行会被标记为重复数据。例如下图中红色矩形框内的三行数据。
尽管除了 “学号” 和 “姓名” 外的其他字段的数据并不是完全一样的,但是由于指定了使用这两列来查重,所以最后三行数据就会被判定为重复数据。
subset 参数的默认值是 None,使用默认值时,所有字段都会被用于查重,仅当所有字段都分别相同的数据行才会被认定是重复数据,例如数据 score 的最后两行。
参数 keep 用来确定如何标记重复值,可选的参数值及其含义如下:
'first' :默认的参数值,将除了第一次出现的重复值标记为 True,即首次出现的重复值不会被认定为重复值。
'last' :将除了最后一次出现的重复值标记为 True,即最后一次出现的重复值不会被认定为重复值。
False :将所有重复值标记为 True。

下面用一个例子来体会一下 df.duplicated()

场景 7:根据“学号”和“姓名”字段找到并取出所有的重复行。

# 根据要求,指定查重字段为 '学号' 和 '姓名',所以参数 subset=['学号', '姓名']
# 根据要求,需要找到所有的重复值,所以参数 keep=False
score.duplicated(subset=['学号''姓名'], keep=False)
 
这个结果缺失与数据中的重复情况是一致的,但这样还不够。我们只是找到了重复数据并打上了 True 标签,却并没有把数据取出来,其实取出打上 True 标签的数据只需要简单的一步就可以了。
# 把查重结果作为条件筛选的条件,使用数据筛选的方法就可以取出数据了
score[score.duplicated(subset=['学号''姓名'], keep=False)]
 

2删除重复值

Pandas 中的 df.drop_duplicates() 是专门用来删除重复值的方法,这个方法的原理就是根据 df.duplicated() 方法找出重复值后将它们删除。所以前者的主要参数和用法与后者是一样的。删除后返回一个新数据,不会直接修改原始数据。如果想要使删除操作在原始数据上生效,需要指定参数 inplace=True,相信大家对这个参数的使用已经比较熟悉了。

场景 8:对数据 score 做去重操作,重复的行数据只保留第一行

# 根据要求,我们只需要使用默认的参数值就可以了
# 指定参数 inplace=True,使操作在原始数据上生效
score.drop_duplicates(inplace=True)
score
 
数据 score 中第4,5两行(行索引为 3 和 4)是完全一样的,参数 keep 的默认值是 'first',表示保留重复数据中首次出现的一行,所以第四行(行索引为 3)得以保留,第五行(索引为 4)被删除。

Part4总结

这篇文章介绍了数据清洗中常用的数据清洗操作——处理缺失值与重复值。对于数据使用者来说,这些技能是必备的,即使我们不会专门去做类似的数据清洗,但是这些方法仍然被其他的数据需求所需要。
下期文章我们将继续为大家介绍 Pandas,学习如何转换数据类型,包括字符型,整数型,浮点型以及日期格式。




社科研究数据治理软硬件体系向下滑动查看更多

我们将在数据治理板块中推出一系列原创推文,帮助读者搭建一个完整的社科研究数据治理软硬件体系。该板块将涉及以下几个模块:

1. 计算机基础知识

(1) 社科研究软硬件体系搭建——虚拟化技术概述与实践

2. 编程基础

(1) 数据治理 | 带你学Python之 环境搭建与基础数据类型介绍篇

(2) 数据治理 | 带你学Python之控制结构与函数

(3) 数据治理 | 带你学Python之面向对象编程基础

(4) 数据治理 | 还在用Excel做数据分析呢?SQL它不香吗

(5) 数据治理 | 普通社科人如何学习SQL?一篇文章给您说明白

(6) 数据治理 |  如何实现SQL数据库的横向匹配

3. 数据采集

(1) 数据治理 | 快速get数据采集技能:理论知识篇

(2) 数据治理 | 数据采集实战:静态网页数据采集

(3) 数据治理 | 数据采集实战:动态网页数据采集

4. 数据存储

(1) 安装篇:数据治理 | 遇到海量数据stata卡死怎么办?这一数据处理利器要掌握

(2) 管理篇: 数据治理 | 多人协同处理数据担心不安全?学会这一招,轻松管理你的数据团队

(3) 数据导入:数据治理 | “把大象装进冰箱的第二步”:海量微观数据如何“塞进”数据库?

(4) 数据治理|Stata如何直连关系型数据库

5. 数据清洗

(1) 数据治理 | 数据清洗必备 — 正则表达式

(2) 数据治理 | 数据分析与清洗工具:Pandas基础

(3) 数据治理 | 数据分析与清洗工具:Pandas数据选取与修改

(4) 数据治理 | 数据分析与清洗工具:用Pandas快速选出你的“心之所向”

(5)本期内容:数据治理 | 数据分析与清洗工具:Pandas缺失值与重复值处理

6. 数据实验室搭建







数据Seminar




这里是大数据、分析技术与学术研究的三叉路口


 文 | 《大数据时代社科研究数据治理实务手册》


    欢迎扫描👇二维码添加关注    

点击下方“阅读全文”了解更多

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存