как найти, сколько раз значения строки последовательно достигают максимума - PullRequest
3 голосов
/ 17 мая 2019

Я хочу узнать, сколько раз значения строки последовательно достигают максимума.

  • Ps1: у моих данных есть 500K строк, поэтому я беспокоюсь о скорости вычислений

  • Ps2: в этом примере startDay = 1 и endDay = 7, но некоторые строки имеют другой день начала или окончания. (например, startDay = 2, endDay = 5 или startDay = 4, endDay = 3. arr_bool контролирует эти условия)

Мои данные:

import pandas as pd
import numpy as np
idx = ['id1', 'id2', 'id3', 'id4', 'id5',
       'id6', 'id7', 'id8', 'id9', 'id10']
data = {'Day1':[0,0,1,0,1,1,0,0,1,1],
        'Day2':[0,1,1,1,2,1,0,1,1,2],
        'Day3':[1,3,1,1,1,0,0,1,3,2],
        'Day4':[1,2,0,1,1,0,0,2,1,1],
        'Day5':[0,2,1,1,1,1,0,2,1,1],
        'Day6':[1,0,1,1,2,1,0,2,1,1],
        'Day7':[0,0,0,1,1,1,0,0,3,1]}

startday = pd.DataFrame([1,1,1,1,1,1,1,1,1,1],columns=['start'], index=idx)
endday = pd.DataFrame([7,7,7,7,7,7,7,7,7,7],columns=['end'], index=idx)
df = pd.DataFrame(data, index=idx)
Neg99 = -999
Neg90 = -900

Я должен искать интервал времени для каждой строки (например, цикл с начала дня до конца дня) Я могу найти максимальные значения во временном интервале, но не могу найти число значений строки, последовательно ударивших по максимуму.

arr_bool = (np.less_equal.outer(startday.start, range(1,8)) 
            & np.greater_equal.outer(endday.end, range(1,8))
            )
df_result = pd.DataFrame(df.mask(~arr_bool).max(axis=1),
                                        index=idx, columns=['result'])

Последние условия:

df_result.result= np.select( condlist = [startday.start > endday.end,
                                         ~arr_bool.any(axis=1)],
                         choicelist = [Neg99,Neg90], 
                         default = df_result.result)

Результат, который я хочу;

result_i_want = pd.DataFrame([2,1,3,6,1,3,0,3,1,2],columns=['result'], index=idx)

Вот решения @WeNYoBen, но они работают медленно;

s=((df.eq(df.max(1),0))&(df.ne(0)))
s.apply(lambda x : x[x].groupby((~x).cumsum()).count().max(),1).fillna(0)

Ответы [ 3 ]

4 голосов
/ 17 мая 2019

Чистая нарезка Numpy и прочее

Смысл этих усилий в том, чтобы ОП попросил скорость. Это должно помочь. Если у вас есть доступ к библиотеке JIT, такой как numba, вы должны использовать ее и просто зацикливаться на каждой строке.

sd = startday.start.values
ed = endday.end.values

dr = ed - sd + 1

i = np.arange(len(df)).repeat(dr)
j = np.concatenate([np.arange(s - 1, e) for s, e in zip(sd, ed)])

v = df.values

mx = np.empty(len(v), dtype=v.dtype)
mx.fill(v.min())
np.maximum.at(mx, i, v[i, j])

b = np.ones((v.shape[0], v.shape[1] + 2), bool)

b[i, j + 1] = (v[i, j] != mx[i]) | (mx[i] == 0)

x, y = np.where(b)

y_ = np.diff(y)
mask = y_ > 0
y__ = y_[mask]
x__ = x[1:][mask]

c = np.empty(len(v), int)
c.fill(y__.min())
np.maximum.at(c, x__, y__)

c - 1

array([2, 1, 3, 6, 1, 3, 0, 3, 1, 2])

Объяснение

Я оставлю очевидное в покое.

Это число дней в каждом интервале

dr = ed - sd + 1

i - сглаженные индексы соответствующих строк для соответствующих сглаженных индексов столбцов в j

