如何在极坐标(h3多边形填充)中并行化PandasUDF以输出字符串类型的UDF?

2exbekwf  于 2023-01-28  发布在  其他
关注(0)|答案(2)|浏览(167)

我想在Polars中执行以下python代码行作为UDF:

w = wkt.loads('POLYGON((-160.043334960938 70.6363054807905, -160.037841796875 70.6363054807905, -160.037841796875 70.6344840663086, -160.043334960938 70.6344840663086, -160.043334960938 70.6363054807905))')
polygon (optionally including holes).
j = shapely.geometry.mapping(w)
h3.polyfill(j, res=10, geo_json_conformant=True)

Pandas/地Pandas:

import pandas as pd
import geopandas as gpd
import polars as pl
from shapely import wkt

pandas_df = pd.DataFrame({'quadkey': {0: '0022133222330023',
  1: '0022133222330031',
  2: '0022133222330100'},
 'tile': {0: 'POLYGON((-160.043334960938 70.6363054807905, -160.037841796875 70.6363054807905, -160.037841796875 70.6344840663086, -160.043334960938 70.6344840663086, -160.043334960938 70.6363054807905))',
  1: 'POLYGON((-160.032348632812 70.6381267305321, -160.02685546875 70.6381267305321, -160.02685546875 70.6363054807905, -160.032348632812 70.6363054807905, -160.032348632812 70.6381267305321))',
  2: 'POLYGON((-160.02685546875 70.6417687358462, -160.021362304688 70.6417687358462, -160.021362304688 70.6399478155463, -160.02685546875 70.6399478155463, -160.02685546875 70.6417687358462))'},
 'avg_d_kbps': {0: 15600, 1: 6790, 2: 9619},
 'avg_u_kbps': {0: 14609, 1: 22363, 2: 15757},
 'avg_lat_ms': {0: 168, 1: 68, 2: 92},
 'tests': {0: 2, 1: 1, 2: 6},
 'devices': {0: 1, 1: 1, 2: 1}}

)
# display(pandas_df)

gdf = pandas_df.copy()
gdf['geometry'] = gpd.GeoSeries.from_wkt(pandas_df['tile'])

import h3pandas
display(gdf.h3.polyfill_resample(10))

这工作起来超级快和容易。然而,从Pandas应用程序调用的polyfill函数作为UDF对于我的数据集的大小来说太慢了。
相反,我很想使用极线,但我遇到了几个问题:

geo类型无法理解

为了更好的表现而努力转向极地

pl.from_pandas(gdf)

失败,并显示:箭头类型错误:未传递numpy. dtype对象
这看起来像geoarrow/geoparquet是不支持的极性

numpy矢量化极坐标接口因缺少几何类型而失败

polars_df = pl.from_pandas(pandas_df)
out = polars_df.select(
    [
        gpd.GeoSeries.from_wkt(pl.col('tile')),
    ]
)

失败:

TypeError: 'data' should be array of geometry objects. Use from_shapely, from_wkb, from_wkt functions to construct a GeometryArray.

全部手工制作

polars_df.with_column(pl.col('tile').map(lambda x: h3.polyfill(shapely.geometry.mapping(wkt.loads(x)), res=10, geo_json_conformant=True)).alias('geometry'))

失败:

Conversion of polars data type Utf8 to C-type not implemented.

最后一个选项看起来是最有希望的(没有特殊的地理空间类型的错误)。但是这个字符串/Utf8类型的C没有被实现的一般错误消息对我来说听起来很奇怪。
此外:

polars_df.select(pl.col('tile').apply(lambda x: h3.polyfill(shapely.geometry.mapping(wkt.loads(x)), res=10, geo_json_conformant=True)))

工作-但缺少其他列-即手动选择这些列的语法不方便。但在附加以下内容时也会失败:

.explode('tile').collect()
# InvalidOperationError: cannot explode dtype: Object("object")
umuewwlo

umuewwlo1#

要解决一些极坐标错误:
wkt函数不能处理pl.Series-你可以使用.to_numpy()来提供一个numpy数组:

