【问题标题】:Pandas apply function to groups, and filter the original dataframePandas 将函数应用于组,并过滤​​原始数据框
【发布时间】:2017-04-08 01:09:37
【问题描述】:

我有一个包含对象及其坐标的 DataFrame:

      id        lat         lng
0   3816  18.384001  -66.114799
1   5922  20.766100 -156.434998
2   1527  21.291394 -157.843085
3   1419  21.291394 -157.843085
4   1651  21.291394 -157.843085

多个对象可以有相同的坐标。数据框很大(数百万条记录)。我有一个坐标为(target_lat, target_lng) 的目标点。我的目标是尽可能高效地在数据框中找到距离目标点 X 英里以内的对象。

我正在使用改编自 this questionhaversine_np 函数。它接受参数(lat_series, lng_series, lat, lng) 并有效地计算lat_series, lng_series(两个系列)和(lat, lng)(两个数字)之间的所有距离。

现在我的问题是如何使用它来过滤距离并选择原始数据框中的对象。

这是我目前的解决方案:

grouper = df.groupby(['lat', 'lng'], sort=False).grouper
lat_series = grouper.result_index.get_level_values(0)  # lats of unique (lat, lng) pairs
lng_series = grouper.result_index.get_level_values(1)  # lngs of unique (lat, lng) pairs
df['location_index'] = grouper.group_info[0]  # assign index of group back to df
distances = haversine_np(lat_series, lng_series, target_lat, target_lng)
mask = distances <= 50  # let's say 50 miles; boolean mask of size = ngroups
loc_indexes = pd.Series(range(grouper.ngroups))[mask]  # select group indexes by mask
df[df.location_index.isin(loc_indexes)]  # select original records by group indexes

它似乎有效,虽然看起来不可靠,因为当我使用pd.Series(range(grouper.ngroups))[mask] 选择相关组索引时,我假设分组的级别值自然索引(从 0 到 ngroups-1)。换句话说,我依赖于grouper.result_index.get_level_values() 中的i-th 元素对应于grouper.group_info[0] 中带有标签i 的组这一事实。我找不到更明确的方法来获取该映射。

问题:

  1. 我使用的方法可靠吗?
  2. 有没有更好(更安全/更简洁/更高效)的方法?

【问题讨论】:

    标签: python pandas dataframe group-by split-apply-combine


    【解决方案1】:

    更新: @DennisGolomazov has found out that this "prefiltering" is not going to work properly for longitudes and make a very good example - 这是一个小演示:

    In [115]: df
    Out[115]:
         id   lat    lng
    5  4444  40.0 -121.0
    0  1111  40.0 -120.0
    
    In [116]: %paste
    threshold = 60
    max_lng_factor = 69.17
    max_lat_factor = 69.41
    target_lat, target_lng = 40, -120
    mask = df.lat.sub(target_lat).abs().le(threshold/max_lat_factor) \
           & \
           df.lng.sub(target_lng).abs().le(threshold/max_lng_factor)
    x = df.loc[mask, ['lat','lng']].drop_duplicates()
    ## -- End pasted text --
    
    In [117]: x
    Out[117]:
        lat    lng
    0  40.0 -120.0
    

    这两个坐标之间的距离小于我们的阈值(60 英里):

    In [119]: haversine_np(-120, 40, -121, 40)
    Out[119]: 52.895043596886239
    

    结论:我们可以预过滤纬度,但不能预过滤经度:

    In [131]: df
    Out[131]:
         id   lat    lng
    5  4444  40.0 -121.0
    0  1111  40.0 -120.0
    1  2222  42.0 -121.0
    

    正确的预过滤:

    In [132]: mask = df.lat.sub(target_lat).abs().le(threshold/max_lat_factor)
         ...: x = df.loc[mask, ['lat','lng']].drop_duplicates()
         ...:
    
    In [133]: x
    Out[133]:
        lat    lng
    5  40.0 -121.0
    0  40.0 -120.0
    

    检查:

    In [135]: df.reset_index() \
         ...:   .merge(x.assign(distance=haversine_np(x.lng, x.lat, target_lng, target_lat))
         ...:           .query("distance <= @threshold"),
         ...:          on=['lat','lng'])
         ...:
    Out[135]:
       index    id   lat    lng   distance
    0      5  4444  40.0 -121.0  52.895044
    1      0  1111  40.0 -120.0   0.000000
    

    旧的、部分错误的答案:

    我会尝试进行预过滤以优化计算。 例如,您可以轻松过滤掉绝对超出“感兴趣的矩形”的点。

    演示:

    threshold = 100
    
    # http://gis.stackexchange.com/questions/142326/calculating-longitude-length-in-miles/142327#142327
    max_lng_factor = 69.17
    max_lat_factor = 69.41
    
    target_lat, target_lng = 21.29, -157.84
    
    mask = df.lat.sub(target_lat).abs().le(threshold/max_lat_factor) \
           & \
           df.lng.sub(target_lng).abs().le(threshold/max_lng_factor)
    
    x = df.loc[mask, ['lat','lng']].drop_duplicates()
    
    df.reset_index() \
      .merge(x.assign(distance=haversine_np(x.lng, x.lat, target_lng, target_lat))
              .query("distance <= @threshold"),
             on=['lat','lng']) \
      .drop('distance',1) \
      .set_index('index')
    

    结果:

    In [142]: df.reset_index() \
         ...:   .merge(x.assign(distance=haversine_np(x.lng, x.lat, target_lng, target_lat))
         ...:           .query("distance <= @threshold"),
         ...:          on=['lat','lng']) \
         ...:   .drop('distance',1) \
         ...:   .set_index('index')
         ...:
    Out[142]:
             id        lat         lng
    index
    1      5922  20.766100 -156.434998
    2      1527  21.291394 -157.843085
    3      1419  21.291394 -157.843085
    4      1651  21.291394 -157.843085
    

    【讨论】:

    • 这个问题的一个问题是,1 度经度仅在赤道处等于 69.17 英里,而靠近两极则更少,这将错误地排除距离足够近但距离足够远的点经度。
    • 如果我错了,请纠正我,但我认为这是不正确的。最大因子是分母(threshold/max_lng_factor),所以因子最大的矩形实际上是最小的。但更接近两极,矩形应该更大,因此该因子应该更小(在两极处降至零)。希望我的解释有意义。
    • @DennisGolomazov,我认为你是对的。所以我们只能预过滤latitude,而不是longitude
    • 这是一个例子。让target_lat, target_lng = (40, -120)。考虑point = (40, -121)threshold = 60。那么这个点不会被你的函数选中,因为threshold/max_lng_factor = 0.867df.lng.sub(target_lng).abs() = 121-120 = 1。但实际上这些点之间的距离是85km = 52.8 miles &lt; 60,所以应该选择点。
    • @DennisGolomazov,是的,很好的例子-谢谢!你是绝对正确的!我要纠正答案...
    【解决方案2】:

    也许我在效率方面遗漏了一些东西,但我不明白你为什么使用 .grouper 方法。 要获取 Lat 和 Long 系列,只需引用它们,即 df['lat'] 或 df.lat,然后您可以直接计算距离

    distances = haversine_np(df.lat, df.lng, target_lat, target_lng)
    

    并使用

    创建一个蒙版
    mask = distances <= 50
    

    掩码现在被索引到数据帧。

    df[mask]
    

    将只提供 True 元素。

    【讨论】:

    • 这里的问题是lat/lng列中有很多重复值,我只想计算haversine_np的唯一值。否则,是的,您的方法将是最简单的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-07
    相关资源
    最近更新 更多