Самая быстрая итерация по df до достижения состояния - PullRequest
0 голосов
/ 29 мая 2018
            A       B
0        0.00  514.51
1        0.75  514.51
2        1.10  514.42
3        3.52  514.41
4        5.59  514.43
5        6.52  514.43
6        7.45  514.42
7        5.53  514.42
8        4.53  514.36
9        3.61  514.38
10       1.55  514.36

Я хочу выбрать все строки, пока первая A value не станет больше tan 6.

Выходные данные должны быть:

            A       B
0        0.00  514.51
1        0.75  514.51
2        1.10  514.42
3        3.52  514.41
4        5.59  514.43

Какой режим итераций будет самым быстрым?

Я пробовал:

def first(g):
    if g.A.ge(45.0).any():
        return g[cond].iloc[0]

df.apply(first)

Ответы [ 4 ]

0 голосов
/ 29 мая 2018

Обновление: исправлено с более быстрым ответом

Для коротких фреймов данных или если ответ находится в начале фрейма данных, вы можете получить быстрые результаты с помощью цикла Python for, который повторяется всначала совпадение, а затем разрывы (см. функцию for_loop() ниже).Но если вам нужно отсканировать сотни или тысячи строк, чтобы найти соответствие, то, вероятно, быстрее использовать векторизованные функции, даже если они выполняют одну или две оценки по всей длине кадра данных.

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

Некоторые функции, которые будут выполнять эту работу:

def nonzero():
    # one-liner if you know there are matches:
    # df.iloc[:(df.A >= 6).nonzero()[0][0],:]
    indexes = (df.A.values >= 6).nonzero()[0]
    if len(indexes) > 0:
        return df.iloc[:indexes[0],:]
    else:
        return df

def for_loop():
    result = df
    for i, a in enumerate(df['A']):
        if a >= 6:
            result = df.iloc[:i,:]
            break
    return result

def idxmax():
    return df.loc[:df.A.ge(4.50).idxmax()]

def cumprod():
    return df[df.A.lt(6).cumprod().astype(bool)]

def next_idx():
    return df.iloc[:next(idx for idx in df.index if df.iloc[idx, 0] > 6)]

def test_it(func, reps):
    dur = timeit.timeit(stmt=func+'()', setup='from __main__ import df, '+func, number=reps)
    print('{}: {}'.format(func, dur))

Тесты с небольшим фреймом данных:

df = pd.DataFrame.from_records([
    [0.00, 514.51],
    [0.75, 514.51],
    [1.10, 514.42],
    [3.52, 514.41],
    [5.59, 514.43],
    [6.52, 514.43],
    [7.45, 514.42],
    [5.53, 514.42],
    [4.53, 514.36],
    [3.61, 514.38],
    [1.55, 514.36]
], columns = ['A','B'])

for func in ['nonzero', 'for_loop', 'idxmax', 'cumprod', 'next_idx']:
    test_it(func, 10000)

# nonzero: 1.28068804741
# for_loop: 1.22211813927
# idxmax: 3.8852930069
# cumprod: 6.28086519241
# next_idx: 1.78734588623

Вот тест с большим фреймом данных, где первое совпадение - строка 600 000 из 1 000 000.Я пропустил for_loop и next_idx, потому что они занимают больше минуты для этого теста.

df = pd.DataFrame({'A':pd.np.arange(0,10,0.000001), 'B':514.51})

for func in ['nonzero', 'idxmax', 'cumprod']:
    test_it(func, 100)

# nonzero: 3.25263190269
# idxmax: 9.08449816704
# cumprod: 24.7965559959

Так что похоже, что цикл Python for с возможностью короткого замыкания может быть самым быстрым для небольших кадров данных, но для больших кадров данных быстрее протестировать каждую строку с помощью векторизованной операции, а затем найти смещение совпадающих строк (например, с помощью функции nonzero()).

0 голосов
/ 29 мая 2018

Использование cumprod

df[df.A.lt(6).cumprod().astype(bool)]
Out[303]: 
      A       B
0  0.00  514.51
1  0.75  514.51
2  1.10  514.42
3  3.52  514.41
4  5.59  514.43
0 голосов
/ 29 мая 2018

Вы также можете использовать iloc и next

df.iloc[:next(idx for idx in df.index if df.iloc[idx, 0] > 6)]

Следуя комментариям @jezrael о времени, вот график времени, где

Method1 : df.iloc[:next(idx for idx in df.index if df.iloc[idx, 0] > 6)]
Method2 : df[df.A.lt(6).cumprod().astype(bool)]
Method3 : df.loc[:df.A.ge(4.50).idxmax()]

Так, что

times

В основном M2 и M3 очень близки друг к другу, с небольшим предпочтением для M2, так как df выходит за пределы 100k строк +.M1, безусловно, наименее эффективен для больших dfs, хотя быстрее для очень маленьких dfs.

В основном скорость зависит от того, где находится первое вхождение и насколько велика df.Здесь я установил фиксированное первое вхождение ближе к началу, было бы интересно посмотреть в разных позициях :) Я могу добавить позже

0 голосов
/ 29 мая 2018

Используйте loc с idxmax для выбора всех первых строк True по логической маске:

df = df.loc[:df.A.ge(4.50).idxmax()]
print (df)
      A       B
0  0.00  514.51
1  0.75  514.51
2  1.10  514.42
3  3.52  514.41
4  5.59  514.43

Подробности :

print (df.A.ge(4.50))
0     False
1     False
2     False
3     False
4      True
5      True
6      True
7      True
8      True
9     False
10    False
Name: A, dtype: bool

print (df.A.ge(4.50).idxmax())
4

Существует множество хороших решений, поэтому мне было любопытно узнать время:

Это действительно зависит от позиции первого значения, поэтому я устанавливаю первое значение в половине значения индекса (в реальных данныхдолжно быть по-другому):

df = pd.DataFrame({'A':np.random.rand(10000)})
df.loc[5000, 'A'] = 10
#print (df)

In [66]: %timeit df[df.A.lt(6).cumprod().astype(bool)]
831 µs ± 31.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [67]: %timeit df.loc[:df.A.ge(4.50).idxmax()]
502 µs ± 4.36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [68]: %timeit df.iloc[:next(idx for idx in df.index if df.iloc[idx, 0] > 6)]
67.7 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [69]: %%timeit
    ...: result = df
    ...: for i, a in enumerate(df['A']):
    ...:     if a >= 6:
    ...:         result = df.iloc[:i+1,:]
    ...:         break
    ...: 
845 µs ± 8.93 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...