gpd.GeoSeries.from_wkt(polars_df.get_column("tile").to_numpy())
0    POLYGON ((-160.04333 70.63631, -160.03784 70.6...
1    POLYGON ((-160.03235 70.63813, -160.02686 70.6...
2    POLYGON ((-160.02686 70.64177, -160.02136 70.6...
dtype: geometry

您可以使用.with_columns()代替.select()
一个二个一个一个
h3.polyfill()正在返回一个python set对象,polars实际上无法"识别"该对象。
您可以将集合转换为list(),极坐标将为您提供list[str]列,而不是object-您可以.explode()而不会出错。

polars_df.with_columns(
   pl.col('tile').apply(lambda x: list(h3.polyfill(shapely.geometry.mapping(wkt.loads(x)), res=10, geo_json_conformant=True)))
     .alias('h3_polyfill')
).explode('h3_polyfill')
shape: (9, 8)
┌──────────────────┬─────────────────────────────────────┬────────────┬────────────┬────────────┬───────┬─────────┬─────────────────┐
│ quadkey          | tile                                | avg_d_kbps | avg_u_kbps | avg_lat_ms | tests | devices | h3_polyfill     │
│ ---              | ---                                 | ---        | ---        | ---        | ---   | ---     | ---             │
│ str              | str                                 | i64        | i64        | i64        | i64   | i64     | str             │
╞══════════════════╪═════════════════════════════════════╪════════════╪════════════╪════════════╪═══════╪═════════╪═════════════════╡
│ 0022133222330023 | POLYGON((-160.043334960938 70.63... | 15600      | 14609      | 168        | 2     | 1       | 8a0d1c1306a7fff │
│ 0022133222330023 | POLYGON((-160.043334960938 70.63... | 15600      | 14609      | 168        | 2     | 1       | 8a0d1c1306b7fff │
│ 0022133222330023 | POLYGON((-160.043334960938 70.63... | 15600      | 14609      | 168        | 2     | 1       | 8a0d1c13079ffff │
│ 0022133222330031 | POLYGON((-160.032348632812 70.63... | 6790       | 22363      | 68         | 1     | 1       | 8a0d1c130757fff │
│ 0022133222330031 | POLYGON((-160.032348632812 70.63... | 6790       | 22363      | 68         | 1     | 1       | 8a0d1c130627fff │
│ 0022133222330031 | POLYGON((-160.032348632812 70.63... | 6790       | 22363      | 68         | 1     | 1       | 8a0d1c13070ffff │
│ 0022133222330100 | POLYGON((-160.02685546875 70.641... | 9619       | 15757      | 92         | 6     | 1       | 8a0d1c1300d7fff │
│ 0022133222330100 | POLYGON((-160.02685546875 70.641... | 9619       | 15757      | 92         | 6     | 1       | 8a0d1c1300c7fff │
│ 0022133222330100 | POLYGON((-160.02685546875 70.641... | 9619       | 15757      | 92         | 6     | 1       | 8a0d1c1300f7fff │
└──────────────────┴─────────────────────────────────────┴────────────┴────────────┴────────────┴───────┴─────────┴─────────────────┘

与在Pandas身上进行"全手工"的方法相比,可能没有太大区别:

pandas_df['geometry'] = wkt.loads(pandas_df['tile'])
pandas_df = pandas_df.assign(
   h3_polyfill=pandas_df['geometry'].map(lambda tile: h3.polyfill(shapely.geometry.mapping(tile), 10, True))
).explode('h3_polyfill')
okxuctiv

okxuctiv2#

Polars的优势在于数据存储在类型为inflexible arrow2的数组中,并且计算得到了优化(类似于一个数据库查询优化器)并在rust中运行,任何时候你在polars中使用apply,你就会失去polars的好处,因为它会把函数发送回python,而不是在rust中优化和计算。Polars doesn't support anything that looks like a shapely geometry也没有内置地理空间几何操作符。
还有geopolars,它还处于绝对的婴儿期,可能还不是特别有用,长话短说,polars(在python中)还不能帮助geo计算。
回到您最初的问题,如果您不使用gdf.h3.polyfill_resample(10),而是使用类似这样的代码(强调 * 类似 * 的代码,因为我对h3不是很熟悉)

from shapely.geometry import mapping
h3.polyfill(mapping(gdf.geometry))

您可能需要研究mapping(gdf.geometry)返回的dict,以获得符合h3使用的geojson输入的内容。
这个想法是,将整个输入直接馈送到h3将(至少有可能,我不知道它的内部结构)允许它在C中执行所有的循环和迭代,而不是在python中执行循环,这是你在apply中得到的。

相关问题