対象読者
Pandas, GeoPandas ユーザ
GIS ユーザ
実行環境
Windows 10
Python 3.9.5
geopandas 0.9.0
pandas 1.3.1
はじめに
GeoPandas でジオメトリックな処理をして、shapely.geometry のオブジェクト(Point とか Polygon とか)を geometry カラムに格納する場合を考えます。
xy座標からポイントを作る1とか、既存のオブジェクトのバッファをとる2といった基本的な処理であれば、geopandas の関数や geopandas.GeoSeries のメソッドで楽に実現できます。
ただ実際は、特殊な処理をして複雑なオブジェクトを作りたい場合があります。
ここでは、pandas.DataFrame.apply() を使って、複雑な形状のオブジェクトを作りたいと思います。
pandas.DataFrame.apply()
pandas を使っているとお世話になることの多い apply() ですが、ここでおさらいをします。
公式リファレンス
pandas.DataFrame.apply
データフレームの列や行に対して関数を適用するメソッドですが、ここではgeopandas.GeoDataFrame の geometry カラムを作りたいので、列の処理を考えます。
DataFrame で列に対する処理をする場合は引数 axis=1 を指定します。
今回の趣旨に沿って apply メソッドの使用例を示します。
(使用法を網羅するものではありません)
処理する pandas.DataFrame はこちら。
import pandas as pd
df = pd.DataFrame([[1, 2], [3, 4], [5, 6]], columns=['A', 'B'])
print(df)
'''
A B
0 1 2
1 3 4
2 5 6
'''
python組み込み関数を列に適用
C列に、A列の値を2進数にして格納
df['C'] = df['A'].apply(bin)
自作関数を列に適用
D列に、A列とB列の和を格納
def add_ab(row):
return row['A'] + row['B']
df['D'] = df.apply(add_ab, axis=1)
無名関数を列に適用
E列に、A列とB列の差を格納
df['E'] = df.apply(lambda row: row['A'] - row['B'], axis=1)
なお lambda 式の中で row.name とするとインデックスにでアクセスできます。
複数の引数をとる関数を列に適用
F列に、A列のm(=2)倍とB列のn(=3)倍の和を格納
def add_am_bn(row, m, n):
return row['A'] * m + row['B'] * n
df['F'] = df.apply(add_am_bn, axis=1, m=2, n=3)
apply の引数として、キーワード引数を渡します。
複数の返り値を複数の列に返す
G列に、A列の2乗を、H列にB列の3乗を返す
def square_a_cube_b(row):
return row['A']**2, row['B']**3
df[['G', 'H']] = df.apply(square_a_cube_b, axis=1, result_type='expand')
apply の result_type 引数に'expand'を指定します。
結果
print(df)
'''
A B C D E F G H
0 1 2 0b1 3 -1 8 1 8
1 3 4 0b11 7 -1 18 9 64
2 5 6 0b101 11 -1 28 25 216
'''
# C列: A列の値を2進数にして格納
# D列: A列とB列の和を格納
# E列: A列とB列の差を格納
# F列: A列のm(=2)倍とB列のn(=3)倍の和を格納
# G列: A列の2乗
# H列: B列の3乗
十字マーク入りのひし形を散りばめる
本題に入ります。
GeoPandas の GeoDataFrame に geometry カラムを設定する例です。
点(x, y)の座標をもとに、
交点(x+a, y+b)、長さ(x方向 c、y方向 d)の十字マーク(マルチライン)と
中心(x+a, y+b)、対角線(x方向 2c, y方向 2d)のひし形(ポリゴン)
を作ります。
こう書かれてもよく分からないと思いますが、十字マーク入りのひし形になります。
先に結果を載せます。
出力したシェープファイルをQGISで表示しています。
コード
複数の引数をとり、複数のオブジェクトを返す自作関数 xy2geometries() を引数として、x座標 と y座標 をカラムにもつ GeoDataFrame に apply() します。
import random
import geopandas as gpd
from shapely.geometry import MultiLineString
from shapely.geometry import Polygon
fp_cross = 'cross.shp'
fp_diamond = 'diamond.shp'
# apply する自作関数
def xy2geometries(row, a, b, c, d):
x = row['x']
y = row['y']
# 十字マーク
coords_cross = [
((x+a-c/2, y+b), (x+a+c/2, y+b)),
((x+a, y+b-d/2), (x+a, y+b+d/2))
]
cross = MultiLineString(coords_cross)
# ひし形
coords_diamond = [
(x+a-2*c/2, y+b),
(x+a, y+b-2*d/2),
(x+a+2*c/2, y+b),
(x+a, y+b+2*d/2)
]
diamond = Polygon(coords_diamond)
return cross, diamond
# ランダムな10点のx座標とy座標
gdf = gpd.GeoDataFrame({
'x': [random.randint(-100, 100) for _ in range(10)],
'y': [random.randint(-100, 100) for _ in range(10)]
})
# 2列にそれぞれ十字マーク(ライン)とひし形(ポリゴン)を格納
# ここで apply() を使います
gdf[['cross', 'diamond']] = gdf.apply(
xy2geometries, axis=1, result_type='expand', a=5, b=6, c=7, d=8
)
# geometryカラムを指定
gdf_cross = gdf.set_geometry('cross')
gdf_diamond = gdf.set_geometry('diamond')
# シェープファイル出力
gdf_cross[['x', 'y', 'cross']].to_file(fp_cross)
gdf_diamond[['x', 'y', 'diamond']].to_file(fp_diamond)
最後に
Pandas の apply() のおさらいと、GeoPandas の GeoDataFrame への応用について書きました。
for文を使うことなく複雑な列処理を高速にできるというのが apply() の魅力だと思います。
今回は十字マークとひし形にしましたが、ほかにも例えば、角度と長さで矢印を書いたりメッシュコードからメッシュを作成したりと応用できそうです。