地理空間データ分析もできるpolars、polars-stの紹介
先に結論
- polars-stはpolarsの地理空間データ分析用の拡張機能
- まだ開発されてからの期間が短い関係で、ポリゴンをバイナリとして読み込める点以外はpolarsと大差ない
- 現状はpolars-st単体での分析は難しく、polars-stでSHP等のファイルを読み込み、データ抽出等の管理的な処理の実施後、dask-geopandasかgeopandasに戻して処理するのが良い
背景
pythonでデータ分析をする際、pandasが最も有名なライブラリと思われるが、最近は大規模データ等のメモリに載らないデータの取り扱い時にはPySparkやPolarsが使われることが増えてきた。
同様に地理空間データ分析においても、Plateauのデータ等の大規模なデータを取り扱う際に、メモリに載らない等の問題に遭遇したので、何か良い方法はないか調べていたところ、polarsの拡張機能であるpolars-stなるものを見つけたので、その使い方と地理空間データ分析でよく用いられるgeopandasを比較して紹介する。なお、パッケージの概要はpolars-st含めてこちらで紹介している。
準備
環境構築
- 分析を行うにあたり、データを用意する。今回は国土数値情報の行政区のデータのN03-20250101.shpを利用する。
- このデータは市区町村名とそのコードやその担当部局との関係性を示したもの
- 環境はGoogle Colaboratoryを利用する。その際、ランタイムタブからランタイムのタイプがPython3か念の為確認する。(初期設定はPython3)
!pip install polars-st
自分の環境では依存関係のエラーが出た為、問題となっているパッケージを削除したあと、再インストール
!pip uninstall -y pyarrow polars cudf-cu12 pylibcudf-cu12 cudf-polars-cu12
!pip install polars-st
ライブラリの活用
データの読み込み
データの中身の確認は下記のコマンドで行う
※添付の画像において、N03_003列が空なのは、このデータセットの仕様
polars-st
import polars_st as pst
# 読み込むファイルのパスの指定
shp_file_path = "/content/N03-20250101_prefecture.shp"
# データフレームの最初の5行のみ表示
pst_df = pst.read_file(shp_file_path)
print(pst_df.head())

geopandas
import geopandas as gpd
# 読み込むファイルのパスの指定
shp_file_path = "/content/N03-20250101_prefecture.shp"
# データフレームの最初の5行のみ表示
gpd_df = gpd.read_file(shp_file_path)
print(gpd_df.head())

polars-stとgeopandasの大きな違いはgeometryの部分が前者はバイナリの一方、後者は緯度経度の座標情報になっている。その為、動作確認時には少々扱いづらい。
なお、このようなデータ形式にしている理由は
- データを保存する際、polarsのobject型の定義が特殊故にエラーになる
- バイナリ化する事により、ファイルサイズの軽量化や処理の高速化
の為と考える。
条件に基づいたデータ抽出
用意したデータのうち、N03_001(都道府県名)のプロパティが北海道のもののみ抜き出す処理は下記の通り
polars-st
#N03_001の値が北海道に一致するもののみ抽出
pst_df_filtered = pst_df.filter(pst_df["N03_001"] == "北海道")
print(pst_df_filtered.head())
geopandas
gpd_df_filtered = gpd_df.loc[gpd_df["N03_001"] == "北海道"]
print(gpd_df_filtered.head())
filter内の処理はどちらもN03_001で北海道と一致する場合True、そうでない場合はFalseを返すという処理の為、関数の挙動が多少異なる程度で利用可能。
ポリゴンの結合や交差等の機能
polars-stにおいて、マージ等の空間演算機能は2025/10時点ではドキュメントを見る限り、未実装の為、polars-stのデータをgeopandasやdask-geopandasに変換し、処理に用いるのが良い。
ここではpolars-stのデータをpolarsに変換する処理を2パターン紹介する
一括で処理する場合
処理の流れは下記の通り
- polars-st(polars)からpandasに変換
- pandas上でジオメトリ補正
- pandasからgeopandasに変換
import geopandas as gpd
import shapely.wkb
import pandas as pd
# 1.polarsのto_pandas関数を用いてDataFrameにする
pst_df_pandas = pst_df_filtered.to_pandas()
# 2.バイナリ化されたgeometry部をshapely.wkb.loadsを用いて緯度経度に変換
pst_df_pandas['geometry'] = pst_df_pandas['geometry'].apply(
lambda x: shapely.wkb.loads(x) if x is not None else None
)
# 3.geopandasのGeoDataFrameに変換する
gpd_from_polars_df_simple = gpd.GeoDataFrame(
pst_df_pandas, geometry="geometry"
)
途中経過を保持しつつ処理する場合
処理の流れは下記の通り
- polars-st(polars)で処理したものを一旦parquet(軽量なデータ形式で保存)
- polarsとして読み込み
- polarsでジオメトリ補正
- polarsからpandasに変換
- pandasからgeopandasに変換
※ 「1の前に3のプロセスをしたほうが効率的ではないか」というような意見もあると思われるが、parquetに変換する際、object型のデータを渡すと処理が落ちるので、暫定的にこのような対応をしている
import polars as pl
import shapely.wkb
# 1.parquet形式で書き出し
pst_df_filtered.write_parquet("pst_df.parquet")
# 2.parquetを読み込む
saved_pst_df = pl.read_parquet("pst_df.parquet")
# 3.バイナリ化されているgeometryをshapley.wkbを用いて、Polygon型へ整形
geometry_shapely_polars = saved_pst_df["geometry"].map_elements(
lambda x: shapely.wkb.loads(x) if x is not None else None,
return_dtype=pl.Object # Specify Object dtype for shapely geometries
)
# 整形したジオメトリを元のDataFrameに組み込み、バイナリのジオメトリを落とす
pst_df_filtered_with_shapely_geometry = pst_df_filtered.drop("geometry").with_columns(
geometry_shapely_polars.alias("geometry")
)
# 4.pandasに変換する
pst_df_pandas = pst_df_filtered_with_shapely_geometry.to_pandas()
# 5.geopandasのGeoDataFrameに変換する
gpd_from_polars_df_simple = gpd.GeoDataFrame(
pst_df_pandas, geometry="geometry"
)
まとめ
polarsのGIS処理版であるgeopolarsもpolars-stも発展途上の為、これら単体で地理空間データ分析を行うのは難しい。ただ、polars-stは精力的にアップデートがされている事や処理自体はpolarsベースで軽量な為、ポリゴンの切り出し等の簡易的な処理であれば、polars-stを使い、より高度な処理はgeopandasやdask-geopandasを使うというのも手と感じた。
Discussion