Pandas 将函数应用于组,并过滤原始数据帧
Pandas apply function to groups, and filter the original dataframe
我有一个包含对象及其坐标的 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 英里范围内找到数据框中的对象。
我正在使用 haversine_np
改编自 this question 的函数。它采用参数 (lat_series, lng_series, lat, lng)
并有效地计算 lat_series, lng_series
(两个系列)和 (lat, lng)
(两个数字)之间的所有距离。
现在我的问题是如何使用它来过滤原始数据框中的距离和 select 对象。
这是我目前的解决方案:
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
它似乎工作,虽然看起来不可靠,因为当我 select 使用 pd.Series(range(grouper.ngroups))[mask]
相关组索引时,我假设分组的级别值是自然索引的(从 0 到 ngroups-1)。换句话说,我依赖于 grouper.result_index.get_level_values()
中的 i-th
元素对应于 grouper.group_info[0]
中带有标签 i
的组这一事实。我找不到更明确的方法来获取该映射。
问题:
- 我用的方法靠谱吗?
- 有没有更好的(更安全/更简洁/更高效)的方法?
更新: - 这是一个小演示:
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
旧的、部分不正确的答案:
我会尝试进行预过滤以优化计算。
例如,您可以轻松过滤掉绝对在 "rectangle of interest".
之外的点
演示:
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
也许我在效率方面遗漏了一些东西,但我不明白你为什么要使用 .grouper 方法。
要获得 Lat 和 Long 系列,只需引用它们,即 df['lat'] 或 df.lat,然后您可以直接使用
计算距离
distances = haversine_np(df.lat, df.lng, target_lat, target_lng)
并使用
创建一个遮罩
mask = distances <= 50
掩码现在已索引到数据帧。
df[mask]
将仅提供 True 元素。
我有一个包含对象及其坐标的 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 英里范围内找到数据框中的对象。
我正在使用 haversine_np
改编自 this question 的函数。它采用参数 (lat_series, lng_series, lat, lng)
并有效地计算 lat_series, lng_series
(两个系列)和 (lat, lng)
(两个数字)之间的所有距离。
现在我的问题是如何使用它来过滤原始数据框中的距离和 select 对象。
这是我目前的解决方案:
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
它似乎工作,虽然看起来不可靠,因为当我 select 使用 pd.Series(range(grouper.ngroups))[mask]
相关组索引时,我假设分组的级别值是自然索引的(从 0 到 ngroups-1)。换句话说,我依赖于 grouper.result_index.get_level_values()
中的 i-th
元素对应于 grouper.group_info[0]
中带有标签 i
的组这一事实。我找不到更明确的方法来获取该映射。
问题:
- 我用的方法靠谱吗?
- 有没有更好的(更安全/更简洁/更高效)的方法?
更新:
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
旧的、部分不正确的答案:
我会尝试进行预过滤以优化计算。 例如,您可以轻松过滤掉绝对在 "rectangle of interest".
之外的点演示:
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
也许我在效率方面遗漏了一些东西,但我不明白你为什么要使用 .grouper 方法。 要获得 Lat 和 Long 系列,只需引用它们,即 df['lat'] 或 df.lat,然后您可以直接使用
计算距离distances = haversine_np(df.lat, df.lng, target_lat, target_lng)
并使用
创建一个遮罩mask = distances <= 50
掩码现在已索引到数据帧。
df[mask]
将仅提供 True 元素。