【问题标题】:Pandas Return Separate DataFrame Values Based on FunctionPandas 根据函数返回单独的 DataFrame 值
【发布时间】:2020-04-16 18:03:05
【问题描述】:

我有两个 DataFrame,df1 是地点的位置,df2 是车站的位置。我试图找到一种更有效的方法来应用距离函数来查找哪些站点在某个范围内并返回站点的名称。如果距离函数是+/- 1 的纬度差,这是我的预期结果:

# df1
   Lat  Long 
0   30    31    
1   37    48    
2   54    62    
3   67    63     

# df2
   Station_Lat  Station_Long Station
0           30            32     ABC    
1           43            48     DEF    
2           84            87     GHI    
3           67            62     JKL    

# ....Some Code that compares df1 and df2....

# result
   Lat  Long  Station_Lat  Station_Long Station
    30    31           30            32     ABC
    67    63           67            62     JKL

我有一个解决方案,它使用 cartesian product/Cross Join 在单个 DataFrame 上应用函数。该解决方案有效,但我在真实数据集中有数百万行,这使得笛卡尔积非常慢。

import pandas as pd
df1 = pd.DataFrame({'Lat' : [30, 37, 54, 67],
                    'Long' : [31, 48, 62, 63]})

df2 = pd.DataFrame({'Station_Lat' : [30, 43, 84, 67],
                    'Station_Long' : [32, 48, 87, 62],
                    'Station':['ABC', 'DEF','GHI','JKL']})

# creating a 'key' for a cartesian product
df1['key'] = 1
df2['key'] = 1

# Creating the cartesian Join
df3 = pd.merge(df1, df2, on='key')

# some distance function that returns True or False
# assuming the distance function I want is +/- 1 of two values
def some_distance_func(x,y):
    return x-y >= -1 and x-y <= 1

# applying the function to a column using vectorized approach
# https://stackoverflow.com/questions/52673285/performance-of-pandas-apply-vs-np-vectorize-to-create-new-column-from-existing-c
df3['t_or_f'] =  list(map(some_distance_func,df3['Lat'],df3['Station_Lat']))

# result
print(df3.loc[df3['t_or_f']][['Lat','Long','Station_Lat','Station_Long','Station']].reset_index(drop=True))

我也尝试过使用iterrows() 的循环方法,但这比交叉连接方法慢。有没有更 Pythonic/有效的方法来实现我正在寻找的东西?

【问题讨论】:

    标签: python pandas dataframe distance


    【解决方案1】:

    也许它更快:

    df2= df2.sort_values("Station_Lat")
    

    排序后,可以使用'searchsorted':

    df1["idx"]=df2.Station_Lat.searchsorted(df1.Lat)
    

    “idx”是“最近的”站纬度。索引,或者 idx+1 就是这个。也许您需要复制 df2 中的最后一行(请参阅“searchsorted doc”)以避免过度索引它。 使用“应用”这个自定义函数:

    def dist(row): 
        if  abs(row.Lat-df2.loc[row.idx].Station_Lat)<=1: 
                return df2.loc[row.idx].Station 
        elif abs(row.Lat-df2.loc[row.idx+1].Station_Lat)<=1: 
                return df2.loc[row.idx+1].Station 
    
        return False 
    
    df1.apply(dist,axis=1)                                                                                               
    
    0      ABC
    1    False
    2    False
    3      JKL
    dtype: object
    

    编辑: 因为在 'dist()' 中假设 df2.index 是有序且单调递增的(参见:roww.idx+1),所以必须更正第一行代码:

    df2= df2.sort_values("Station_Lat").reset_index(drop=True)
    

    'dist()' 这样更快一些(但不优于笛卡尔积方法):

    def dist(row):  
              idx=row.idx 
              lat1,lat2= df2.loc[idx:idx+1,"Station_Lat"] 
              if  abs(row.Lat-lat1)<=1:  
                     return df2.loc[idx,"Station"] 
              elif abs(row.Lat-lat2)<=1:  
                     return df2.loc[idx+1,"Station"] 
              return False 
    

    【讨论】:

    • 我真的很喜欢这个答案,不知道searchsorted 是我可以利用的东西!在这些示例数据帧上,我的 list(map(some_distance_func,df3['Lat'],df3['Station_Lat'])) 仍然更快。我的是60.3 µs ± 20.5 µs per loop,这个答案是2.83 ms ± 575 µs per loop。但我也许可以让这更有效率!
    【解决方案2】:

    lambda 怎么样?

    df3[df3.apply(lambda x, col1='Lat', col2='Station_Lat': x[col1]-x[col2] >= -1 and x[col1]-x[col2] <= 1, axis=1)]['Station']
    

    输出:

    0     ABC
    15    JKL
    

    编辑:这是第二种解决方案。 (注意:这也使用 abs() 因为 >=-1 和

    for i in df1.index:
        for j in df2.index:
            if abs(df1.loc[i, 'Lat'] - df2.loc[j, 'Station_Lat']) <=1:
                print(df2.loc[j, 'Station'])
    

    或者,以列表理解形式:

    df2.loc[[i for i in df1.index for j in df2.index if abs(df1.loc[i, 'Lat'] - df2.loc[j, 'Station_Lat']) <=1], 'Station']
    

    输出:

    ABC
    JKL
    

    【讨论】:

    • 感谢您的回答,不幸的是,这使用了由交叉联接/笛卡尔积创建的 DataFrame。这就是我要避免的。
    • @MattR 酷,酷。我发布了另一个解决方案。不需要df3。您也可以尝试输入提示,例如def some_distance_func(x: int, y: int) -&gt; int: return x-y &gt;= -1 and x-y &lt;= 1
    【解决方案3】:

    您可以使用pd.cut 函数指定包含纬度的适当间隔,然后简单地合并两个数据框以获得结果:

    bins = [(i-1,i+1) for i in df1['Lat']]
    bins = [item for subbins in bins for item in subbins]
    
    df1['Interval'] = pd.cut(df1['Lat'], bins=bins)
    df2['Interval'] = pd.cut(df2['Station_Lat'], bins=bins)
    
    pd.merge(df1,df2)
    

    此解决方案比您的解决方案稍快。 10.2 ms ± 201 µs per loop12.2 ms ± 1.34 ms per loop

    【讨论】:

    • 我一直在使用这个答案,但是对于任何偶然发现这个问题的人,您可能会收到错误ValueError: bins must increase monotonically. - 看起来这是pandas 中的一个错误。 应该在 0.25 中修复,但没有。
    • 修复上述错误的方法是将bins第三次分配给排序集。 bins=sorted(set(bins))。在pd.cut 中,垃圾箱必须是唯一的并且增加。所以排序并把它变成set 解决了这个问题!
    猜你喜欢
    • 2022-07-07
    • 1970-01-01
    • 1970-01-01
    • 2016-01-06
    • 2016-12-09
    • 2018-02-23
    • 2021-03-16
    • 2020-05-11
    • 2020-03-19
    相关资源
    最近更新 更多