【问题标题】:Using pandas to identify nearest objects使用 pandas 识别最近的物体
【发布时间】:2020-03-30 01:04:26
【问题描述】:

我有一个可以使用任何编程语言完成的作业。我选择了 Python 和 pandas,因为我没有使用这些的经验,并认为这将是一次很好的学习体验。我能够使用我从传统计算机编程中知道的传统循环来完成任务,并且它在数千行上运行良好,但是一旦我让它处理数百万行,它就会让我的笔记本电脑突然停止。任务概述如下。

您在二维平面上有一条两条车道的道路。一条车道供汽车使用,另一条车道供卡车使用。数据如下所示(每个表跨越数百万行):

汽车

   id  start  end
0  C1    200  215
1  C2    110  125
2  C3    240  255
...

卡车

   id  start  end
0  T1    115  175
1  T2    200  260
2  T3    280  340
3  T4     25   85
...

上面的两个dataframe对应这个:

startend 列代表道路上的任意位置,其中 start = 车辆的后边缘,end = 车辆的前边缘。

任务是识别离每辆车最近的卡车。一辆卡车与一辆汽车最多可以有三种不同的关系:

  1. Back - 它在汽车后部 (cars.end > trucks.end)
  2. 对面 - 在汽车对面 (cars.start >= trucks.start and cars.end <= trucks.end)
  3. 前面 - 它在汽车前面 (cars.start < trucks.start)

我强调“最多”,因为如果后面或前面有另一辆车更靠近最近的卡车,那么这种关系将被忽略。在上图的情况下,我们可以观察到以下几点:

  • C1:背面 = T1,横向 = T2,正面 = 无(C3 阻挡)
  • C2:背面 = T4,横向 = 无,正面 = T1
  • C3:后方 = 无(C1 阻挡),交叉 = T2,前方 = T3

最终输出需要与以下新列一起附加到 cars 数据框:

  • trucks 数据帧交叉引用的数据
  • 对于后面的位置,间隙距离 (cars.start - trucks.end)
  • 对于前面的位置,间隙距离 (trucks.start - cars.end)

最终的cars 数据框应如下所示:

   id  start  end  back_id  back_start  back_end  back_distance  across_id  across_start  across_end  front_id  front_start  front_end  front_distance
0  C1    200  215       T1         115       175             25         T2           200         260
1  C2    110  125       T4          25        85             25                                             T1          115        175             -10
2  C3    240  255                                                       T2           200         260        T3          280        340              25

熊猫是完成这项任务的最佳工具吗?如果有一个更合适的工具可以有效地交叉引用和附加基于数百万行的计算的列,那么我会全力以赴。

【问题讨论】:

  • 如果您已经有一个良好的循环 for 来完成这项工作,请查看可以加快您的代码速度的库 Numba。在 pandas 中,merge_asof 可以帮助您了解三个不同的方向,您可以将其作为参数传递以查找您的 3 个关系

标签: python pandas


【解决方案1】:

所以对于 pandas,您可以使用 merge_asof,这是一种方法,对于数百万行可能效率不高:

#first sort values
trucks = trucks.sort_values(['start'])
cars = cars.sort_values(['start'])

#create back condition
df_back = pd.merge_asof(trucks.rename(columns={col:f'back_{col}' 
                                               for col in trucks.columns}), 
                        cars.assign(back_end=lambda x: x['end']), 
                        on='back_end', direction='forward')\
            .query('end>back_end')\
            .assign(back_distance=lambda x: x['start']-x['back_end'])

#create across condition: here note that cars is the first of the 2 dataframes
df_across = pd.merge_asof(cars.assign(across_start=lambda x: x['start']),
                          trucks.rename(columns={col:f'across_{col}' 
                                                 for col in trucks.columns}), 
                          on=['across_start'], direction='backward')\
              .query('end<=across_end')

#create front condition
df_front = pd.merge_asof(trucks.rename(columns={col:f'front_{col}' 
                                                for col in trucks.columns}), 
                         cars.assign(front_start=lambda x: x['start']), 
                         on='front_start', direction='backward')\
             .query('start<front_start')\
             .assign(front_distance=lambda x: x['front_start']-x['end'])

# merge all back to cars
df_f = cars.merge(df_back, how='left')\
           .merge(df_across, how='left')\
           .merge(df_front, how='left')

你得到

print (df_f)
   id  start  end back_id  back_start  back_end  back_distance  across_start  \
0  C2    110  125      T4        25.0      85.0           25.0           NaN   
1  C1    200  215      T1       115.0     175.0           25.0         200.0   
2  C3    240  255     NaN         NaN       NaN            NaN         240.0   

  across_id  across_end front_id  front_start  front_end  front_distance  
0       NaN         NaN       T1        115.0      175.0           -10.0  
1        T2       260.0      NaN          NaN        NaN             NaN  
2        T2       260.0       T3        280.0      340.0            25.0  

【讨论】:

  • Ben.T,非常感谢。您的解决方案比我的要简洁得多。我的解决方案涉及遍历数组,在codereview.stackexchange.com/a/239946/221419 中使用find_nearest() 为每个项目查找最近的汽车和卡车值,然后从那里创建新列。当您处理数百万行时,我发现循环很糟糕。我还没有计时,但我感觉你的会更快,因为它避免了循环。我将尝试实施来自blog.paperspace.com/… 的想法以改进您的解决方案。
  • 我还在学习pd.merge_asof()——我不明白的一件事是,当我打印最终数据帧时,across_id 列出现在across_start 之后柱子?后列和前列都很好,只是交叉条件没有按预期顺序。
  • @thdoan 所以我会说列顺序的不同是因为在前面和后面 trucks 是左边的 df 和 cars 右边的 merge_asof 而在它的对面是对面
  • @thdoan 关于函数find_nearest,快速浏览一下,可能可以对其进行矢量化处理,也许所谓的number 也可以是一个数组,然后您可以执行ufunc.outer。也就是说,这两个数据帧都有数百万行,这是不可能的,因为结果对于标准计算机内存来说太大了。
  • 我没有矢量化的经验——如果carstrucks 表不一样,是否可以进行矢量化?抱歉,我忘了说在真实数据集中,这两个表有不同的行数和不同的列(尽管它们共享idstartend)。关于不同的列,可以通过创建具有公共列的新数据框来纠正,但我不确定不同的行数(我的印象是,为了矢量化你需要相同数量的行,因为它类似于矩阵运算)。
猜你喜欢
  • 1970-01-01
  • 2015-09-27
  • 2021-10-14
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-16
  • 2022-06-13
相关资源
最近更新 更多