用于计算许多距离的矢量化

时间:2017-08-21 21:19:26

标签: python pandas numpy vectorization

我是numpy / pandas和矢量化计算的新手。我正在做一个数据任务,我有两个数据集。数据集1包含具有经度和纬度的位置列表以及变量A.数据集2还包含具有其经度和纬度的位置列表。对于数据集1中的每个位置,我想计算它到数据集2中所有位置的距离,但我只想得到数据集2中小于变量A值的位数。另请注意数据集非常大,因此我需要使用矢量化操作来加速计算。

例如,我的dataset1可能如下所示:

id lon    lat   varA
1  20.11 19.88  100
2  20.87 18.65  90
3  18.99 20.75  120

我的数据集2可能如下所示:

placeid lon lat 
a       18.75 20.77
b       19.77 22.56
c       20.86 23.76
d       17.55 20.74 

然后对于dataset1中的id == 1,我想计算它到数据集2中所有四个点(a,c,c,d)的距离,我想计算一下距离的数量是多少比varA的对应值。例如,计算的四个距离是90,70,120,110,varA是100.那么值应该是2.

我已经有了一个矢量化函数来计算两对坐标之间的距离。假设函数(hasrsine(x,y))已正确实现,我有以下代码。

dataset2['count'] = dataset1.apply(lambda x: 
haversine(x['lon'],x['lat'],dataset2['lon'], dataset2['lat']).shape[0], axis 
= 1)

但是,这会给出总行数,但不会满足我的要求。

有人能指出我如何使代码有效吗?

3 个答案:

答案 0 :(得分:2)

如果你可以将坐标投影到局部投影(例如UTM),这对pyproj非常直接,并且通常比lon / lat更有利于测量,那么有很多很多使用scipy.spatial的更快方式。 df['something'] = df.apply(...)np.vectorize()都没有真正的矢量化,在引擎盖下,他们使用循环。

ds1
    id  lon lat varA
0   1   20.11   19.88   100
1   2   20.87   18.65   90
2   3   18.99   20.75   120

ds2
    placeid lon lat
0   a   18.75   20.77
1   b   19.77   22.56
2   c   20.86   23.76
3   d   17.55   20.74


from scipy.spatial import distance

# gey coordinates of each set of points as numpy array
coords_a = ds1.values[:,(1,2)]
coords_b = ds2.values[:, (1,2)]
coords_a
#out: array([[ 20.11,  19.88],
#       [ 20.87,  18.65],
#       [ 18.99,  20.75]])

distances = distance.cdist(coords_a, coords_b)
#out: array([[ 1.62533074,  2.70148108,  3.95182236,  2.70059253],
#       [ 2.99813275,  4.06178532,  5.11000978,  3.92307278],
#       [ 0.24083189,  1.97091349,  3.54358575,  1.44003472]])

distances实际上是每对点之间的距离。 coords_a.shape(3, 2)coords_b.shape(4, 2),因此结果为(3,4)np.distance的默认指标为eculidean,但也有其他指标。 为了这个例子,让我们假设vara是:

vara = np.array([2,4.5,2])

(而不是100 90 120)。我们需要确定第一行distances中的哪个值小于2,第二行中4.5更小,...,解决此问题的一种方法是减去每个值来自相应行的vara(请注意,我们必须调整vara的大小):

vara.resize(3,1)
res = res - vara
#out: array([[-0.37466926,  0.70148108,  1.95182236,  0.70059253],
#       [-1.50186725, -0.43821468,  0.61000978, -0.57692722],
#       [-1.75916811, -0.02908651,  1.54358575, -0.55996528]])

然后将正值设置为零并将负值设为正值将为我们提供最终数组:

res[res>0] = 0
res = np.absolute(res)
#out: array([[ 0.37466926,  0.        ,  0.        ,  0.        ],
#            [ 1.50186725,  0.43821468,  0.        ,  0.57692722],
#            [ 1.75916811,  0.02908651,  0.        ,  0.55996528]])

现在,总结每一行:

sum_ = res.sum(axis=1)
#out:  array([ 0.37466926,  2.51700915,  2.34821989])

并计算每行中的项目:

count = np.count_nonzero(res, axis=1)
#out: array([1, 3, 3])

这是一个完全矢量化(自定义)的解决方案,您可以根据自己的喜好进行调整,并且应该适应任何级别的复杂性。另一个解决方案是cKDTree。代码来自文档。将它用于你的问题应该相当容易,但如果你需要帮助,请不要犹豫。

x, y = np.mgrid[0:4, 0:4]
points = zip(x.ravel(), y.ravel())
tree = spatial.cKDTree(points)
tree.query_ball_point([2, 0], 1)
[4, 8, 9, 12]

query_ball_point()找到点x的距离r内的所有点,并且速度非常快。

最后一点注意事项:不要将这些算法与lon / lat输入一起使用,特别是如果您感兴趣的区域远离赤道,因为错误会变得很大。

<强>更新

要投射坐标,您需要将WGS84 (lon/lat)转换为适当的UTM。要找出应该投射哪个区域epsg.io

lon = -122.67598
lat = 45.52168
WGS84 = "+init=EPSG:4326"
EPSG3740 = "+init=EPSG:3740"
Proj_to_EPSG3740 = pyproj.Proj(EPSG3740)

Proj_to_EPSG3740(lon,lat)
# out: (525304.9265963673, 5040956.147893889)

您可以执行df.apply()并使用Proj_to_...来投射df。

答案 1 :(得分:1)

IIUC:

来源DF:

In [160]: d1
Out[160]:
   id    lon    lat  varA
0   1  20.11  19.88   100
1   2  20.87  18.65    90
2   3  18.99  20.75   120

In [161]: d2
Out[161]:
  placeid    lon    lat
0       a  18.75  20.77
1       b  19.77  22.56
2       c  20.86  23.76
3       d  17.55  20.74

矢量化haversine函数:

def haversine(lat1, lon1, lat2, lon2, to_radians=True, earth_radius=6371):
    if to_radians:
        lat1, lon1, lat2, lon2 = pd.np.radians([lat1, lon1, lat2, lon2])

    a = pd.np.sin((lat2-lat1)/2.0)**2 + \
        pd.np.cos(lat1) * pd.np.cos(lat2) * pd.np.sin((lon2-lon1)/2.0)**2

    return earth_radius * 2 * pd.np.arcsin(np.sqrt(a))

解决方案:

x = d2.assign(x=1) \
      .merge(d1.loc[d1['id']==1, ['lat','lon']].assign(x=1),
             on='x', suffixes=['','2']) \
      .drop(['x'], 1)

x['dist']  = haversine(x.lat, x.lon, x.lat2, x.lon2)

的产率:

In [163]: x
Out[163]:
  placeid    lon    lat   lat2   lon2        dist
0       a  18.75  20.77  19.88  20.11  172.924852
1       b  19.77  22.56  19.88  20.11  300.078600
2       c  20.86  23.76  19.88  20.11  438.324033
3       d  17.55  20.74  19.88  20.11  283.565975

过滤

In [164]: x.loc[x.dist < d1.loc[d1['id']==1, 'varA'].iat[0]]
Out[164]:
Empty DataFrame
Columns: [placeid, lon, lat, lat2, lon2, dist]
Index: []

让我们改变d1,这样几行就能满足标准:

In [171]: d1.loc[0, 'varA'] = 350

In [172]: d1
Out[172]:
   id    lon    lat  varA
0   1  20.11  19.88   350   # changed: 100 --> 350 
1   2  20.87  18.65    90
2   3  18.99  20.75   120

In [173]: x.loc[x.dist < d1.loc[d1['id']==1, 'varA'].iat[0]]
Out[173]:
  placeid    lon    lat   lat2   lon2        dist
0       a  18.75  20.77  19.88  20.11  172.924852
1       b  19.77  22.56  19.88  20.11  300.078600
3       d  17.55  20.74  19.88  20.11  283.565975

答案 2 :(得分:1)

scipy.spatial.distance.cdist与用户定义的距离算法一起用作metric

h = lambda u, v: haversine(u['lon'], u['lat'], v['lon'], v['lat'])
dist_mtx = scipy.spatial.distance.cdist(dataset1, dataset2, metric = h)

然后检查区域中的号码,只播放

dataset2['count'] = np.sum(dataset1['A'][:, None] > dist_mtx, axis = 0)