从另一个 Dataframe 中的一个 Dataframe 中查找元素并返回其索引的快速方法
Posted
技术标签:
【中文标题】从另一个 Dataframe 中的一个 Dataframe 中查找元素并返回其索引的快速方法【英文标题】:Fast approach to find elements from one Dataframe in another and return their indexes 【发布时间】:2019-02-28 16:50:24 【问题描述】:简单地说,我正在尝试将第一个 DataFrame
的 2 列中的值与另一个 DataFrame
中的相同列进行比较。匹配行的索引作为新列存储在第一个 DataFrame
中。
让我解释一下:我正在处理地理特征(纬度/经度),主要的 DataFrame
(称为 df
)有大约 55M 的观测值,看起来有点有点像这样:
如您所见,只有两行数据看起来合法(索引 2 和 4)。
第二个DataFrame
——称为legit_df
——要小得多,并且包含我认为合法的所有地理数据:
在不讨论 WHY 的情况下,主要任务是将来自df
的每个纬度/经度观测值与legit_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)
由于这段代码在DataFrame
的观察次数为 55M 时非常慢,我的问题是:有没有更快的方法来解决这个问题?
我将Short, Self Contained, Correct (Compilable), Example 分享给 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
,只有10k observations
,在我的机器上运行大约需要7秒。使用 100k observations
,运行大约需要 67 秒。现在想象一下当我必须处理 55M 行时我的痛苦......
【问题讨论】:
您在 legit_df 中观察哪些地理区域?如果一切都在格林威治的左边,那么您的经度将为负数,您只需丢弃所有不是的。 @coldspeed 谢谢,但我唯一感兴趣的是加速这段代码。我只创建了假位置,因此更容易理解第二个 DataFrame 的需求。 我明白这一点,但有时“加速代码”意味着了解代码试图做什么、正在处理的数据以及任何相关的领域知识。因此,如果您想提高代码加速的机会,我强烈建议您回答任何应该出现的问题。df.merge(legit_df.reset_index(), on=['pickup_latitude', 'pickup_longitude'], how='left').fillna(-1)
?
@ChrisA 我将你的合并结果存储到df['legit']
,我很傻。
【参考方案1】:
我认为您可以使用合并而不是当前逻辑来显着加快这一速度:
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)
【讨论】:
【参考方案2】:您可以在公共键上使用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 ms ± 254 µs(平均值 ± 标准偏差,7 次运行,每次 100 个循环)
【讨论】:
以上是关于从另一个 Dataframe 中的一个 Dataframe 中查找元素并返回其索引的快速方法的主要内容,如果未能解决你的问题,请参考以下文章
pandas dataframe 中的 explode 函数
在 Pandas 中为 DataFrame 中的每一行返回多行
创建一个空的 Pandas DataFrame,然后填充它?