i = np.arange(len(df)).repeat(dr)
j = np.concatenate([np.arange(s - 1, e) for s, e in zip(sd, ed)])

mx будет максимальным значением для каждого интервала.

b будет логическим массивом с шириной на 2 столбца больше, чем v. Для этого случая это выглядит так:

#       Buffer                                                  Buffer
#        /--\                                                    /--\
array([[ True,  True,  True, False, False,  True, False,  True,  True],
       [ True,  True,  True, False,  True,  True,  True,  True,  True],
       [ True, False, False, False,  True, False, False,  True,  True],
       [ True,  True, False, False, False, False, False, False,  True],
       [ True,  True, False,  True,  True,  True, False,  True,  True],
       [ True, False, False,  True,  True, False, False, False,  True],
       [ True, False, False, False, False, False, False, False,  True],
       [ True,  True,  True,  True, False, False, False,  True,  True],
       [ True,  True,  True, False,  True,  True,  True, False,  True],
       [ True,  True, False, False,  True,  True,  True,  True,  True]])

Причина появления столбцов буфера в том, что я могу вычислить разницу позиций после использования np.where

Теперь я заполняю b, где значения v не равны максимальным значениям в mx

 #             not equal to max       is equal to zero
 b[i, j + 1] = (v[i, j] != mx[i]) | (mx[i] == 0)

Затем я нахожу, где находятся эти позиции y.

Взяв diff, я нахожу количество позиций от одного экземпляра, не равного max, до следующей позиции, не равной max. Это всегда будет на единицу больше, чем искомое число, но мы исправим это позже.

Кроме того, diff уменьшит длину на единицу, но на самом деле есть куча вещей, которые нам не нужны, потому что мне не нужно брать разницу из одного ряда относительно предыдущего ряда. К счастью, я могу избавиться от всех нулевых или отрицательных различий, потому что они не имеют смысла.

Я использую np.maximum.at (снова), но на этот раз для различий, чтобы найти наибольшую разницу, и это будет наибольшая длина последовательных максимальных значений для каждой строки.

Имейте в виду, что это на самом деле больше, чем

Уф. Я устал печатать ...

2 голосов
/ 17 мая 2019

Вот еще одно numpy решение.Во-первых, сроки по сравнению с @piRSquared для справки.Мой код в 14 раз быстрее на большом примере, но дает точно такой же результат.

# both methods give the expected result on small OP example                                                        
      result                                                                                                    
id1        2                                                                                                    
id2        1                                                                                                    
id3        3                                                                                                    
id4        6                                                                                                    
id5        1                                                                                                    
id6        3                                                                                                    
id7        0                                                                                                    
id8        3                                                                                                    
id9        1                                                                                                    
id10       2                                                                                                    
      result                                                                                                    
id1        2                                                                                                    
id2        1                                                                                                    
id3        3                                                                                                    
id4        6                                                                                                    
id5        1                                                                                                    
id6        3                                                                                                    
id7        0                                                                                                    
id8        3                                                                                                    
id9        1
id10       2

# timings on 50,000 rows random example
pp 12.89263810031116
pi 189.0821446024347
# comparison of results
result    True
dtype: bool

Код:

import pandas as pd
import numpy as np

# OP example
idx = ['id1', 'id2', 'id3', 'id4', 'id5',
       'id6', 'id7', 'id8', 'id9', 'id10']
data = {'Day1':[0,0,1,0,1,1,0,0,1,1],
        'Day2':[0,1,1,1,2,1,0,1,1,2],
        'Day3':[1,3,1,1,1,0,0,1,3,2],
        'Day4':[1,2,0,1,1,0,0,2,1,1],
        'Day5':[0,2,1,1,1,1,0,2,1,1],
        'Day6':[1,0,1,1,2,1,0,2,1,1],
        'Day7':[0,0,0,1,1,1,0,0,3,1]}

startday = pd.DataFrame([1,1,1,1,1,1,1,1,1,1],columns=['start'], index=idx)
endday = pd.DataFrame([7,7,7,7,7,7,7,7,7,7],columns=['end'], index=idx)
df = pd.DataFrame(data, index=idx)
Neg99 = -999
Neg90 = -900

