Итерация по DataFrame и отслеживание определенной продолжительности последовательности - PullRequest
0 голосов
/ 27 июня 2018

Я бы хотел выяснить, как часто встречаются отрицательные значения и как долго эта отрицательная цена возникает.

пример df

d = {'value': [1,2,-3,-4,-5,6,7,8,-9,-10], 'period':[1,2,3,4,5,6,7,8,10]}
df = pd.DataFrame(data=d)

Я проверил, какие строки имеют отрицательные значения. df['value'] < 0

Я подумал, что мог бы просто перебрать каждую строку, сохранить счетчик для случая, когда возникает отрицательное значение и, возможно, переместить эту строку в другой df, как я хотел бы сохранить начало period и конец period.

Что я сейчас пытаюсь

def count_negatives(df):
    df_negatives = pd.DataFrame(columns=['start','end', 'counter'])
    for index, row in df.iterrows():
        counter = 0
        df_negative_index = 0

        while(row['value'] < 0):
            # if its the first one add it to df as start ?
            # grab the last one and add it as end
            #constantly overwrite the counter?
            counter += 1
        #add counter to df row
        df_negatives['counter'] = counter
    return df_negatives

За исключением того, что это дает мне бесконечный цикл, я думаю. Если я заменим while на if, я застрял, придя к способу отслеживать, как долго.

Ответы [ 2 ]

0 голосов
/ 27 июня 2018

Я хотел бы сохранить начальный и конечный период.

Если это ваше требование, вы можете использовать itertools.groupby. Также обратите внимание, что серия period не требуется, поскольку Pandas предоставляет натуральный целочисленный индекс (начиная с 0), если не указано явно.

from itertools import groupby
from operator import itemgetter

d = {'value': [1,2,-3,-4,-5,6,7,8,-9,-10]}
df = pd.DataFrame(data=d)

ranges = []
for k, g in groupby(enumerate(df['value'][df['value'] < 0].index), lambda x: x[0]-x[1]):
    group = list(map(itemgetter(1), g))
    ranges.append((group[0], group[-1]))

print(ranges)

[(2, 4), (8, 9)]

Затем, чтобы преобразовать в фрейм данных:

df = pd.DataFrame(ranges, columns=['start', 'end'])

print(df)

   start  end
0      2    4
1      8    9
0 голосов
/ 27 июня 2018

Я думаю, лучше избегать петель:

#compare by < 
a = df['value'].lt(0)
#running sum
b = a.cumsum()
#counter only for negative consecutive values
df['counter'] = b-b.mask(a).ffill().fillna(0).astype(int)
print (df)
   value  period  counter
0      1       1        0
1      2       2        0
2     -3       3        1
3     -4       4        2
4     -5       5        3
5      6       6        0
6      7       7        0
7      8       8        0
8     -9       9        1
9    -10      10        2

Или, если не требуется сброс счетчика:

a = df['value'].lt(0)
#repalce values per mask a to 0
df['counter'] = a.cumsum().where(a, 0)
print (df)
   value  period  counter
0      1       1        0
1      2       2        0
2     -3       3        1
3     -4       4        2
4     -5       5        3
5      6       6        0
6      7       7        0
7      8       8        0
8     -9       9        4
9    -10      10        5

Если хотите начальный и конечный период:

#comapre for negative mask
a = df['value'].lt(0)
#inverted mask
b = (~a).cumsum()

#filter only negative rows
c = b[a].reset_index()

#aggregate first and last value per groups
df = (c.groupby('value')['index']
       .agg([('start', 'first'),('end', 'last')])
       .reset_index(drop=True))
print (df)
   start  end
0      2    4
1      8    9
...