Как бы вы оптимизировали этот короткий, но очень медленный Python l oop? - PullRequest
3 голосов
/ 14 января 2020

Я переключаюсь с R на Python. К сожалению, я обнаружил, что хотя некоторые структуры работают почти мгновенно в R, они занимают несколько секунд (и даже минут) в Python. После прочтения я обнаружил, что циклы настоятельно не рекомендуются в pandas, и рекомендуются другие альтернативы, такие как векторизация и применение.

В этом примере кода: из столбца значений, отсортированных от минимального до максимального, сохраняйте все значения, которые идут первыми после пробела длины '200'.

import numpy as np
import pandas as pd

#Let's create the sample data. It consists of a column with random sorted values, and an extra True/False column, where we will flag the values we want
series = np.random.uniform(1,1000000,100000)
test = [True]*100000
data = pd.DataFrame({'series' : series, 'test':test })
data.sort_values(by=['series'], inplace=True)

#Loop to get rid of the next values that fall within the '200' threshold after the first next valid value
for i in data['series']:
    if data.loc[data['series'] == i,'test'].item() == True:
        data.loc[(data['series'] > i) & (data['series'] <= i+200  ) ,'test' ] = False
#Finally, let's keep the first values after any'200' threshold             
data = data.loc[data['test']==True , 'series']

Можно ли превратить это в функцию, векторизовать, применить или любую другую структуру, отличную от 'for' l * 1010? * заставить его работать почти мгновенно?

Ответы [ 3 ]

3 голосов
/ 14 января 2020

Это мой подход с while l oop:

head = 0
indexes = []
while head < len(data):
    thresh = data['series'].iloc[head] + 200
    indexes.append(head)
    head += 1
    while head < len(data) and data['series'].iloc[head] < thresh:
        head+=1

# output:
data = data.iloc[indexes]

# double check with your approach
set(data.loc[data['test']].index) == set(data.iloc[indexes].index)
# output: True

Выше было 984 мс, в то время как ваш подход занял 56 с.

2 голосов
/ 14 января 2020

searchsorted

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

Обратите внимание, что я использую такой же подход, как Quang, потому что они верны, вы должны l oop. Разница в том, что я использую searchsorted, чтобы найти следующую позицию в каждой позиции, а не зацикливаться на каждой позиции и оценивать, следует ли мне добавить эту позицию.

a = data.series.to_numpy()
head = 0
indexes = [head]
while head < len(data):
    head = a[head:].searchsorted(a[head] + 200) + head
    if -1 < head < len(data):
        indexes.append(head)

data.iloc[indexes]

              series  test
77193       5.663829  True
36166     210.829727  True
85730     413.206840  True
68686     613.849315  True
88026     819.096379  True
...              ...   ...
13863  999074.688286  True
31992  999276.058929  True
71844  999487.746496  True
84515  999690.104536  True
6029   999891.101087  True

[4761 rows x 2 columns]
2 голосов
/ 14 января 2020

Вы можете сделать это с помощью простого однопроходного алгоритма, используя один l oop над серией; нет необходимости в векторизации или что-то в этом роде. На моей машине это занимает 33 миллисекунды, поэтому не "мгновенно", а мигает, и вы пропустите это.

def first_after_gap(series, gap=200):
    out = []
    last = float('-inf')
    for x in series:
        if x - last >= gap:
            out.append(x)
            last = x
    return out

Пример:

>>> import numpy as np
>>> series = sorted(np.random.uniform(1, 1000000, 100000))
>>> from timeit import timeit
>>> timeit(lambda: first_after_gap(series), number=1)
0.03264855599991279
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...