在Pandas DataFrame中用None替换无效值

119

有没有一种方法可以在Python的Pandas中用None替换值?

你可以使用df.replace('pre', 'post')来将一个值替换为另一个值,但是如果你想要用None值替换,就无法实现。如果你尝试这样做,你会得到一个奇怪的结果。

下面是一个示例:

df = DataFrame(['-',3,2,5,1,-5,-1,'-',9])
df.replace('-', 0)

该函数返回成功的结果。

但是,

df.replace('-', None)

该函数将返回以下结果:
0
0   - // this isn't replaced
1   3
2   2
3   5
4   1
5  -5
6  -1
7  -1 // this is changed to `-1`...
8   9

为什么会返回这样奇怪的结果?
因为我想将此数据框倒入MySQL数据库,所以不能将NaN值放入数据框中的任何元素,而是想要放置None。当然,您可以先将'-'更改为NaN,然后再将NaN转换为None,但我想知道为什么数据框的行为如此糟糕。
在Python 2.7和OS X 10.8上测试了pandas 0.12.0 dev。Python是OS X上预安装的版本,我使用SciPy Superpack脚本安装了pandas,供您参考。

write_frame函数是否无法将NaN解析为none - Andy Hayden
是的。你遇到了 InternalError: (1054, u"Unknown column 'nan' in 'field list'") 错误。我不知道除了在执行 write_frame 方法之前将 NaN 转换为 None 之外还有什么解决方法。 - Blaszard
你正在使用哪个版本的pandas? - Andy Hayden
Scipy超级包是否已经推出dev版本了?好的,我认为你应该在Github上提出这个问题,修复起来不应该太难。 - Andy Hayden
1
如果您正在从CSV/Excel读取此数据,则可以使用na_values参数将这些值读入为NaN。更多信息请参见此答案。 - cs95
如果有人主要是因为替换的第二个可疑部分(在第7条记录中引入了-1的值)而来到这里,那么这是由于'method'参数的默认值为'pad'。如果pandas的维护者碰巧正在阅读这篇文章,那么这种默认行为在我经历过的任何环境中都是不可取的,并且很容易导致数据损坏! - bsplosion
10个回答

141

实际上,在pandas的后续版本中,这会导致TypeError错误:

df.replace('-', None)
TypeError: If "to_replace" and "value" are both None then regex must be a mapping

您可以通过传递列表或字典来完成它:

