从另一个 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.mergehow='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,然后填充它?

从另一个 DataFrame 将列添加到 Pyspark DataFrame

如何使用熊猫从另一个数据框中的一个数据框中查找值?

从另一个 DataFrame 添加一列