【问题标题】:Replace NaN with empty list in a pandas dataframe用熊猫数据框中的空列表替换 NaN
【发布时间】:2015-10-12 13:40:20
【问题描述】:

我正在尝试用一个空列表 [] 替换我的数据中的一些 NaN 值。但是,该列表表示为 str 并且不允许我正确应用 len() 函数。有没有办法用 pandas 中的实际空列表替换 NaN 值?

In [28]: d = pd.DataFrame({'x' : [[1,2,3], [1,2], np.NaN, np.NaN], 'y' : [1,2,3,4]})

In [29]: d
Out[29]:
           x  y
0  [1, 2, 3]  1
1     [1, 2]  2
2        NaN  3
3        NaN  4

In [32]: d.x.replace(np.NaN, '[]', inplace=True)

In [33]: d
Out[33]:
           x  y
0  [1, 2, 3]  1
1     [1, 2]  2
2         []  3
3         []  4

In [34]: d.x.apply(len)
Out[34]:
0    3
1    2
2    2
3    2
Name: x, dtype: int64

【问题讨论】:

    标签: python pandas dataframe


    【解决方案1】:

    这适用于使用isnullloc 来掩盖系列:

    In [90]:
    d.loc[d.isnull()] = d.loc[d.isnull()].apply(lambda x: [])
    d
    
    Out[90]:
    0    [1, 2, 3]
    1       [1, 2]
    2           []
    3           []
    dtype: object
    
    In [91]:
    d.apply(len)
    
    Out[91]:
    0    3
    1    2
    2    0
    3    0
    dtype: int64
    

    您必须使用apply 执行此操作,以便列表对象不会被解释为分配回 df 的数组,该数组将尝试将形状与原始系列对齐

    编辑

    使用您更新的示例,以下工作:

    In [100]:
    d.loc[d['x'].isnull(),['x']] = d.loc[d['x'].isnull(),'x'].apply(lambda x: [])
    d
    
    Out[100]:
               x  y
    0  [1, 2, 3]  1
    1     [1, 2]  2
    2         []  3
    3         []  4
    
    In [102]:    
    d['x'].apply(len)
    
    Out[102]:
    0    3
    1    2
    2    0
    3    0
    Name: x, dtype: int64
    

    【讨论】:

      【解决方案2】:

      为了扩展接受的答案,应用调用可能会特别昂贵 - 没有它也可以通过从头构造一个 numpy 数组来完成相同的任务。

      isna = df['x'].isna()
      df.loc[isna, 'x'] = pd.Series([[]] * isna.sum()).values
      

      快速时间比较:

      def empty_assign_1(s):
          s[s.isna()].apply(lambda x: [])
      
      def empty_assign_2(s):
          [[]] * s.isna().sum()
      
      series = pd.Series(np.random.choice([1, 2, np.nan], 1000000))
      
      %timeit empty_assign_1(series)
      >>> 61 ms ± 964 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
      
      %timeit empty_assign_2(series)
      >>> 2.17 ms ± 70.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
      

      快近 10 倍!

      编辑: 修复了@valentin 指出的错误

      在这种情况下执行赋值时,您必须小心处理数据类型。在上面的示例中,测试系列是浮动的,但是,添加 [] 元素会强制整个系列成为对象。如果您执行类似的操作,Pandas 会为您处理这些事情

      idx = series.isna()
      series[isna] = series[isna].apply(lambda x: [])
      

      因为 apply 的输出本身就是一个系列。您可以像这样使用分配开销测试实时性能(我添加了一个字符串值,因此系列是一个对象,您可以改为使用数字作为替换值而不是空列表以避免强制)。

      def empty_assign_1(s):
          idx = s.isna()
          s[idx] = s[idx].apply(lambda x: [])
      
      def empty_assign_2(s):
          idx = s.isna()
          s.loc[idx] = [[]] * idx.sum()
      
      series = pd.Series(np.random.choice([1, 2, np.nan, '2'], 1000000))
      
      %timeit empty_assign_1(series.copy())
      >>> 45.1 ms ± 386 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
      
      %timeit empty_assign_2(series.copy())
      >>> 24 ms ± 393 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
      

      其中大约 4 毫秒与副本有关,10 倍到 2 倍,仍然相当不错。

      【讨论】:

      • 这个答案具有误导性,因为第一个函数empty_assign_1() 的实现似乎不正确。它将 lambda 函数应用于系列中的 每个 元素,而不是仅应用于值实际上为 NaN 的元素。应该是s[s.isna()].apply(...)。在此修复后执行时序比较实际上会反转结果,从而使第一个函数变得更快。
      • 哈!您实际上确实发现了一个错误,我似乎忘记了isna 不是dropna 的倒数。话虽如此,原来的帖子仍然是正确的。您观察到反转的原因是对 pd.Series 的不必要的构造函数调用(这也很慢)。只需使用[[]]*s.isna().sum(),您就会重新开始营业。由于 pandas 解释列表输入的方式,用列表替换 nans 会使这个特定问题的上下文变得复杂,因此您需要使用 dtype='object'.loc 创建 series 以进行分配(或替换为非列表) .
      【解决方案3】:

      您也可以为此使用列表推导:

      d['x'] = [ [] if x is np.NaN else x for x in d['x'] ]
      

      【讨论】:

      • 恕我直言,这是最清晰的解决方案。
      猜你喜欢
      • 2015-08-04
      • 2014-07-07
      • 2018-03-25
      • 1970-01-01
      • 2019-08-01
      • 1970-01-01
      • 2020-10-12
      • 2020-11-29
      • 2018-10-23
      相关资源
      最近更新 更多