为什么Pandas不能使用掩码/replace将nan替换为0数组?



我有一个这样的系列

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

,我只是想用[0,0,0]代替NaN

我试过了

s.fillna([0,0,0]) # TypeError: "value" parameter must be a scalar or dict, but you passed a "list"
s[s.isna()] = [[0,0,0],[0,0,0]] # just replaces the NaN with a single "0". WHY?!
s.fillna("NAN").replace({"NAN":[0,0,0]}) # ValueError: NumPy boolean array indexing assignment cannot 
#assign 3 input values to the 2 output values where the mask is true

s.fillna("NAN").replace({"NAN":[[0,0,0],[0,0,0]]}) # TypeError: NumPy boolean array indexing assignment
# requires a 0 or 1-dimensional input, input has 2 dimensions

我真的不明白,为什么前两种方法不起作用(也许我得到了第一种方法,但第二种方法我无法理解)。

多亏了这个so -问答,我们可以通过

is_na = s.isna()
s.loc[is_na] = s.loc[is_na].apply(lambda x: [0,0,0])

,但由于apply往往是相当慢的,我不明白,为什么我们不能使用replace或切片如上

熊猫与列表工作痛苦,这是一个hack的解决方案:

s = s.fillna(pd.Series([[0,0,0]] * len(s), index=s.index))
print (s)
0    [1, 2, 3]
1    [1, 2, 3]
2    [0, 0, 0]
3    [1, 2, 3]
4    [1, 2, 3]
5    [0, 0, 0]
dtype: object

Series.reindex

s.dropna().reindex(s.index, fill_value=[0, 0, 0])

0    [1, 2, 3]
1    [1, 2, 3]
2    [0, 0, 0]
3    [1, 2, 3]
4    [1, 2, 3]
5    [0, 0, 0]
dtype: object

文档说明此值不能为list

用于填充孔的值(例如0),交替为a指定每个值使用哪个值的dict/Series/DataFrame索引(用于Series)或列(用于DataFrame)。值不在dict/Series/DataFrame将不会被填充。不能为列表。

这可能是当前实现的限制,如果不能修补源代码,您必须采用变通方法(如下所示)。


然而,如果你不打算使用锯齿数组,你真正想做的可能是用pd.DataFrame()替换pd.Series(),例如:

import numpy as np
import pandas as pd

s = pd.DataFrame(
[[1, 2, 3],
[1, 2, 3],
[np.nan],
[1, 2, 3],
[1, 2, 3],
[np.nan]],
dtype=pd.Int64Dtype())  # to mix integers with NaNs

s.fillna(0)
#    0  1  2
# 0  1  2  3
# 1  1  2  3
# 2  0  0  0
# 3  1  2  3
# 4  1  2  3
# 5  0  0  0

如果你确实需要使用锯齿数组,你可以使用任何从其他答案中提出的解决方案,或者你可以让你的一个尝试工作,例如:

ii = s.isna()
nn = ii.sum()
s[ii] = pd.Series([[0, 0, 0]] * nn).to_numpy()
# 0    [1, 2, 3]
# 1    [1, 2, 3]
# 2    [0, 0, 0]
# 3    [1, 2, 3]
# 4    [1, 2, 3]
# 5    [0, 0, 0]
# dtype: object

基本上使用NumPy掩蔽来填充系列。诀窍是为在NumPy级别上工作的赋值生成一个兼容的对象。

如果输入中有太多的nan,则以类似的方式使用s.notna()可能会更有效/更快,例如:

import pandas as pd

result = pd.Series([[0, 0, 0]] * len(s))
result[s.notna()] = s[s.notna()]

让我们尝试做一些基准测试,其中:

  • replace_nan_isna()from above
import pandas as pd

def replace_nan_isna(s, value, inplace=False):
if not inplace:
s = s.copy()
ii = s.isna()
nn = ii.sum()
s[ii] = pd.Series([value] * nn).to_numpy()
return s
  • replace_nan_notna()也来自上面
import pandas as pd

def replace_nan_notna(s, value, inplace=False):
if inplace:
raise ValueError("In-place not supported!")
result = pd.Series([value] * len(s))
result[s.notna()] = s[s.notna()]
return result
  • replace_nan_reindex()来自@ShubhamSharma的回答
def replace_nan_reindex(s, value, inplace=False):
if not inplace:
s = s.copy()
s.dropna().reindex(s.index, fill_value=value)
return s
  • replace_nan_fillna()是来自@jezrael的回答
import pandas as pd

def replace_nan_fillna(s, value, inplace=False):
if not inplace:
s = s.copy()
s.fillna(pd.Series([value] * len(s), index=s.index))
return s

,代码如下:

import numpy as np
import pandas as pd

def gen_data(n=5, k=2, p=0.7, obj=(1, 2, 3)):
return pd.Series(([obj] * int(p * n) + [np.nan] * (n - int(p * n))) * k)

funcs = replace_nan_isna, replace_nan_notna, replace_nan_reindex, replace_nan_fillna
# : inspect results
s = gen_data(5, 1)
for func in funcs:
print(f'{func.__name__:>20s}  {func(s, value)}')
print()
# : generate benchmarks
s = gen_data(100, 1000)
value = (0, 0, 0)
base = funcs[0](s, value)
for func in funcs:
print(f'{func.__name__:>20s}  {(func(s, value) == base).all()!s:>5}', end='  ')
%timeit func(s, value)
#     replace_nan_isna   True  100 loops, best of 5: 16.5 ms per loop
#    replace_nan_notna   True  10 loops, best of 5: 46.5 ms per loop
#  replace_nan_reindex   True  100 loops, best of 5: 9.74 ms per loop
#   replace_nan_fillna   True  10 loops, best of 5: 36.4 ms per loop

表明reindex()可能是最快的方法。

最新更新