我有一个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
。我需要解决一般的正负整数。
答案 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可以添加更多分组/分区功能,从而改善其中一些功能。在任何情况下,您的特定用例都有一些特定的怪癖,使其与典型的运行长度任务略有不同。