一般使用特殊类型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
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()
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
# 返回有缺失值的行
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())]
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>
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]
"""
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
"""
通过赋值即可完成插入。
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
"""
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参数:
除了取前后值,还可以取经过计算得到的值,比如平均值填充法:
# 填充列的平均值
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}})
插值(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’。
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。
(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
"""
Pandas中数据替换的方法包含数值、文本、缺失值等替换,经常用于数据清洗与整理、枚举转换、数据修正等情况。Series和DataFrame的replace()都提供了一种高效灵活的方法。
# 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}) # 用字典映射对应替换值,将0和1这两个值替换成10和100,如果值不存在,则不进行操作
ser.replace({'a': 0, 'b': 5}, 100) # 将a列的0、b列的5替换成100
df.replace({'a': {0: 100, 4: 400}}) # 指定列里的替换规则
# 将1, 2, 3替换为它们前一个值
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
"""
# 将1, 2, 3替换为它们后一个值
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
"""
替换方法默认没有开启正则匹配模式,直接按原字符进行匹配替换,如果如果字符规则比较复杂的内容,可以使用正则表达式进行匹配。
# 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'})
替换可以处理缺失值相关的问题,也可以处理无效值,需要先将其替换成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})
还有很多操作,这里不再赘述,详见书本。
# 生成数据
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)
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]]
# 检测重复值
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
# 0和1重复,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
"""
df.drop_duplicates(subset=None, keep='first', inplace=True, ignore_index=False)
# subset:指定的标签或标签序列,仅删除这些重复值,默认情况为所有列
# keep:'first', 'last', False
# ignore_index:如果为True,则重新分配自然索引(0, 1, ...)
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']) # 同上
Pandas 0.24.0引入了两种从Pandas对象中获取NumPy数组的新方法。
ds.to_numpy():用在Index、Series和DataFrame对象;
s.array:PandasArray,用于Index和Series,封装了numpy.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)
"""
对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
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', '), ('name', 'O'), ('team', 'O'), ('Q1', '),
('Q2', '), ('Q3', '), ('Q4', ')])
"""
# 转为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', '), ('name', 'O'), ('team', 'O'),
('Q1', '), ('Q2', '), ('Q3', '), ('Q4', ')]))
"""
用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)) # 转为矩阵
本节介绍了如何将Pandas的两大数据类型DataFrame和Series转为Numpy的格式,推荐使用to_numpy()方法。
本章介绍了在Pandas中缺失值的表示方法以及如何找到缺失值,重复值的筛选方法以及如何对它们进行删除、替换和填充等操作。