2015-04-07 2 views
3

У меня есть DataFrame с одним столбцом с положительными и отрицательными целыми числами. Для каждой строки я хотел бы видеть, сколько последовательных строк (начиная с текущей строки и включая ее) имеют отрицательные значения.Проверка последующих значений в 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 в моем примере. Мне нужно решить для положительных и отрицательных целых чисел вообще.

ответ

4

FWIW, вот довольно пандативный ответ, который не требует каких-либо функций или применяется.Занимает от here (среди других ответов я уверен), и благодаря @DSM за упоминание восходящего = 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.

+1

Это ближе к тому, что я собирался написать, но вы можете упростить, выполнив что-то вроде 'cumcount (восходящий = False) + 1'. Тем не менее, я слишком ленив, чтобы проверить случаи краев. :-) – DSM

+0

@DSM Спасибо, внесли изменения. Проще и намного быстрее. – JohnE

+0

Это отлично работает, когда DataFrame содержит только «1» и «-1», но, похоже, не работает, когда они принимают другие значения. Ошибка моя, потому что я сформулировал свой вопрос путающе - я сформулировал свою проблему в терминах целых чисел, но в примере я только положил «1» и «-1». (Я все же поддержал это, хотя, потому что он решил пример). – ASGM

3

Это была интересная головоломка. Я нашел способ сделать это, используя инструменты 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, и вы можете найти один и тот же подход, используемый в различных ответов, спросите, как использовать информацию о нескольких строках в пандах. Ваш вопрос немного сложнее, потому что ваш критерий идет обратным образом (с запросом числа , следующего за негативами, а не числа , предшествующего негативам), и потому, что вы хотите только одну сторону группировки (т. Е. Вам нужен только номер последовательных негативов, а не число последовательных чисел с одним и тем же знаком).

Вот более подробная версия одного и того же кода, с некоторым объяснением, что может сделать его легче понять:

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] 

Как вы можете видеть, операции по длине прогона стиле, обычно не просто в панд. Существует, однако, an open issue для добавления дополнительных возможностей группировки/разбиения, которые улучшат некоторые из этих. В любом случае ваш конкретный вариант использования имеет некоторые специфические особенности, которые делают его немного отличным от типичной задачи продолжительной длины.

+0

Оба эти ответы были очень полезными. Я особенно ценил подробное объяснение, которое вы дали. Мне было трудно принять только одного, но решили выбрать @ JohnE, потому что решение было немного проще. Но я бы выбрал оба, если мог. – ASGM

Смежные вопросы