测试DataFrame中的后续值

时间:2015-04-07 18:53:06

标签: python pandas

我有一个DataFrame,其中一列有正负整数。对于每一行,我想看看有多少连续行(从当前行开始并包括当前行)具有负值。

因此,如果序列为2, -1, -3, 1, -1,则结果为0, 2, 1, 0, 1

我可以通过遍历所有索引来执行此操作,使用.iloc拆分列,并next()找出下一个正值的位置。但我觉得这并没有充分利用熊猫的能力,而且我认为这是一种更好的方法。我已尝试使用.shift()expanding_window但未成功。

还有更多" pandastic"找出当前一个符合某种逻辑条件后连续行数的方法?

现在正在做什么:

import pandas as pd

df = pd.DataFrame({"a": [2, -1, -3, -1, 1, 1, -1, 1, -1]})

df["b"] = 0
for i in df.index:
    sub = df.iloc[i:].a.tolist()
    df.b.iloc[i] = next((sub.index(n) for n in sub if n >= 0), 1)

编辑:我意识到,即使我自己的例子在最后有一个以上的负值时也无法运作。这样就可以提供更好的解决方案。

编辑2:我用整数表示问题,但最初只在我的示例中放了1-1。我需要解决一般的正负整数。

2 个答案:

答案 0 :(得分:5)

FWIW,这是一个相当简洁的答案,不需要任何功能或适用。借用here(以及我确定的其他答案),并感谢@DSM提到升序=错误选项:

df = pd.DataFrame({"a": [2, -1, -3, -1, 1, 1, -1, 1, -1, -2]})

df['pos'] = df.a > 0
df['grp'] = ( df['pos'] != df['pos'].shift()).cumsum()
dfg = df.groupby('grp')
df['c'] = np.where( df['a'] < 0, dfg.cumcount(ascending=False)+1, 0 )

   a  b    pos  grp  c
0  2  0   True    1  0
1 -1  3  False    2  3
2 -3  2  False    2  2
3 -1  1  False    2  1
4  1  0   True    3  0
5  1  0   True    3  0
6 -1  1  False    4  1
7  1  0   True    5  0
8 -1  1  False    6  2
9 -2  1  False    6  1

我认为这种方法的一个好处是,一旦你设置了&#39; grp&#39;变量你可以使用标准的groupby方法很容易地做很多事情。

答案 1 :(得分:3)

这是一个有趣的谜题。我找到了一种使用熊猫工具的方法,但我认为你会认为它更不透明:-)。这是一个例子:

data = pandas.Series([1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1])
x = data[::-1] # reverse the data

print(x.groupby(((x<0) != (x<0).shift()).cumsum()).apply(lambda x: pandas.Series(
    np.arange(len(x))+1 if (x<0).all() else np.zeros(len(x)),
    index=x.index))[::-1])

输出正确:

0     0
1     3
2     2
3     1
4     0
5     2
6     1
7     0
8     0
9     1
10    0
dtype: float64

基本思想与我在this question的答案中描述的基本思路相似,您可以找到在各种答案中使用的相同方法,这些答案会询问如何在pandas中使用行间信息。你的问题有点棘手,因为你的标准反过来了(要求跟随负数的数量而不是前面负数的数量),因为你只需要一方面分组(即,您只需要连续负数的数量,而不是具有相同符号的连续数字的数量)。

以下是相同代码的更详细版本,并提供了一些可能更容易理解的解释:

def getNegativeCounts(x):
    # This function takes as input a sequence of numbers, all the same sign.
    # If they're negative, it returns an increasing count of how many there are.
    # If they're positive, it just returns the same number of zeros.
    # [-1, -2, -3] -> [1, 2, 3]
    # [1, 2, 3] -> [0, 0, 0]
    if (x<0).all():
        return pandas.Series(np.arange(len(x))+1, index=x.index)
    else:
        return pandas.Series(np.zeros(len(x)), index=x.index)

# we have to reverse the data because cumsum only works in the forward direction
x = data[::-1]

# compute for each number whether it has the same sign as the previous one
sameSignAsPrevious = (x<0) != (x<0).shift()
# cumsum this to get an "ID" for each block of consecutive same-sign numbers
sameSignBlocks = sameSignAsPrevious.cumsum()
# group on these block IDs
g = x.groupby(sameSignBlocks)
# for each block, apply getNegativeCounts
# this will either give us the running total of negatives in the block,
# or a stretch of zeros if the block was positive
# the [::-1] at the end reverses the result
# (to compensate for our reversing the data initially)
g.apply(getNegativeCounts)[::-1]

正如您所看到的,在pandas中,游程长度式操作通常不简单。但是,an open issue可以添加更多分组/分区功能,从而改善其中一些功能。在任何情况下,您的特定用例都有一些特定的怪癖,使其与典型的运行长度任务略有不同。