Pandas - 用空的Python字典对象替换DataFrame中的所有NaN值

17

我有一个pandas的数据框,其中每个单元格都包含一个Python字典。

>>> data = {'Q':{'X':{2:2010}, 'Y':{2:2011, 3:2009}},'R':{'X':{1:2013}}}
>>> frame = DataFrame(data)
>>> frame
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}        NaN
我想用一个空字典来替换NaN,以得到这个结果:
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}        {}

然而,由于fillna函数将空字典解释为列->值映射而不是标量值,如果我仅仅这样做,它什么也不会做(也就是说,它不起作用):

>>> frame.fillna(inplace=True, value={})
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}        NaN

有没有办法使用 fillna 来实现我想要的功能?我是否必须遍历整个 DataFrame 或构建一个傻傻的字典,将所有列映射到空字典?


“愚蠢的解决方案”甚至都行不通,因为它会尝试使用那个字典来确定每个列系列中要使用哪些值。此时,您需要编写每个值的索引。所以很遗憾,这是不可行的。请按照下面描述的使用loc。 - Mark Whitfield
@MarkWhitfield 别放弃!你可以创建一个字典中的字典来使它工作。看看我的解决方案。 - Shashank Agarwal
值得注意的是,在单元格中存储非标量条目并不被真正支持,而且许多pandas功能将会出现问题。当然,你的情况可能会有所不同。 - DSM
@DSM,numpy不支持在单元格中使用Python对象吗?我印象中pandas是基于numpy的,并且也支持任何数据类型,对吗? - ValAyal
6个回答

17

我能够这样使用 DataFrame.applymap

>>> from pandas import isnull
>>> frame=frame.applymap(lambda x: {} if isnull(x) else x)
>>> frame
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}         {}

这个解决方案避免了EdChum的解决方案中的问题(在该解决方案中,所有NaN单元格最终指向内存中的同一基础字典对象,防止它们独立更新)和Shashank的问题(其中可能需要构建一个潜在的大型数据结构,带有嵌套的字典,仅用于指定一个空字典值)。


6

DataFrame.where是一种非常直接的方法来实现这个目标:

>>> data = {'Q': {'X': {2: 2010}, 'Y': {2: 2011, 3: 2009}}, 'R': {'X': {1: 2013}}}
>>> frame = DataFrame(data)
>>> frame
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}        NaN

>>> frame.where(frame.notna(), lambda x: [{}])
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}         {}

此外,它似乎更快一些:

>>> %timeit frame.where(frame.notna(), lambda x: [{}])
791 µs ± 16.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit frame.applymap(lambda x: {} if isnull(x) else x)
1.07 ms ± 7.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

在更大的数据集上,我观察到速度提升了约10倍。


3
问题在于当一个字典传递给fillna函数时,它会尝试根据数据框中的列填充值。因此,我尝试的第一个解决方案是 -
frame.fillna({column: {} for column in frame.columns})

但是,如果在第二级提供了一个字典,它将尝试将键与索引匹配,因此有效的解决方案是 -

frame.fillna({column: {ind: {} for ind in frame.index} for column in frame.columns})

这提供了 -

                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}         {}

EdChum的答案可能更适合你的需求,但当你不想直接在原位进行更改时可以使用此方法。

编辑:上述解决方案适用于较小的框架,但对于较大的框架可能会出现问题。使用replace可以解决这个问题。

frame.replace(np.nan, {column: {} for column in frame.columns})

是的,这个方法可行,但我在原问题中称其为“愚蠢”的解决方案的原因是似乎浪费了大量的资源去构建一个庞大的数据结构(我的DataFrame实际上不是2x2的,你可以想象一下),特别是Python字典占用内存较多。 - ValAyal
1
@ValAyal同意,这有点傻。对于较小的帧来说它起作用,但是对于较大的帧,这可能会成为一个问题。然而,我们可以使用replace! 请参见编辑后的答案。 - Shashank Agarwal
使用frame.replace存在与EdChum的答案中的frame.loc类似的问题。特定列中的所有NaN最终都指向内存中的同一个字典对象,因此它们无法独立更改。最终我使用frame.applymap成功地实现了我想要的效果(请参见我的答案)。 - ValAyal
@ValAyal 不错的解决方案! - Shashank Agarwal

2
使用 .values 访问器可以直接将值分配到 numpy 数组中:
frame.R = frame.R.astype(object)  # assertion

frame.R.values[frame.R.isnull()] = {}

1

这个可以使用loc实现:

In [6]:

frame.loc[frame['R'].isnull(), 'R'] = {}
frame
Out[6]:
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}         {}

1
如果我执行 df.ix['Y', 'R']['snth'] = 12,那么所有设置为空字典的位置都将变成 {'snth': 12} - acushner
另一种看待这个问题的方式是,如果将 id 应用于该框架,那么所有空字典将具有相同的 id。 - acushner
不过,如果 OP 永远不打算更新字典,那也没关系。 - acushner
1
@acushner 我明白你的意思,但对于OP存储空字典来说,这是个问题,对我来说这是一件奇怪的事情,只会带来一系列痛苦。 - EdChum
@acushner,你说得对,这正是这个解决方案的情况。虽然我现在不打算更新空字典,但它确实是一个等待发生的错误,然后当然会带来很多痛苦。此外,这仍然需要我手动迭代每一列,对吗? - ValAyal
显示剩余3条评论

0

@Josh_Bode的回答对我帮助很大。这是一个非常微小的不同版本。我使用了mask()而不是where()(相当微不足道的更改)。我还更新了分配空字典的方式。通过创建与框架一样长的字典实例列表,然后进行分配,我避免了许多相同字典的陷阱。

>>> data = {'Q': {'X': {2: 2010}, 'Y': {2: 2011, 3: 2009}}, 'R': {'X': {1: 2013}}}
>>> frame = DataFrame(data)
>>> frame
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}        NaN

>>> frame.mask(frame.isna(), lambda x: [{} for _ in range(len(frame)])
                    Q          R
X           {2: 2010}  {1: 2013}
Y  {2: 2011, 3: 2009}         {}

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