我有两个具有基于位置值的HUGE Pandas数据帧,我需要用df2中距df1每个点小于1000m的记录数更新df1 ['count']。
这是我导入到Pandas中的数据的示例
df1 = lat long valA count
0 123.456 986.54 1 0
1 223.456 886.54 2 0
2 323.456 786.54 3 0
3 423.456 686.54 2 0
4 523.456 586.54 1 0
df2 = lat long valB
0 123.456 986.54 1
1 223.456 886.54 2
2 323.456 786.54 3
3 423.456 686.54 2
4 523.456 586.54 1
实际上,df1具有约1000万行,df2具有约100万行
我使用Pandas DF.itertuples()方法创建了一个工作嵌套的FOR循环,该方法对于较小的测试数据集(df1 = 1k行和df2 = 100行需要大约一个小时才能完成)工作正常,但是完整的数据设置成倍增大,根据我的计算将需要数年才能完成。这是我的工作代码...
import pandas as pd
import geopy.distance as gpd
file1 = 'C:\\path\\file1.csv'
file2 = 'C:\\path\\file2.csv'
df1 = pd.read_csv(file1)
df2 = pd.read_csv(file2)
df1.sort_values(['long', 'lat']), inplace=True)
df2.sort_values(['long', 'lat']), inplace=True)
for irow in df1.itertuples():
count = 0
indexLst = []
Location1 = (irow[1], irow[2])
for jrow in df2.itertuples():
Location2 = (jrow[1], jrow[2])
if gpd.distance(Location1, Location2).kilometers < 1:
count += 1
indexLst.append(jrow[0])
if count > 0: #only update DF if a match is found
df1.at[irow[0],'count'] = (count)
df2.drop(indexLst, inplace=True) #drop rows already counted from df2 to speed up next iteration
#save updated df1 to new csv file
outFileName = 'combined.csv'
df1.to_csv(outFileName, sep=',', index=False)
df2中的每个点仅需计数一次,因为df1中的点是均匀间隔的。为此,我添加了一个drop语句,以便在计算完行之后将其从df2中删除,以期缩短迭代时间。我也尝试过最初创建一个merge / join语句,而不是嵌套循环,但是没有成功。
在此阶段,对提高效率的任何帮助将不胜感激!
编辑: 目标是用df2中小于1公里的点数更新df1中的“计数”列(如下所示),并输出到新文件。
df1 = lat long valA count
0 123.456 986.54 1 3
1 223.456 886.54 2 1
2 323.456 786.54 3 9
3 423.456 686.54 2 2
4 523.456 586.54 1 5
答案 0 :(得分:3)
经常做这种事情,我发现了一些最佳实践:
1)尽量使用numpy和numba
2)尝试尽可能利用并行化
3)跳过矢量化代码的循环(我们在这里使用带有numba的循环来利用并行化)。
在这种情况下,我想指出geopy带来的减速。尽管它是一个很好的程序包,并且可以产生相当准确的距离(与Haversine方法相比),但速度却要慢得多(关于原因为什么没有看过实现)。
import numpy as np
from geopy import distance
origin = (np.random.uniform(-90,90), np.random.uniform(-180,180))
dest = (np.random.uniform(-90,90), np.random.uniform(-180,180))
%timeit distance.distance(origin, dest)
每个循环216 µs±363 ns(平均±标准偏差,共运行7次,每个循环1000次)
这意味着在那个时间间隔,计算1000万x 1百万的距离大约需要21.6亿秒或60万小时。甚至并行性也只会有很大帮助。
由于您对这些点很近很感兴趣,因此建议您使用Haversine distance(距离较远时精度较低)。
from numba import jit, prange, vectorize
@vectorize
def haversine(s_lat,s_lng,e_lat,e_lng):
# approximate radius of earth in km
R = 6373.0
s_lat = s_lat*np.pi/180.0
s_lng = np.deg2rad(s_lng)
e_lat = np.deg2rad(e_lat)
e_lng = np.deg2rad(e_lng)
d = np.sin((e_lat - s_lat)/2)**2 + np.cos(s_lat)*np.cos(e_lat) * np.sin((e_lng - s_lng)/2)**2
return 2 * R * np.arcsin(np.sqrt(d))
%timeit haversine(origin[0], origin[0], dest[1], dest[1])
每个循环1.85 µs±53.9 ns(平均±标准偏差,共运行7次,每个循环100000次)
这已经是100倍的改进。但是我们可以做得更好。您可能已经注意到我从numba添加的@vectorize
装饰器。这允许先前的标量Haversine函数被向量化并采用向量作为输入。我们将在下一步中利用它:
@jit(nopython=True, parallel=True)
def get_nearby_count(coords, coords2, max_dist):
'''
Input: `coords`: List of coordinates, lat-lngs in an n x 2 array
`coords2`: Second list of coordinates, lat-lngs in an k x 2 array
`max_dist`: Max distance to be considered nearby
Output: Array of length n with a count of coords nearby coords2
'''
# initialize
n = coords.shape[0]
k = coords2.shape[0]
output = np.zeros(n)
# prange is a parallel loop when operations are independent
for i in prange(n):
# comparing a point in coords to the arrays in coords2
x, y = coords[i]
# returns an array of length k
dist = haversine(x, y, coords2[:,0], coords2[:,1])
# sum the boolean of distances less than the max allowable
output[i] = np.sum(dist < max_dist)
return output
希望您现在将拥有一个数组,该数组等于第一组坐标的长度(在您的情况下为1000万个)。然后,您可以将其分配给数据框作为计数!
测试时间100,000 x 10,000:
n = 100_000
k = 10_000
coords1 = np.zeros((n, 2))
coords2 = np.zeros((k, 2))
coords1[:,0] = np.random.uniform(-90, 90, n)
coords1[:,1] = np.random.uniform(-180, 180, n)
coords2[:,0] = np.random.uniform(-90, 90, k)
coords2[:,1] = np.random.uniform(-180, 180, k)
%timeit get_nearby_count(coords1, coords2, 1.0)
每个循环2.45 s±73.2 ms(平均±标准偏差,共运行7次,每个循环1次)
不幸的是,这仍然意味着您将需要大约20,000+秒的时间。而这是在具有80个内核的计算机上(基于top
的使用情况,使用了76ish)。
这是我目前能做的最好的事情,祝你好运(也,第一篇文章,感谢激发我的贡献!)
PS:您可能还会研究Dask数组和函数map_block(),以并行化此函数(而不是依赖于prange)。您如何划分数据可能会影响总执行时间。
PPS:1,000,000 x 100,000(比您的全套小100倍):3分27秒(207秒),因此缩放比例似乎是线性的,有点宽容。
PPPS:通过简单的纬度差异过滤器实现:
@jit(nopython=True, parallel=True)
def get_nearby_count_vlat(coords, coords2, max_dist):
'''
Input: `coords`: List of coordinates, lat-lngs in an n x 2 array
`coords2`: List of port coordinates, lat-lngs in an k x 2 array
`max_dist`: Max distance to be considered nearby
Output: Array of length n with a count of coords nearby coords2
'''
# initialize
n = coords.shape[0]
k = coords2.shape[0]
coords2_abs = np.abs(coords2)
output = np.zeros(n)
# prange is a parallel loop when operations are independent
for i in prange(n):
# comparing a point in coords to the arrays in coords2
point = coords[i]
# subsetting coords2 to reduce haversine calc time. Value .02 is from playing with Gmaps and will need to change for max_dist > 1.0
coords2_filtered = coords2[np.abs(point[0] - coords2[:,0]) < .02]
# in case of no matches
if coords2_filtered.shape[0] == 0: continue
# returns an array of length k
dist = haversine(point[0], point[1], coords2_filtered[:,0], coords2_filtered[:,1])
# sum the boolean of distances less than the max allowable
output[i] = np.sum(dist < max_dist)
return output
答案 1 :(得分:1)
我最近做了类似的事情,但是没有用lat,lon做,我只需要找到最近的点及其距离。为此,我使用了 scipy.spatial.cKDTree 程序包。很快。 cKDTree
我认为您可以使用 query_ball_point()函数。
from scipy import spatial
import pandas as pd
file1 = 'C:\\path\\file1.csv'
file2 = 'C:\\path\\file2.csv'
df1 = pd.read_csv(file1)
df2 = pd.read_csv(file2)
# Build the index
tree = spatial.cKDTree(df1[['long', 'lat']])
# Then query the index
您应该尝试一下。