Проще говоря, я пытаюсь сравнить значения из двух столбцов первого DataFrame с такими же столбцами в другом DataFrame. Индексы совпадающих строк сохраняются как новый столбец в первом DataFrame.

Позвольте мне пояснить : я работаю с географическими объектами (широта / долгота), и основной DataFrame, называемый df, имеет примерно 55 миллионов наблюдений, которые выглядят как немного так:

enter image description here

Как видите, всего две строки с данными, которые выглядят достоверными (индексы 2 и 4).

Второй DataFrame, называемый legit_df, намного меньше и содержит все географические данные, которые я считаю правильными:

enter image description here

Не вдаваясь в подробности ПОЧЕМУ, основная задача заключается в сравнении каждого наблюдения широты / долготы из df с данными legit_df. При успешном совпадении индекс legit_df копируется в новый столбец df, в результате чего df выглядит следующим образом:

enter image description here

Значение -1 используется, чтобы показать, когда не было успешного совпадения. В приведенном выше примере действительными были только наблюдения с индексами 2 и 4, которые нашли свои совпадения с индексами 1 и 2 в legit_df.

В моем текущем подходе к решению этой проблемы используется .apply(). Да, это медленно, но я не смог найти способ векторизовать функцию ниже или использовать Cython для ее ускорения:

def getLegitLocationIndex(lat, long):
    idx = legit_df.index[(legit_df['pickup_latitude'] == lat) & (legit_df['pickup_longitude'] == long)].tolist()
    if (not idx):
        return -1
    return idx[0]

df['legit']  = df.apply(lambda row: getLegitLocationIndex(row['pickup_latitude'], row['pickup_longitude']), axis=1)

Поскольку этот код работает очень медленно на DataFrame с 55 млн наблюдений, у меня вопрос : есть ли более быстрый способ решить эту проблему?

Я делюсь кратким, автономным, правильным (компилируемым) примером для help-you-help -me предложить более быструю альтернативу:

import pandas as pd
import numpy as np

data1 = { 'pickup_latitude'  : [41.366138,   40.190564,  40.769413],
          'pickup_longitude' : [-73.137393, -74.689831, -73.863300]
        }

legit_df = pd.DataFrame(data1)
display(legit_df)

####################################################################################

observations = 10000
lat_numbers = [41.366138,   40.190564,  40.769413, 10, 20, 30, 50, 60, 80, 90, 100]
lon_numbers = [-73.137393, -74.689831, -73.863300, 11, 21, 31, 51, 61, 81, 91, 101]

# Generate 10000 random integers between 0 and 10
random_idx = np.random.randint(low=0, high=len(lat_numbers)-1, size=observations)
lat_data = []
lon_data = []

# Create a Dataframe to store 10000 pairs of geographical coordinates
for i in range(observations):
    lat_data.append(lat_numbers[random_idx[i]])
    lon_data.append(lon_numbers[random_idx[i]])

df = pd.DataFrame({ 'pickup_latitude' : lat_data, 'pickup_longitude': lon_data })
display(df.head())

####################################################################################

def getLegitLocationIndex(lat, long):
    idx = legit_df.index[(legit_df['pickup_latitude'] == lat) & (legit_df['pickup_longitude'] == long)].tolist()
    if (not idx):
        return -1
    return idx[0]


df['legit']  = df.apply(lambda row: getLegitLocationIndex(row['pickup_latitude'], row['pickup_longitude']), axis=1)
display(df.head())

В приведенном выше примере создается df всего с 10 КБ observations, что занимает около 7 секунд для запуска на моем компьютере. При 100k observations запуск занимает ~ 67 секунд. А теперь представьте мои страдания, когда мне нужно обработать 55 миллионов строк ...

1
karlphillip 25 Сен 2018 в 09:23

2 ответа

Лучший ответ

Я думаю, вы можете значительно ускорить это, используя слияние вместо текущей логики:

full_df = df.merge(legit_df.reset_index(), how="left", on=["pickup_longitude", "pickup_latitude"])

Это сбрасывает индекс справочной таблицы, чтобы сделать ее столбцом, и объединяется по долготе

full_df = full_df.rename(index = str, columns={"index":"legit"})
full_df["legit"] = full_df["legit"].fillna(-1).astype(int)

Это переименовывает в имя столбца, после которого вы были, и заполняет все пропуски в столбце соединения с -1

< Сильный > Ориентиры :

Старый подход: 5.18 s ± 171 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Новый подход: 23.2 ms ± 1.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
Sven Harris 25 Сен 2018 в 06:52

Вы можете DataFrame.merge с how='left' на общих ключах. Сначала сбрасываем индекс legit_df.

Затем fillna с -1:

df.merge(legit_df.reset_index(), on=['pickup_latitude', 'pickup_longitude'], how='left').fillna(-1)

Производительность тестирования:

%%timeit
df['legit']  = df.apply(lambda row: getLegitLocationIndex(row['pickup_latitude'], row['pickup_longitude']), axis=1)

5,81 с ± 179 мс на цикл (среднее ± стандартное отклонение из 7 прогонов, по 1 циклу в каждом)

%%timeit
(df.merge(legit_df.reset_index(),on=['pickup_latitude', 'pickup_longitude'], how='left').fillna(-1))

6,27 мс ± 254 мкс на цикл (среднее ± стандартное отклонение из 7 прогонов, по 100 циклов в каждом)

1
Chris A 25 Сен 2018 в 06:48