【学习笔记】《深入浅出Pandas》第10章:Pandas数据清洗

10.1 缺失值的认定

10.1.1 缺失值类型

一般使用特殊类型NaN代表缺失值,可以用Numpy定义为np.NaN或np.nan。在Pandas 1.0以后的版本中,实验性地使用标量pd.NA来代表。

# 原数据
df = pd.DataFrame({
    'A': ['a1', 'a1', 'a2', 'a2'],
    'B': ['b1', 'b2', None, 'b2'],
    'C': [1, 2, 3, 4],
    'D': [5, 6, None, 8],
    'E': [5, None, 7, 8]
})
"""
	A	B		C	D	E
0	a1	b1		1	5.0	5.0
1	a1	b2		2	6.0	NaN
2	a2	None	3	NaN	7.0
3	a2	b2		4	8.0	8.0
"""

以上数据中,2B、2D和1E为缺失值。字符串类型的缺失值表示为None,浮点型的缺失值表示为NaN。
如果想把正负无穷大也当作缺失值,可以通过全局配置来设定:

# 将无穷值设置为缺失值
pd.options.mode.ues_inf_as_na = True

10.1.2 缺失值判断

df.isna()及其别名df.isnull()是Pandas中判断缺失值的主要方法,对整个数据进行缺失值判断,True为缺失:

# 检测缺失值
df.isna()

"""

		A		B		C		D		E
0	False	False	False	False	False
1	False	False	False	False	True
2	False	True	False	True	False
3	False	False	False	False	False
"""
# 只对某个列进行缺失值检测
df.D.isna()
"""
0    False
1    False
2     True
3    False
Name: D, dtype: bool
"""

反之,df.notna()可以让非缺失值显示为True,让缺失值显示为False:

# 检测非缺失值
df.notna()
"""
		A		B		C		D		E
0	True	True	True	True	True
1	True	True	True	True	False
2	True	False	True	False	True
3	True	True	True	True	True
"""
# 检测某列的非缺失值
df.D.notna()

10.1.3 缺失值统计

sum可以统计数据中的缺失值,计算时将False当作0、将True当作1。

# 布尔值的求和
pd.Series([True, True, False]).sum() # 2
# 计算数据中每列的缺失值情况
df.isnull().sum()
"""
A    0
B    1
C    0
D    1
E    1
dtype: int64
"""
# 计算数据中每行的缺失值情况
df.isnull().sum(1)
"""
0    0
1    1
2    2
3    0
dtype: int64
"""
# 计算总缺失值
df.isna().sum().sum() # 3

10.1.4 缺失值筛选

# 返回有缺失值的行
df.loc[df.isna().any(1)] # df.isna().any(1) 行方向上,只要有一个缺失值就是True
"""
	A	B		C	D	E
1	a1	b2		2	6.0	NaN
2	a2	None	3	NaN	7.0
"""
# 返回有缺失值的列
df.loc[:, df.isna().any()] # 将表达式放在loc的列位上
"""
	B		D	E
0	b1		5.0	5.0
1	b2		6.0	NaN
2	None	NaN	7.0
3	b2		8.0	8.0
"""

如果想要查询没有缺失值的行和列,可以对表达式取反:

# 没有缺失值的行
df.loc[~(df.isna().any(1))]

# 没有缺失值的列
df.loc[:, ~(df.isna().any())]

10.1.5 NA标量

pd.NA是一个专门表示缺失值的标量,代表空整数、空布尔、空字符。它提供了一个“缺失值”指示器,该指示器可以在各种数据类型中一致使用。

s = pd.Series([1, 2, None, 4], dtype="Int64")
"""
0       1
1       2
2    <NA>
3       4
dtype: Int64
"""
s[2] is pd.NA # True

pd.NA本身也是一个缺失值:

pd.isna(pd.NA) # True

以下是pd.NA参与运算的一些逻辑示例:

# 加法
pd.NA + 1 # <NA>

# 乘法
'a' * pd.NA # <NA>

pd.NA ** 0 # 1
1 ** pd.NA # 1

以下是其比较运算的示例:

pd.NA == 1 # <NA>
pd.NA == pd.NA # <NA>

10.1.6 时间数据中的缺失值

