Повысить скорость фильтра панд, сохранив индексы?

1

У меня есть следующий df:

df = pd.DataFrame({'ID1':[1,2,3,4,5,6],'ID2':[2,6,6,2,1,2],'AREA':[1,1,1,1,1,1]})
...

    ID1 ID2 AREA
0   1   2   1
1   2   6   1
2   3   6   1
3   4   2   1
4   5   1   1
5   6   2   1

Я аккумулирую столбец AREA так:

for id_ in df.ID1:   
    id1_filter = df.ID1 == id_
    id2_filter = (df.ID1 == id_) | (df.ID2 == id_)
    df.loc[id1_filter, 'AREA'] = df.loc[id2_filter].AREA.sum()

print(df)
...
ID1 ID2 AREA
0   1   2   2
1   2   6   5
2   3   6   1
3   4   2   1
4   5   1   1
5   6   2   7

Для каждого id_ в ID1 AREA суммируется, где ID1 == id_ или ID2 == id_, и он всегда запускается, когда df сортируется по ID1.

Реальный фреймворк данных, над которым я работаю, составляет 150 000 записей, каждая строка которых принадлежит уникальному ID1. Выполнение вышеуказанного на этом кадре данных занимает 2,5 часа. Поскольку эта операция будет проходить неоднократно в обозримом будущем, я решил сохранить индексы True значений в id1_filter и id2_filter в БД со следующей схемой.

Таблица ID1:

ID_,INDEX_
1  ,   0
2  ,   1
etc, ect

Таблица ID2:

ID_,INDEX_
1  ,   0
1  ,   4
2  ,   0
2  ,   1
2  ,   3
2  ,   5
etc, etc

В следующий раз, когда я запустил накопление в столбце AREA (теперь заполненное разными значениями AREA), я прочитал в таблицах sql и конвертировал их в dicts. Затем я использую эти dicts для захвата записей, которые мне нужны во время цикла суммирования.

id1_dict = pd.read_sql('select * from ID1',db_engine).groupby('ID_').INDEX_.unique().to_dict()
id2_dict = pd.read_sql('select * from ID2',db_engine).groupby('ID_').INDEX_.unique().to_dict()

# print indices for id1_filter and id2_fillter for id 1
print(id1_dict[1])
print(id2_dict[1])
...
[0]
[0, 4]

 for id_ in df.ID1:
        df.loc[id1_dict[id_], 'AREA'] = df.loc[id2_dict[id_]].AREA.sum()

При работе таким образом это занимает всего 6 минут!

Мой вопрос: есть ли лучший/стандартный способ справиться с этим сценарием, т.е. Сохранить выбор данных для дальнейшего использования? Боковое замечание. Я установил индекс в столбцах идентификатора таблицы SQL и попытался получить индексы, запросив таблицу для каждого идентификатора, и он работает хорошо, но все равно занимает немного больше, чем выше (9 минут).

Теги:
pandas
dataframe

1 ответ

1
Лучший ответ

Один из способов сделать это:

df = df.set_index('ID1') 
for row in df.join(df.groupby('ID2')['AREA'].apply(lambda x: x.index.tolist()),rsuffix='_').dropna().itertuples():
    df.loc[row[0],'AREA'] += df.loc[row[3],'AREA'].sum()
df = df.reset_index()

и вы получите ожидаемый результат

   ID1  ID2  AREA
0    1    2     2
1    2    6     5
2    3    6     1
3    4    2     1
4    5    1     1
5    6    2     7

Теперь на более df например:

df = pd.DataFrame( {'ID1':range(1,1501),'ID2': np.random.randint(1,1501,(1500,)),'AREA':[1]*1500}, 
                   columns = ['ID1','ID2','AREA'])

Представленный здесь метод поворачивается примерно на 0,76 с на моем компьютере, в то время как ваш первый работает в 6.5 с.

В конечном итоге вы можете создать df_list например:

df_list = (df.set_index('ID1')
             .join(df.set_index('ID1').groupby('ID2')['AREA']
                     .apply(lambda x: x.index.tolist()),rsuffix='_ID2')
             .dropna().drop(['AREA','ID2'],1))

сохранить где-нибудь информацию, связанную с ID1 и ID2: здесь вы можете увидеть, что идентификатор равен 2 в столбце ID2, где значение ID1 = 1, 4 и 6

      AREA_ID2
ID1           
1          [5]
2    [1, 4, 6]
6       [2, 3]

а затем вы можете запустить, чтобы не воссоздать df_list с небольшой разницей в коде:

df = df.set_index('ID1') 
for row in df_list.itertuples():
    df.loc[row[0],'AREA'] += df.loc[row[1],'AREA'].sum()
df = df.reset_index()

Надеюсь, что это быстрее

  • 1
    Это оно! Это займет всего 3 минуты, чтобы запустить весь Shebang. Я многому научился из этого кода, и он указал мне на некоторые явные недостатки.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню