使用更Pythonic/Pandaic的方式循环遍历pandas Series

7
这很可能是非常基础的问题,但我无法解决。 假设我有这样一个序列:
s1 = pd.Series([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4])

如何在不使用循环的情况下对此“系列”的子系列进行操作?

例如,假设我想将其转换为包含四个元素的新“系列”。新“系列”中的第一个元素是原始“系列”中前三个元素(1、1、1)的总和,第二个元素是后三个元素(2、2、2)的总和,以此类推。

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

我该如何做到这一点?

4个回答

7

您还可以使用np.add.reduceat,通过指定要在每个第三个元素处进行缩减的切片并计算它们的累加和:

>>> 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

1
它已经按照同样的模式添加了相当长一段时间的系列。 - Nickil Maveli
1
我认为使用求和的更快版本将获得视图:%timeit pd.Series(s.values.reshape(-1,3).sum(1)) - Divakar
@Divakar,这肯定更快。如果您愿意,可以编辑您的帖子。 - Nickil Maveli
在我的帖子中添加了另一种解决方案,并相应地添加了时间结果。 - Divakar
1
np.einsum 确实是最快的。做得好! - Nickil Maveli
1
你的代码很好,既通用又高效 :) - Divakar

6
这是一个使用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的帖子相同的基准数据集进行计时 -

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

5
您可以使用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)

整洁。而且比我的答案快。 - IanS
1
然而,您的答案更加健壮,因为它也适用于s1长度不是3的倍数的情况。 - Nikolas Rieble
3
你可以直接让numpy计算长度,而不需要自己计算:np.sum(np.array(s1).reshape(-1, 3), axis=1) - MSeifert
太好了!这种方法简单直接,几乎完美地解决了我的问题。因此我接受它作为答案。 - rdv

3
这将计算滚动总和:
s1.rolling(3).sum()

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

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

输出:

2      3.0
5      6.0
8      9.0
11    12.0

为什么前两个值是NaN?请清除一下。 - Mohammad Yusuf
前两个值为NaN,因为您至少需要3个值才能计算滚动总和。您可以更改代码:s1.rolling(3, min_periods=1).sum() - IanS

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