NaT表示时间数据中的缺失值,NaT和NaN是兼容的。

pd.Series([pd.Timestamp('20200101'), None, pd.Timestamp('20200103')])
"""
0   2020-01-01
1          NaT
2   2020-01-03
dtype: datetime64[ns]
"""

10.1.7 整型数据中的缺失值

NaN是浮点型,因此缺少一个整数的列可以转换为整型。

# NaN是浮点型
type(df.at[2, 'D']) # (2, 'D')位置上是NaN
# numpy.float64
pd.Series([1, 2, np.nan, 4], dtype=pd.Int64Dtype())
"""
0       1
1       2
2    <NA>
3       4
dtype: Int64
"""

10.1.8 插入缺失值

通过赋值即可完成插入。

df.loc[0] = None # 修改索引为0的行 a1/b1 -> None ,int/float -> NaN
df.loc[1] = np.nan # 修改索引为1的行 all -> NaN
df.A = pd.NA # 修改A列 all -> <NA>
# 原数据列
"""
	A	B		C	D	E
0	a1	b1		1	5.0	5.0
1	a1	b2		2	6.0	NaN
2	a2	None	3	NaN	7.0
3	a2	b2		4	8.0	8.0
"""

# new
""" 
		A	B		C	D	E
0	<NA>	None	NaN	NaN	NaN
1	<NA>	NaN		NaN	NaN	NaN
2	<NA>	None	3.0	NaN	7.0
3	<NA>	b2		4.0	8.0	8.0
"""

10.2 缺失值的操作

10.2.1 缺失值填充

df.fillna(x)可以将缺失值填充为指定的值:

df.fillna(0) # 填充为0
df.fillna('missing') # 填充为指定字符
df.one.fillna('暂无') # 指定字段填充
df.one.fillna(0, inplace) # 指定字段填充
df.fillna(0, limit=1) # 只替换第一个

# 将不同列的缺失值替换为不同的值
values = {'A': 0, 'B': 1, 'C': 2, 'D': 3}
df.fillna(value=values)

需要注意的是,如果想让填充立即生效,需要重新为df赋值或传入参数inplace=True。

有时不能填入固定值,而要按照一定方法填充。df.fillna()提供了method参数:

  • pad/ffill:向前填充,使用前一个有效值填充,df.fillna(method=‘ffill’)可以简写为df.ffill();
  • bfill/backfill:向后填充,使用后一个有效值填充,df.fillna(method=‘bfill’)可以简写为df.bfill()。

除了取前后值,还可以取经过计算得到的值,比如平均值填充法:

# 填充列的平均值
df.fillna(df.mean())

# 对指定列填充平均值
df.fillna(df.mean()['B':'C'])

# 另一种填充列的平均值方法
df.where(pd.notna(df), df.mean(), axis='columns')

缺失值填充的另一个思路是使用替换方法df.replace():

# 将指定列的空值替换成指定值
df.replace({'toy': {np.nan: 100}})

10.2.2 插值填充

插值(interpolate)是离散函数拟合的重要方法,利用它可根据函数在有限个点处的取值状况,估算出函数在其他点处的近似值。Series和DataFrame对象都有interpolate()方法,默认情况下,该方法在缺失值处执行线性插值。它利用数学方法来估计缺失点的值,对于较大的数据非常有用。

s = pd.Series([0, 1, np.nan, 3])

# 插值填充
s.interpolate()
"""
0    0.0
1    1.0
2    2.0
3    3.0
dtype: float64
"""

其中默认method=‘linear’,即使用线性方法,认为数据呈现一条直线。method方法指定的是插值的算法。
如果数据增长速率越来越快,可以选择method='quadratic’二次插值;如果数据集呈现出累计分布的样子,选择method=‘pchip’;如果需要填补默认值,以平滑绘图为目标,选择method=‘akima’。

10.2.3 缺失值删除

df.dropna()方法可以删除缺失值:

# 删除有缺失值的行
df.dropna()

# 删除有缺失值的列
df.dropna(1)
df.dropna(axis='columns')
df.dropna(axis=1)

df.dropna(how='all') # 删除所有值都缺失的行
df.dropna(thresh=2) # 删除至少有两个缺失值的行
df.dropna(subset=['name', 'born']) # 指定判断缺失值的列范围
df.dropna(inplace=True) # 使删除的结果生效
df.col.dropna() # 指定列的缺失值删除

