提高当前由 lambda 函数创建的 LineString 创建的性能
Improve performance of LineString creation, that currently is created by a lambda function
我有一个这样的数据框(这个例子只有四行,但实际上它有 O(10^6) 行):
DF:
nodeid lon lat wayid
0 1 1.70 42.10 52
1 2 1.80 42.30 52
2 3 1.75 42.20 53
3 4 1.72 42.05 53
我需要按 wayid
分组并连接组中每个元素的 lon
和 lat
列,以获得如下输出:
output:
wayid
52 LINESTRING (1.7 42.1, 1.8 42.3)
53 LINESTRING (1.75 42.2, 1.72 42.05)
dtype: object
我可以通过以下方式创建示例 DataFrame:
DF = pd.DataFrame([[1, 1.7, 42.1, 52], [2, 1.8, 42.3, 52], [3, 1.75, 42.2, 53], [4, 1.72, 42.05, 53]])
DF.columns = ['nodeid', 'lon', 'lat', 'wayid']
我可以获得所需的输出,应用这样的 lambda 函数:
DF.groupby('wayid').apply(lambda r: LineString(np.array(r[['lon','lat']])))
然而,这是一个相当缓慢的过程,我需要以某种方式改进它(除了出现警告消息之外)。
关于如何通过提高性能获得相同结果的任何想法?
注意: 最后,实际上我需要这样的 GeoDataFrame:
GDF = gp.GeoDataFrame(geometry=DF.groupby('wayid')\
.apply(lambda r: LineString(np.array(r[['lon','lat']])))
以防设计出更好的解决方案。
看起来很合理。具有 10^6 lat/lon 对的模拟数据框。通过删除 numpy 数组的创建有一个小的优化,因为 r
是一个数据框,您可以在其中访问 numpy 数组 .values
import geopandas as gpd
import pandas as pd
import numpy as np
from shapely.geometry import LineString
import warnings
warnings.filterwarnings('ignore')
cities = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))
c = cities.sample(1)
p = c["geometry"].values[0]
SIZE = 1000
N=20
x = np.random.uniform(p.x - 2, p.x + 2, SIZE)
y = np.random.uniform(p.y - 2, p.y + 2, SIZE)
grid = np.random.randint(0, SIZE//N, size=[SIZE, SIZE])
DF = pd.DataFrame(
[
{"wayid": way, "lon": x[g[1]], "lat": y[g[0]]}
for way in range(SIZE//N)
for g in np.argwhere(grid == way)
]
)
%timeit GEOMETRY = DF.groupby('wayid').apply(lambda r: LineString(np.array(r[['lon','lat']])))
%timeit GEOMETRY = DF.groupby('wayid').apply(lambda r: LineString(r.loc[:,["lon","lat"]].values))
GEOMETRY = DF.groupby('wayid').apply(lambda r: LineString(r.loc[:,["lon","lat"]].values))
GDF = gpd.GeoDataFrame(geometry=GEOMETRY, crs=cities.crs)
print(f"""DF: {DF.shape}
GDF: {GDF.shape}""")
输出
137 ms ± 1.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
134 ms ± 642 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
DF: (1000000, 3)
GDF: (50, 1)
我有一个这样的数据框(这个例子只有四行,但实际上它有 O(10^6) 行):
DF:
nodeid lon lat wayid
0 1 1.70 42.10 52
1 2 1.80 42.30 52
2 3 1.75 42.20 53
3 4 1.72 42.05 53
我需要按 wayid
分组并连接组中每个元素的 lon
和 lat
列,以获得如下输出:
output:
wayid
52 LINESTRING (1.7 42.1, 1.8 42.3)
53 LINESTRING (1.75 42.2, 1.72 42.05)
dtype: object
我可以通过以下方式创建示例 DataFrame:
DF = pd.DataFrame([[1, 1.7, 42.1, 52], [2, 1.8, 42.3, 52], [3, 1.75, 42.2, 53], [4, 1.72, 42.05, 53]])
DF.columns = ['nodeid', 'lon', 'lat', 'wayid']
我可以获得所需的输出,应用这样的 lambda 函数:
DF.groupby('wayid').apply(lambda r: LineString(np.array(r[['lon','lat']])))
然而,这是一个相当缓慢的过程,我需要以某种方式改进它(除了出现警告消息之外)。
关于如何通过提高性能获得相同结果的任何想法?
注意: 最后,实际上我需要这样的 GeoDataFrame:
GDF = gp.GeoDataFrame(geometry=DF.groupby('wayid')\
.apply(lambda r: LineString(np.array(r[['lon','lat']])))
以防设计出更好的解决方案。
看起来很合理。具有 10^6 lat/lon 对的模拟数据框。通过删除 numpy 数组的创建有一个小的优化,因为 r
是一个数据框,您可以在其中访问 numpy 数组 .values
import geopandas as gpd
import pandas as pd
import numpy as np
from shapely.geometry import LineString
import warnings
warnings.filterwarnings('ignore')
cities = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))
c = cities.sample(1)
p = c["geometry"].values[0]
SIZE = 1000
N=20
x = np.random.uniform(p.x - 2, p.x + 2, SIZE)
y = np.random.uniform(p.y - 2, p.y + 2, SIZE)
grid = np.random.randint(0, SIZE//N, size=[SIZE, SIZE])
DF = pd.DataFrame(
[
{"wayid": way, "lon": x[g[1]], "lat": y[g[0]]}
for way in range(SIZE//N)
for g in np.argwhere(grid == way)
]
)
%timeit GEOMETRY = DF.groupby('wayid').apply(lambda r: LineString(np.array(r[['lon','lat']])))
%timeit GEOMETRY = DF.groupby('wayid').apply(lambda r: LineString(r.loc[:,["lon","lat"]].values))
GEOMETRY = DF.groupby('wayid').apply(lambda r: LineString(r.loc[:,["lon","lat"]].values))
GDF = gpd.GeoDataFrame(geometry=GEOMETRY, crs=cities.crs)
print(f"""DF: {DF.shape}
GDF: {GDF.shape}""")
输出
137 ms ± 1.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
134 ms ± 642 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
DF: (1000000, 3)
GDF: (50, 1)