【问题标题】:More Pythonic/Pandaic approach to looping over a pandas Series循环遍历 pandas 系列的更多 Pythonic/Pandaic 方法
【发布时间】:2017-05-20 00:31:50
【问题描述】:

这很可能是非常基本的东西,但我无法弄清楚。 假设我有一个这样的系列:

s1 = pd.Series([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4])

如何在不恢复使用 for 循环的情况下对本系列的子系列进行操作?

例如,假设我想把它变成一个包含四个元素的新系列。这个新 Series 中的第一个元素是原 Series 中前三个元素的总和 (1, 1, 1),第二个元素是后三个元素的总和 (2, 2, 2),依此类推:

s2 = pd.Series([3, 6, 9, 12])

我该怎么做?

【问题讨论】:

    标签: python loops pandas numpy


    【解决方案1】:

    这计算滚动和:

    s1.rolling(3).sum()
    

    您只需要选择每三个元素:

    s1.rolling(3).sum()[2::3]
    

    输出:

    2      3.0
    5      6.0
    8      9.0
    11    12.0
    

    【讨论】:

    • 干净。为什么前 2 个值是 NaN?
    • 前 2 个值是 NaN,因为您至少需要 3 个值来计算滚动和。您可以更改:s1.rolling(3, min_periods=1).sum().
    【解决方案2】:

    您可以使用 numpy 重塑系列 s1,然后对各行求和,例如:

    np.sum(np.array(s1).reshape(len(s1)/3,3), axis = 1)
    

    导致

    array([ 3,  6,  9, 12], dtype=int64)
    

    编辑:正如MSeifert 在他的评论中提到的,你也可以让 numpy 计算长度,例如:

    np.sum(np.array(s1).reshape(-1, 3), axis=1)
    

    【讨论】:

    • 整洁。而且比我的回答要快。
    • 但是你的答案更可靠,因为如果 s1 的长度不是 3 的倍数,它也可以工作
    • 您也可以告诉 numpy 计算长度,而不是自己计算长度:np.sum(np.array(s1).reshape(-1, 3), axis=1)
    • 太棒了!这种方法很简单,几乎可以直接解决我的问题。由于这些原因被接受为答案。
    【解决方案3】:

    这是一个使用np.bincount 处理通用元素数量的 NumPy 方法 -

    pd.Series(np.bincount(np.arange(s1.size)//3, s1))
    

    示例运行 -

    In [42]: s1 = pd.Series([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 9, 5])
    
    In [43]: pd.Series(np.bincount(np.arange(s1.size)//3, s1))
    Out[43]: 
    0     3.0
    1     6.0
    2     9.0
    3    12.0
    4    14.0
    dtype: float64
    

    如果我们真的渴望性能以及系列的长度可以被窗口长度整除的情况,我们可以使用s1.values,然后reshape,最后使用np.einsum来查看系列求和,就像这样 -

    pd.Series(np.einsum('ij->i',s.values.reshape(-1,3)))
    

    @Nickil Maveli's post 中使用的基准数据集相同的时间 -

    In [140]: s = pd.Series(np.repeat(np.arange(10**5), 3))
    
    # @Nickil Maveli's soln
    In [141]: %timeit pd.Series(np.add.reduceat(s.values, np.arange(0, s.shape[0], 3)))
    100 loops, best of 3: 2.07 ms per loop
    
    # Using views+sum
    In [142]: %timeit pd.Series(s.values.reshape(-1,3).sum(1))
    100 loops, best of 3: 2.03 ms per loop
    
    # Using views+einsum
    In [143]: %timeit pd.Series(np.einsum('ij->i',s.values.reshape(-1,3)))
    1000 loops, best of 3: 1.04 ms per loop
    

    【讨论】:

      【解决方案4】:

      您还可以使用np.add.reduceat,通过指定在每个第 3 个元素处减少的切片并计算它们的运行总和:

      >>> pd.Series(np.add.reduceat(s1.values, np.arange(0, s1.shape[0], 3)))
      0     3
      1     6
      2     9
      3    12
      dtype: int64
      

      时间限制:

      arr = np.repeat(np.arange(10**5), 3)
      s = pd.Series(arr)
      s.shape
      (300000,)
      
      # @IanS soln
      %timeit s.rolling(3).sum()[2::3]        
      100 loops, best of 3: 15.6 ms per loop
      
      # @Divakar soln
      %timeit pd.Series(np.bincount(np.arange(s.size)//3, s))  
      100 loops, best of 3: 5.44 ms per loop
      
      # @Nikolas Rieble soln
      %timeit pd.Series(np.sum(np.array(s).reshape(len(s)/3,3), axis = 1))  
      100 loops, best of 3: 2.17 ms per loop
      
      # @Nikolas Rieble modified soln
      %timeit pd.Series(np.sum(np.array(s).reshape(-1, 3), axis=1))  
      100 loops, best of 3: 2.15 ms per loop
      
      # @Divakar modified soln
      %timeit pd.Series(s.values.reshape(-1,3).sum(1))
      1000 loops, best of 3: 1.62 ms per loop
      
      # Proposed solution in post
      %timeit pd.Series(np.add.reduceat(s.values, np.arange(0, s.shape[0], 3)))
      1000 loops, best of 3: 1.45 ms per loop
      

      【讨论】:

      • 按照相同的模式添加了相当长的系列。
      • 我认为更快的求和版本将获得视图:%timeit pd.Series(s.values.reshape(-1,3).sum(1))
      • @Divakar,它肯定更快。如果您愿意,可以编辑您的帖子。
      • np.einsum 确实是其中最快的。干得好!
      • 而你是通用和高性能的完美结合:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-21
      • 1970-01-01
      • 2021-01-29
      • 1970-01-01
      • 1970-01-01
      • 2023-03-24
      • 2018-10-20
      相关资源
      最近更新 更多