注意,df.dropna()操作不能替换原来的数据。如果需要替换,可以重新赋值或者传入参数inplace=True。

10.2.4 缺失值参与计算

(1)加法:忽略缺失值(字符串型),或者将其按0处理(数字)。

# 原数据
df = pd.DataFrame({
    'A': ['a1', 'a1', 'a2', 'a2'],
    'B': ['b1', 'b2', None, 'b2'],
    'C': [1, 2, 3, 4],
    'D': [5, 6, None, 8],
    'E': [5, None, 7, 8]
})
df.sum()
"""
A    a1a1a2a2
C          10
D          19
E          20
dtype: object
"""

(2)累加、累乘:忽略NA值,但值会保留在序列中。使用参数skipna=False会跳过有缺失值的计算并返回缺失值。

# 累加
df.D.cumsum()
"""
0     5.0
1    11.0
2     NaN
3    19.0
Name: D, dtype: float64
"""
# 累加 并跳过缺失值
df.D.cumsum(skipna=False)
"""
0     5.0
1    11.0
2     NaN
3     NaN
Name: D, dtype: float64
"""

(3)统计:df.count()在统计时,缺失值不计数。

df.count()
"""
A    4
B    3
C    4
D    3
E    3
dtype: int64
"""

(4)聚合分组:如果聚合分组的列里有空值,则会自动忽略这些值。如果需要计入有空值的分组,可传入dropna=False。

# 聚合,空值忽略
df.groupby('B').sum()
"""
	C	D		E
B			
b1	1	5.0		5.0
b2	6	14.0	8.0
"""
# 聚合,计入缺失值
df.groupby('B', dropna=False).sum()
"""
	C	D		E
B				
b1	1	5.0		5.0
b2	6	14.0	8.0
NaN	3	0.0		7.0
"""

10.3 数据替换

Pandas中数据替换的方法包含数值、文本、缺失值等替换,经常用于数据清洗与整理、枚举转换、数据修正等情况。Series和DataFrame的replace()都提供了一种高效灵活的方法。

10.3.1 指定值替换

# Series的替换
ser = pd.Series([0., 1., 2., 3., 4.])
ser.replace(0, 5) # 0 -> 5
# 批量替换
ser.replace([0, 1, 2, 3, 4], [4, 3, 2, 1, 0]) # 一一对应进行替换
ser.replace({0: 10, 1: 100}) # 用字典映射对应替换值,01这两个值替换成10100,如果值不存在,则不进行操作
ser.replace({'a': 0, 'b': 5}, 100) # 将a列的0、b列的5替换成100
df.replace({'a': {0: 100, 4: 400}}) # 指定列里的替换规则

10.3.2 使用替换方式

# 将123替换为它们前一个值 
ser = pd.Series([0., 1., 2., 3., 4.])
ser.replace([1, 2, 3], method='pad') # ffill效果相同 1->3
"""
0    0.0
1    0.0
2    0.0
3    0.0
4    4.0
dtype: float64
"""
# 将123替换为它们后一个值 
ser = pd.Series([0., 1., 2., 3., 4.])
ser.replace([1, 2, 3], method='bfill') # 3->1
"""
0    0.0
1    4.0
2    4.0
3    4.0
4    4.0
dtype: float64
"""

10.3.3 字符替换

替换方法默认没有开启正则匹配模式,直接按原字符进行匹配替换,如果如果字符规则比较复杂的内容,可以使用正则表达式进行匹配。

# bat -> new 不使用正则表达式
df.replace(to_replace='bat', value='new')

# ba开头的值 -> new 使用正则表达式
df.replace(to_replace=r'^ba.$', value='new', regex=True)
# 如果多列规则不一,可以按以下格式传入
df.replace({'A': r'^ba.$'}, {'A': 'new'}, regex=True)

# 多个规则均替换相同的值
df.replace(regex=[r'^ba.$', 'foo'], value='new')

# 多个正则及对应的替换内容
df.replace(regex={r'^ba.$': 'new', 'foo': 'xyz'})

10.3.4 缺失值替换

