Python 从一个数据帧中查找另一个数据帧中的元素并返回其索引的快速方法

Python 从一个数据帧中查找另一个数据帧中的元素并返回其索引的快速方法,python,pandas,numpy,apply,Python,Pandas,Numpy,Apply,简单地说,我试图将第一个DataFrame的两列中的值与另一个DataFrame中的相同列进行比较。匹配行的索引作为新列存储在firstDataFrame中 让我解释一下:我正在研究地理特征(纬度/经度),主要的数据帧——称为df——有55M的观测值,看起来有点像这样: 如您所见,只有两行数据看起来合法(索引2和4) 第二版代码>数据文件——被称为代码> LeigTyDF ,它要小得多,并且我认为所有的地理数据都是合法的: 不必解释原因,主要任务是将df的每个纬度/经度观测值与legit\u

简单地说,我试图将第一个
DataFrame
的两列中的值与另一个
DataFrame
中的相同列进行比较。匹配行的索引作为新列存储在first
DataFrame

让我解释一下:我正在研究地理特征(纬度/经度),主要的
数据帧
——称为
df
——有55M的观测值,看起来有点像这样:

如您所见,只有两行数据看起来合法(索引2和4)

第二版代码>数据文件——被称为代码> LeigTyDF ,它要小得多,并且我认为所有的地理数据都是合法的:

不必解释原因,主要任务是将
df
的每个纬度/经度观测值与
legit\u df
的数据进行比较。当成功匹配时,
legit_df
的索引被复制到
df
的新列中,导致
df
如下所示:

-1
用于显示未成功匹配的时间。在上面的例子中,唯一有效的观察结果是索引2和4处的观察结果,它们在
legit_df
中的索引1和2处找到了匹配项

我目前解决这个问题的方法是使用
.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)
由于此代码在具有55M观测值的
数据帧上的速度非常慢,我的问题是:有没有更快的方法来解决此问题

我正在分享一个帮助您帮助我想出一个更快的选择:

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
,只需10k
观测值
,在我的机器上运行大约需要7秒。对于100k
观测值
,运行大约需要67秒。现在想象一下,当我不得不处理5500万行时,我的痛苦…

我认为使用合并而不是当前逻辑,可以显著加快速度:

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/圈(7次运行的平均值±标准偏差,每个循环1次)

新方法:
23.2ms±1.3ms/循环(平均±标准偏差为7次运行,每个循环10次)
您可以
DataFrame。在公用键上将
how='left'
合并。首先重置合法df的索引

然后用-1填充NA

df.merge(legit_df.reset_index(), on=['pickup_latitude', 'pickup_longitude'], how='left').fillna(-1)
测试性能: 每个回路5.81 s±179 ms(7次运行的平均值±标准偏差,每个回路1次)


每个回路6.27 ms±254µs(平均±标准偏差为7次运行,每个回路100次)

您在法定df中观察到了哪些地理区域?如果所有的东西都在格林威治的左边,你的经度将是负的,你只需要丢弃所有不在的东西。@coldspeed谢谢,但我唯一感兴趣的是加速这个代码。我只是创建了假位置,以便更容易理解第二个数据帧的需要。我理解这一点,但有时“加速代码”意味着理解代码试图做什么,它正在使用的数据,以及任何相关的领域知识。因此,如果你想提高你的代码速度,我强烈建议你回答任何应该出现的问题。
df.merge(legit\u df.reset\u index(),on=['picku\u latitude','picku\u longide'],how='left')。fillna(-1)
?@ChrisA我将你的合并结果存储到
df['legit']
,我真傻。
%%timeit
df['legit']  = df.apply(lambda row: getLegitLocationIndex(row['pickup_latitude'], row['pickup_longitude']), axis=1)
%%timeit
(df.merge(legit_df.reset_index(),on=['pickup_latitude', 'pickup_longitude'], how='left').fillna(-1))