Ускоренный подсчет повторяющихся вхождений в зависимости от конкретных c условий в Pandas кадре данных - PullRequest
1 голос
/ 01 августа 2020

У меня есть такой Pandas фрейм данных:

name number
A   0.8466
A   0.8463
A   0.8482
A   0.8455
A   0.8423
A   0.8405
A   0.842
A   0.8453
A   0.8419
A   0.8394
A   0.8376
A   0.8368
A   0.8388
A   0.8392
A   0.8409
A   0.8415
A   0.8424
A   0.8425
A   0.8433
A   0.8412
A   0.8397

Я хотел бы подсчитать, сколько раз upper и сколько раз условие lower выполняется в скользящем окне 3 number значений. У меня есть рабочий код:

df['Upper'] = 0
df['Lower'] = 0
for i in range(len(df)):
    if i < 1:
        df['Upper'].iloc[i] = 0
        df['Lower'].iloc[i] = 0
    else:
        chip_sum_r = 0
        chip_sum_s = 0
        for j in range(3):
            if df['name'].iloc[i-j] == df['name'].iloc[i]:
                if df['number'].iloc[i-j] <= df['number'].iloc[i]*1.003 and df['number'].iloc[i-j] >   df['number'].iloc[i]:
                    chip_sum_r += 1
                if df['number'].iloc[i-j] >= df['number'].iloc[i]*0.997 and df['number'].iloc[i-j] < df['number'].iloc[i]:
                   chip_sum_s += 1
        df['Upper'].iloc[i] = chip_sum_r
        df['Lower'].iloc[i] = chip_sum_s

В итоге я получаю:

    name  number   Upper  Lower
0     A  0.8466      0      0
1     A  0.8463      1      0
2     A  0.8482      0      2
3     A  0.8455      1      0
4     A  0.8423      0      0
5     A  0.8405      1      0
6     A  0.8420      1      1
7     A  0.8453      0      0
8     A  0.8419      1      0
9     A  0.8394      1      0
10    A  0.8376      1      0
11    A  0.8368      1      0
12    A  0.8388      0      2
13    A  0.8392      0      2
14    A  0.8409      0      2
15    A  0.8415      0      2
16    A  0.8424      0      2
17    A  0.8425      0      2
18    A  0.8433      0      2
19    A  0.8412      2      0
20    A  0.8397      1      0

Однако это очень медленно для большого количества точек данных. Есть ли способ ускорить это, я не уверен, подойдет ли здесь векторизованный подход?

Ответы [ 3 ]

1 голос
/ 01 августа 2020

Конечно, мы можем сделать это через numpy трансляцию

n=3
s=df.number.values
s=s[:,None]/s
df['Lower']=np.sum(np.tril(np.triu(np.tril((s<=1.003) & (s>1)), -n+1)), 1)
df['Upper']=np.sum(np.tril(np.triu(np.tril((s<1) & (s>=0.997)), -n+1)), 1)
df
Out[52]: 
   name  number  Lower  Upper
0     A  0.8466      0      0
1     A  0.8463      0      1
2     A  0.8482      2      0
3     A  0.8455      0      1
4     A  0.8423      0      0
5     A  0.8405      0      1
6     A  0.8420      1      1
7     A  0.8453      0      0
8     A  0.8419      0      1
9     A  0.8394      0      1
10    A  0.8376      0      1
11    A  0.8368      0      1
12    A  0.8388      2      0
13    A  0.8392      2      0
14    A  0.8409      2      0
15    A  0.8415      2      0
16    A  0.8424      2      0
17    A  0.8425      2      0
18    A  0.8433      2      0
19    A  0.8412      0      2
20    A  0.8397      0      1
1 голос
/ 01 августа 2020

Хотя я не уверен, что это значительно увеличит вашу производительность после тестирования (10 миллисекунд), вы можете использовать .shift() как альтернативу циклическому просмотру строк и np.where() для ваших условий. В приведенном ниже решении можно было бы всего 1 oop 3 раза, но я тестировал решение Йорбена, и он был в 30 раз быстрее - 0,3 миллисекунды на 1 oop.

df['Upper'] = 0
df['Lower'] = 0
for j in range(3):
    df['Upper'] = np.where((df['name'] == df.shift(j)['name'])
                                & (df.shift(j)['number'] <= df['number']*1.003)
                                & (df.shift(j)['number'] >  df['number']),
                                df['Upper'] + 1, df['Upper'])
    df['Lower'] = np.where((df['name'] == df.shift(j)['name'])
                                & (df.shift(j)['number'] >= df['number']*0.997)
                                & (df.shift(j)['number'] <  df['number']),
                                df['Lower'] + 1, df['Lower'])

вывод:

  name  number  Upper   Lower
0   A   0.8466  0   0
1   A   0.8463  1   0
2   A   0.8482  0   2
3   A   0.8455  1   0
4   A   0.8423  0   0
5   A   0.8405  1   0
6   A   0.8420  1   1
7   A   0.8453  0   0
8   A   0.8419  1   0
9   A   0.8394  1   0
10  A   0.8376  1   0
11  A   0.8368  1   0
12  A   0.8388  0   2
13  A   0.8392  0   2
14  A   0.8409  0   2
15  A   0.8415  0   2
16  A   0.8424  0   2
17  A   0.8425  0   2
18  A   0.8433  0   2
19  A   0.8412  2   0
20  A   0.8397  1   0
0 голосов
/ 01 августа 2020

Вы могли бы иметь возможность векторизовать, применив пользовательскую функцию к скользящему окну размером 3

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.core.window.rolling.Rolling.apply.html

Даже если это не векторизация для ваша функция, если вы используете движок Numba (требующий установки пакета), вы, вероятно, увидите значительный прирост производительности по сравнению с raw python.

EDIT: если это применимо, что-то вроде обычного pd или np, скорее всего, это не векторизован. Вместо этого можно было бы сгенерировать кучу промежуточных столбцов с помощью .shift () и построить вычисление таким образом - тогда каждое промежуточное вычисление будет векторизовано.

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