# large example
IDX = [f'id{i}' for i in range(1,50_001)]
STARTDAY, ENDDAY = (pd.DataFrame({c:l}, index=IDX) for c,l in zip(('start','end'), np.sort(np.random.randint(1,8,(2,50_000)), axis=0)))
DF = pd.DataFrame({f'Day{i}':l for i,l in enumerate(np.random.randint(0,4,(7, 50_000)), 1)}, index=IDX)

def pp():
    if restrict_max:
        data = np.where((startday.values<=np.arange(1,8)) & (endday.values>=np.arange(1,8)), df.values, 0)
        mask = data==np.maximum((data==0).all(1), data.max(1))[:, None]
    else:
        mask = (df.values==np.maximum((df.values==0).all(1), df.values.max(1))[:, None]) & (startday.values<=np.arange(1,8)) & (endday.values>=np.arange(1,8))
    y, x = np.where(np.diff(mask, axis=1, prepend=False, append=False))
    y = y[::2]
    x = x[1::2]-x[::2]
    res = np.zeros(df.values.shape[:1], int)
    nl = np.flatnonzero(np.diff(y, prepend=-1))
    res[y[nl]] = np.maximum.reduceat(x, nl)
    return pd.DataFrame({'result': res}, index=df.index)

def pi():
    sd = startday.start.values
    ed = endday.end.values

    dr = ed - sd + 1

    i = np.arange(len(df)).repeat(dr)
    j = np.concatenate([np.arange(s - 1, e) for s, e in zip(sd, ed)])

    v = df.values

    mx = np.empty(len(v), dtype=v.dtype)
    mx.fill(v.min())
    np.maximum.at(mx, i, v[i, j])

    b = np.ones((v.shape[0], v.shape[1] + 2), bool)

    b[i, j + 1] = (v[i, j] != mx[i]) | (mx[i] == 0)

    x, y = np.where(b)

    y_ = np.diff(y)
    mask = y_ > 0
    y__ = y_[mask]
    x__ = x[1:][mask]

    c = np.empty(len(v), int)
    c.fill(y__.min())
    np.maximum.at(c, x__, y__)

    return pd.DataFrame({'result': c - 1}, index=df.index)

restrict_max=True

print(pp())
print(pi())
df, startday, endday = DF, STARTDAY, ENDDAY

from timeit import timeit

print('pp', timeit(pp,number=10)*100)
print('pi', timeit(pi,number=10)*100)
print((pp()==pi()).all())
0 голосов
/ 17 мая 2019

Попробуйте следующее решение:

Начните с определения функции, которая будет применена к каждой строке:

def fn(row):
    sd = startday.loc[row.name, 'start'] - 1
    ed = endday.loc[row.name, 'end']
    rr = row.values[sd:ed]
    vMax = rr.max()
    if vMax > 0:
        gr = itertools.groupby(rr)
        return max([ len(list(grp)) for key, grp in gr if key == vMax ])
    else:
        return 0

Ключевая разница между itertools.groupby (используется выше) и pd.groupby означает, что версия itertools создает новую группу каждое изменение исходного значения, поэтому каждая группа содержит серию одинаковых значений (из текущей строки).

Первым этапом является выбор нужного фрагмента текущей строки.

  • sd - начальный индекс (включая),
  • ed - конечный индекс (исключая),
  • rr - правильный срез (далее называемый строка ).

Если максимальное значение в текущей строке> 0, то понимание списка используется выше:

  • Читает key и grp (текущая группа) из результата groupby .
  • Если клавиша (значение, включенное в текущую группу) является максимальное значение, тогда значение, добавленное к результату, является длиной текущей группы.

Значение, возвращаемое функцией, составляет максимум из этого списка, то есть длина самой длинной последовательности максимальных значений.

Если текущая строка содержит только нули (max == 0), вернуть 0.

Тогда единственное, что нужно сделать, это применить вышеуказанную функцию к каждой строке:

df['result'] = df.apply(fn, axis=1)

Конечно, вам нужно импортировать itertools .

Преимущество моего решения перед другим ответом в том, что оно значительно короче.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...