【问题标题】:Testing subsequent values in a DataFrame在 DataFrame 中测试后续值
【发布时间】:2015-04-07 18:53:06
【问题描述】:

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

所以如果一个序列是2, -1, -3, 1, -1,那么结果就是0, 2, 1, 0, 1

我可以通过遍历所有索引来做到这一点,使用.iloc 拆分列,并使用next() 找出下一个正值在哪里。但我觉得这并没有利用熊猫的能力,我想有更好的方法来做到这一点。我尝试过使用.shift()expanding_window,但没有成功。

有没有一种更“夸张”的方法来找出当前行满足某些逻辑条件之后的连续行数?

下面是现在的工作:

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。我需要解决一般的正整数和负整数。

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    FWIW,这是一个相当熊猫的答案,不需要任何功能或适用。借用here(我敢肯定还有其他答案)并感谢@DSM 提到了ascending=False 选项:

    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
    

    我认为这种方法的一个好处是,一旦设置了“grp”变量,您就可以使用标准的 groupby 方法轻松地做很多事情。

    【讨论】:

    • 这更接近我将要写的内容,但您可以通过像cumcount(ascending=False)+1 这样的操作来简化。不过,我懒得检查边缘情况。 :-)
    • @DSM 谢谢,做出了改变。更简单,更快。
    • 当 DataFrame 仅包含 1-1 时,这很有效,但当它们采用其他值时,它似乎不起作用。错在我,因为我的问题措辞令人困惑——我用整数来表达我的问题,但我只在示例中输入了1-1。 (不过,我仍然赞成它,因为它解决了这个例子)。
    • @ASGM 好的,这非常小。我更新了答案。还将示例数据框更改为以 2 个负数结尾。
    【解决方案2】:

    这是一个有趣的谜题。我找到了一种使用 pandas 工具的方法,但我想你会同意它更加不透明:-)。示例如下:

    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 中的行间信息的答案中找到相同的方法。你的问题有点棘手,因为你的标准是相反的(要求 following 否定的数量而不是 preceding 否定的数量),并且因为你只想要一侧分组(即,您只需要连续负数的数量,而不是具有相同符号的连续数字的数量)。

    这是相同代码的更详细版本,并带有一些解释,可能更容易掌握:

    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 可以添加更多的分组/分区功能来改善其中的一些问题。无论如何,您的特定用例有一些特定的怪癖,使其与典型的运行长度任务有点不同。

    【讨论】:

    • 这两个答案都非常有帮助。我特别感谢您给出的详尽解释。我很难接受一个,但决定选择@JohnE,因为解决方案更简单一些。但如果可以的话,我会选择两者。
    猜你喜欢
    • 2023-04-11
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多