Проще говоря, я пытаюсь сравнить значения из 2 столбцов первого DataFrame
с теми же столбцами в другом DataFrame
. Индексы совпадающих строк сохраняются в виде нового столбца в первом DataFrame
.
Позвольте мне объяснить: я работаю с географическими особенностями (широта/долгота), а основной DataFrame
- df
- имеет что-то вроде наблюдений 55M, которые выглядят примерно так:
Как вы можете видеть, есть только две строки с данными, которые выглядят законными (индексы 2 и 4).
Второй DataFrame
- legit_df
- намного меньше и имеет все географические данные, которые я считаю законными:
Не вдаваясь в ПОЧЕМУ, основная задача заключается в сравнении каждого наблюдения широты/долготы от df
до данных legit_df
. Когда есть успешное совпадение, индекс legit_df
копируется в новый столбец df
, в результате чего df
выглядит следующим образом:
Значение -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
с наблюдениями 55M, мой вопрос: есть ли более быстрый способ решить эту проблему?
Я использую Short, Self Contained, Correct (Compilable), Example, чтобы помочь вам помочь, придумать более быструю альтернативу:
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
только с observations
10 тыс., Что занимает около 7 секунд для запуска на моей машине. При observations
100 тыс. Требуется около 67 секунд. Теперь представьте мое страдание, когда я должен обработать 55M строк...
Я думаю, вы можете значительно ускорить это, используя слияние вместо текущей логики:
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)
Вы можете DataFrame.merge
с тем, how='left'
на общих ключах. legit_df
сначала legit_df
индекс 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 циклов)