Повышение производительности цикла for, сравнивающего строки данных pandas - PullRequest
0 голосов
/ 27 июня 2018

У меня проблема с производительностью Python / Pandas. У меня есть цикл для сравнения последовательных строк в Pandas DataFrame:

for i in range(1, N):
    if df.column_A.iloc[i] == df.column_A.iloc[i-1]:
        if df.column_B.iloc[i] == 'START' and df.column_B.iloc[i-1] == 'STOP':
            df.time.iloc[i] = df.time.iloc[i] - df.time.iloc[i-1]

Который работает правильно, но очень медленно. Мой фрейм данных имеет около 1 млн строк, и мне интересно, есть ли способ улучшить производительность. Я читал о векторизации, но не могу понять, с чего начать.

1 Ответ

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

Я думаю, вы можете использовать shift и mask:

mask = ((df.column_A == df.column_A.shift()) 
        & (df.column_B == 'START') & (df.column_B.shift() == 'STOP'))
df.loc[mask, 'time'] -= df.time.shift().loc[mask]

Маска выбирает строку, где значение в 'column_A' равно значению в предыдущем (получено с помощью shift), а где "column_B" равно "START", а предыдущая строка - "STOP". Использование loc позволяет изменить значение для всех выбранных строк на mask в столбце «время», удалив значение в предыдущей строке (снова shift) с той же маской во времени столбца

РЕДАКТИРОВАТЬ: с примером:

df = pd.DataFrame({'column_A': [0,1,1,2,1,2,2], 'column_B': ['START', 'STOP', 'START','STOP', 'START','STOP', 'START'], 'time':range(7)})
   column_A column_B  time
0         0    START     0
1         1     STOP     1
2         1    START     2
3         2     STOP     3
4         1    START     4
5         2     STOP     5
6         2    START     6

поэтому здесь строки с номерами 2 и 6 соответствуют вашему условию, поскольку предыдущая строка имеет то же значение в column_A и получает «START» в column_B, а предыдущая строка имеет «STOP».

После запуска кода вы получите df:

   column_A column_B  time
0         0    START   0.0
1         1     STOP   1.0
2         1    START   1.0
3         2     STOP   3.0
4         1    START   4.0
5         2     STOP   5.0
6         2    START   1.0

где значение во времени в строке 2 равно 1 (первоначально 2 минус значение в предыдущей строке 1) и то же самое для строки 6 (6 - 5)

РЕДАКТИРОВАТЬ для сравнения времени давайте создадим df с 3000 строками

df = pd.DataFrame( [['A', 'START', 3], ['A', 'STOP', 6], ['B', 'STOP', 2], 
                    ['C', 'STOP', 1], ['C', 'START', 9], ['C', 'STOP', 7]],
                   columns=['column_A', 'column_B', 'time'] )
df = pd.concat([df]*500)
df.shape
Out[16]: (3000, 3)

теперь создайте две функции двумя методами:

# original method
def espogian (df):
    N = df.shape[0]
    for i in range(1, N):
        if df.column_A.iloc[i] == df.column_A.iloc[i-1]:
            if df.column_B.iloc[i] == 'START' and df.column_B.iloc[i-1] == 'STOP':
                df.time.iloc[i] = df.time.iloc[i] - df.time.iloc[i-1]
    return df
# mine
def ben(df):
    mask = ((df.column_A == df.column_A.shift()) 
        & (df.column_B == 'START') & (df.column_B.shift() == 'STOP'))
    df.loc[mask, 'time'] -= df.time.shift().loc[mask]
    return df

и запустить timeit:

%timeit espogian (df)
1 loop, best of 3: 8.71 s per loop

%timeit ben (df)
100 loops, best of 3: 4.79 ms per loop

# verify they are equal
df1 = espogian (df)
df2 = ben (df)
(df1==df2).all()
Out[24]: 
column_A    True
column_B    True
time        True
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...