替换可以处理缺失值相关的问题,也可以处理无效值,需要先将其替换成nan,再进行缺失值处理。

# 构建数据
d = {'a': list(range(4)),
     'b': list('ab..'),
     'c': ['a', 'b', np.nan, 'd']}
df = pd.DataFrame(d)
"""
	a	b	c
0	0	a	a
1	1	b	b
2	2	.	NaN
3	3	.	d
"""
# 将.替换成NaN
df.replace('.', np.nan)

# 使用正则表达式,将空格等替换成NaN
df.replace(r'\s*\.\s*', np.nan, regex=True)
# 对应替换 a换b,点换NaN
df.replace({'a': 'b', '.': np.nan})
df.replace(['a', '.'], ['b', np.nan]) # 效果同上
# 点换dot, a换astuff
df.replace([r'\.', r'(a)'], ['dot', r'\1stuff'], regex=True)
# 将b中的点替换成NaN,可以多列
df.replace({'b': '.'}, {'b': np.nan})

还有很多操作,这里不再赘述,详见书本。

10.3.5 数字替换

# 生成数据
df = pd.DataFrame(np.random.randn(10, 2))
df[np.random.rand(df.shape[0]) > 0.5] = 1.5 
# df.shape[0]返回行数
# 生成10行随机数,大于0.5的那一行赋值为1.5
# 将1.5替换为NaN
df.replace(1.5, np.nan)

# 将1.5替换成NaN, 同时将左上角的值替换成a
df.replace([1.5, df.iloc[0, 0]], [np.nan, 'a'])

# 使替换生效
df.replace(1.5, np.nan, inplace=True)

10.3.6 数据修剪

df.clip(lower, upper)可以修剪数据中的极端值。当数据大于upper时使用upper的值,小于lower时用lower的值。同样地,指定参数inplace=True使生效。

# 修剪成max=3 min=0
df.clip(0, 3)

# 按列指定下限和上限阈值
# eg:数据按同索引位的c值和c对应值+1进行修剪
c = pd.Series([-1, 1, 3])
df.clip(c, c+1, axis=0)
"""
  c	 c+1
[[-1, 0], # index=0: min=-1 max=0
 [1,  2], # 以下同理
 [3,  4]] 

10.4 重复值及删除数据

10.4.1 重复值识别

# 检测重复值
df.duplicated(subset=None, keep='first')

# 返回表示重复行的布尔值序列,默认为一行的所有内容
# subset:可以指定列
# keep:用来确定要标记的重复值
# 可选值有'first',将除了第一次出现的重复值标记为True,默认
# 'last':将除了最后一次出现的重复值标记为True
# Fase:将所有重复值标记为True
# eg
# 原数据
df = pd.DataFrame({
    'A': ['x', 'x', 'z'],
    'B': ['x', 'x', 'x'],
    'C': [1, 1, 2]
})
"""
	A	B	C
0	x	x	1  
1	x	x	1
2	z	x	2
"""
# 全行检测,除了第一次出现外,重复的为True
# 01重复,0第一次出现,因此index=1的行标记为重复
df.duplicated()
"""
0    False
1     True
2    False
dtype: bool
"""

# 指定列检测 所有重复的都为True
df.duplicated(subset=['B'], keep=False)
"""
0    True
1    True
2    True
dtype: bool 
"""

重复值的检测还可以用于数据的查询和筛选:

# 筛选出重复内容
df[df.duplicated()]
"""
	A	B	C