In [11]: df.replace('-', df.replace(['-'], [None]) # or .replace('-', {0: None})
Out[11]:
      0
0  None
1     3
2     2
3     5
4     1
5    -5
6    -1
7  None
8     9

但我建议使用NaN而不是None:

In [12]: df.replace('-', np.nan)
Out[12]:
     0
0  NaN
1    3
2    2
3    5
4    1
5   -5
6   -1
7  NaN
8    9

18
可以简单地使用列表来替换特定的值,例如 df.replace(['-'], [None])df.replace({'-': None})。我认为使用 None 作为标记符会排除其作为一个值的可能性。 - DSM
@user2360798,replace实际上是一个非常丰富(即复杂)的函数,但(dev)docstring确实非常好。 - Andy Hayden
4
我不知道这是否显而易见,但必须将df赋值给自身,如下所示:df = df.replace({'?': np.nan}) - luckyging3r
3
@AndyHayden df.replace('-', df.replace(['-'], [None]) 看起来有点奇怪,这是打错字了吗? - lin_bug
2
@lin_bug 尽管在最近的pandas版本中似乎不再起作用。df.where(df!='-', None)可以使用。 - Andy Hayden
显示剩余5条评论

36

我更喜欢使用 replace 解决问题,并使用字典,因为它简单而优雅:

df.replace({'-': None})

你还可以有更多的替换:

df.replace({'-': None, 'None': None})

即使对于更大的替换,它始终清楚明了地显示替换的内容,这一点在长列表中要困难得多,我个人认为。


2
值得注意的是,这种技术之所以有效,部分原因在于在to_replace中使用dict类型会导致不评估method参数,因此method='pad'默认值不会产生任何负面影响。 - bsplosion

18

where可能是你正在寻找的。所以

data=data.where(data=='-', None) 

来自 Pandas文档

where 方法返回一个与调用者形状相同的对象,其中相应的条目是从调用者中获取条件为 True 的条目,否则从其他地方获取。


7
这个说法实际上是不准确的。data=data.where(data=='-', None)将会用None替换所有与'-'不相等的值。Pandas中的where函数会保留第一个参数(在这种情况下为data=='-')的值,并用第二个参数(在这种情况下为None)替换其他任何值。这有点令人困惑,因为np.where更加明确,它先询问条件,然后在第二个参数中确定True的情况,在第三个参数中确定False的情况。 - clg4

12
在继续阅读本篇文章之前,重要的是要理解NaN和None之间的区别。一个是浮点类型,另一个是对象类型。Pandas更适合使用标量类型,因为这些类型上的许多方法可以进行矢量化。Pandas确实尝试一致地处理None和NaN,但NumPy不能。
我的建议(和安迪的建议)是坚持使用NaN。
但是为了回答你的问题... pandas >= 0.18:使用read_csvna_values = ['-']参数
如果您从CSV / Excel加载此数据,我有好消息告诉您。您可以在数据加载期间使用pd.read_*函数(例如read_csvread_excel)接受na_values属性来消除根源。

file.csv

A,B
-,1
3,-
2,-
5,3
1,-2
-5,4
-1,-1
-,0
9,0

现在,要将 - 字符转换为NaN,请执行以下操作,
import pandas as pd
df = pd.read_csv('file.csv', na_values=['-'])
df

     A    B
0  NaN  1.0
1  3.0  NaN
2  2.0  NaN
3  5.0  3.0
4  1.0 -2.0
5 -5.0  4.0
6 -1.0 -1.0
7  NaN  0.0
8  9.0  0.0

对于其他函数/文件格式也是类似的。

附:在v0.24+版本中,即使您的列具有NaN(是的,说到同时拥有蛋糕并吃掉它),您也可以保留整数类型。您可以指定dtype='Int32'

df = pd.read_csv('file.csv', na_values=['-'], dtype='Int32')
df

     A    B
0  NaN    1
1    3  NaN
2    2  NaN
3    5    3
4    1   -2
5   -5    4
6   -1   -1
7  NaN    0
8    9    0

df.dtypes

A    Int32
B    Int32
dtype: object

dtype不是传统的int类型,而是可空整数类型。还有其他选项。


处理数字数据: 使用pd.to_numericerrors='coerce'

如果你正在处理数字数据,更快的解决方案是使用pd.to_numericerrors='coerce'参数,它将无效值(无法转换为数字的值)强制转换为NaN。

pd.to_numeric(df['A'], errors='coerce')

0    NaN
1    3.0
2    2.0
3    5.0
4    1.0
5   -5.0
6   -1.0
7    NaN
8    9.0
Name: A, dtype: float64

为了保留(可空)整数数据类型,请使用{{}}。
pd.to_numeric(df['A'], errors='coerce').astype('Int32')

0    NaN
1      3
2      2
3      5
4      1
5     -5
6     -1
7    NaN
8      9
Name: A, dtype: Int32 

要强制多个列,使用apply

df[['A', 'B']].apply(pd.to_numeric, errors='coerce').astype('Int32')

     A    B
0  NaN    1
1    3  NaN
2    2  NaN
3    5    3
4    1   -2
5   -5    4
6   -1   -1
7  NaN    0
8    9    0

......并在之后将结果分配回去。

更多信息可以在这个答案中找到。


8

对于Pandas版本≥1.0.0,我会使用DataFrame.replaceSeries.replace

df.replace(old_val, pd.NA, inplace=True)

这样更好有两个原因:

  1. 它使用pd.NA而不是 Nonenp.nan
  2. 它可以选择在原位(in-place)操作,具体取决于内部实现方式,可能更加节省内存。

3
df = pd.DataFrame(['-',3,2,5,1,-5,-1,'-',9])
df = df.where(df!='-', None)

0

或者您也可以使用{{link1:mask}}:

df.mask(df=='-', None)

0
df.replace('-', np.nan).astype("object")

这将确保您稍后可以在数据框中使用isnull()


0

可以使用np.nan来设置空值:

import numpy as np
df.replace('-', np.nan)

优点是df.last_valid_index()能够识别这些为无效值。


0
使用replace并分配新df:
import pandas as pd
df = pd.DataFrame(['-',3,2,5,1,-5,-1,'-',9])
dfnew = df.replace('-', 0)
print(dfnew)


(venv) D:\assets>py teste2.py
   0
0  0
1  3
2  2
3  5
4  1
5 -5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接