1	x	x	1
"""

10.4.2 删除重复值

df.drop_duplicates(subset=None, keep='first', inplace=True, ignore_index=False)
# subset:指定的标签或标签序列,仅删除这些重复值,默认情况为所有列
# keep:'first', 'last', False
# ignore_index:如果为True,则重新分配自然索引(01...

10.4.3 删除数据

df.drop()通过指定标签名称和相应的轴,或直接给定索引或列名称来删除行和列。使用多层索引的时候,可以通过指定级别来删除不同级别上的标签。

df.drop(labels=None, axis=0, index=None, columns=None,
		level=None, inplace=False, errors='raise')
# labels:要删除的行和列,如果要删除多个,传入列表。
# axis:轴的方向。0为行(默认),1为列。
# index:指定的一行或多行。
# column:指定的一列或多列。
# level:索引层级,将删除此层级。
# inplace:布尔值,是否生效。
# errors:ignore或raise,默认为raise;如果为ignore,则忽略错误,仅删除现有标签。
# 删除指定行
df.drop([0, 1])

# 删除指定列
df.drop(['B', 'C'], axis=1)
df.drop(columns=['B', 'C']) # 同上

10.5 NumPy格式转换

10.5.1 转换方法

Pandas 0.24.0引入了两种从Pandas对象中获取NumPy数组的新方法。

ds.to_numpy():用在Index、Series和DataFrame对象;
s.array:PandasArray,用于Index和Series,封装了numpy.ndarray的接口。

10.5.2 DataFrame转为ndarray

df.values和df.to_numpy()返回的是一个array类型:

df.values # 不推荐
df.to_numpy()
"""
array([['Liver', 'E', 89, 21, 24, 64],
       ['Arry', 'C', 36, 37, 37, 57],
       ..., dtype=object)
"""

type(df.to_numpy()) # numpy.ndarray
df.to_numpy().dtype # dtype('O')
type(df.to_numpy().dtype) # numpy.dtype

# 转换指定列
df[['name', 'Q1']].to_numpy()
"""
array([['Liver', 89],
       ['Arry', 36],
       ..., dtype=object)
"""

10.5.3 Series转为ndarray

对Series使用s.values和s.to_numpy()返回的是一个array类型:

df.Q1.values # 不推荐
df.Q1.to_numpy() 
"""
array([89, 36, 57, 93, 65, 24, 61,  9, 64, 77, 17,  9, 83, 51, 80, 48, 63,
       ...
        2, 14, 13, 96, 16, 38, 62, 59, 39, 20, 48, 21, 98, 11, 21],
      dtype=int64)
"""

df.Q1.array
"""
<PandasArray>
[89, 36, 57, 93, 65, 24, 61,  9, 64, 77, 17,  9, 83, 51, 80, 48, 63, 91, 80,
...
 48, 21, 98, 11, 21]
Length: 100, dtype: int64
"""
# 数据类型
type(df.Q1.to_numpy()) # numpy.ndarray
df.Q1.to_numpy().dtype # dtype('int64')
type(df.Q1.to_numpy().dtype) # numpy.dtype
type(df.Q1.array) #pandas.core.arrays.numpy_.PandasArray

10.5.4 df.to_records()

df.to_records()将数据转为NumPy的record array类型,然后在用NumPy的np.array读一下,转为array类型。

# 转为NumPy record array
df.to_records() # 把索引也包含进来了
"""
rec.array([( 0, 'Liver', 'E', 89, 21, 24, 64),
           ( 1, 'Arry', 'C', 36, 37, 37, 57),
           ...
           (99, 'Ben', 'E', 21, 43, 41, 74)],
          dtype=[('index', '<i8'), ('name', 'O'), ('team', 'O'), ('Q1', '<i8'), 
          ('Q2', '<i8'), ('Q3', '<i8'), ('Q4', '<i8')])
"""
# 转为array
np.array(df.to_records())
"""
array([( 0, 'Liver', 'E', 89, 21, 24, 64),
       ( 1, 'Arry', 'C', 36, 37, 37, 57),
       ...
       (99, 'Ben', 'E', 21, 43, 41, 74)],
      dtype=(numpy.record, [('index', '<i8'), ('name', 'O'), ('team', 'O'), 
      ('Q1', '<i8'), ('Q2', '<i8'), ('Q3', '<i8'), ('Q4', '<i8')]))
"""

10.5.5 np.array读取

用np.array直接读取DataFrame或者Series数据,最终也会转化成array类型。

np.array(df) # DataFrame转
np.array(df.Q1) # Series转
np.array(df.Q1.array) # PandasArray转
np.array(df.to_records().view(type=np.matrix)) # 转为矩阵

10.5.6 小结

本节介绍了如何将Pandas的两大数据类型DataFrame和Series转为Numpy的格式,推荐使用to_numpy()方法。

10.6 本章小结

本章介绍了在Pandas中缺失值的表示方法以及如何找到缺失值,重复值的筛选方法以及如何对它们进行删除、替换和填充等操作。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值