🔵

pydeck IconLayerに指定するpandas.DataFrameの警告を防ぐ

2023/03/19に公開

環境

Python 3.11.1

requirements.txt
pandas==1.5.3
pydeck==0.8.0

問題の概要

pydeck (deck.gl のPythonラッパー) におけるIconLayerの用例を示します。サンプルコードをそのまま引用します。
https://pydeck.gl/gallery/icon_layer.html

"""
IconLayer
=========

Location of biergartens in Germany listed on OpenStreetMap as of early 2020.
"""

import pydeck as pdk
import pandas as pd


# Data from OpenStreetMap, accessed via osmpy
DATA_URL = "https://raw.githubusercontent.com/ajduberstein/geo_datasets/master/biergartens.json"
ICON_URL = "https://upload.wikimedia.org/wikipedia/commons/c/c4/Projet_bi%C3%A8re_logo_v2.png"

icon_data = {
    # Icon from Wikimedia, used the Creative Commons Attribution-Share Alike 3.0
    # Unported, 2.5 Generic, 2.0 Generic and 1.0 Generic licenses
    "url": ICON_URL,
    "width": 242,
    "height": 242,
    "anchorY": 242,
}

data = pd.read_json(DATA_URL)
data["icon_data"] = None
for i in data.index:
    # ここで警告 (筆者注)
    data["icon_data"][i] = icon_data

view_state = pdk.data_utils.compute_view(data[["lon", "lat"]], 0.1)

icon_layer = pdk.Layer(
    type="IconLayer",
    data=data,
    get_icon="icon_data",
    get_size=4,
    size_scale=15,
    get_position=["lon", "lat"],
    pickable=True,
)

r = pdk.Deck(layers=[icon_layer], initial_view_state=view_state, tooltip={"text": "{tags}"})
r.to_html("icon_layer.html")

これを実行すると以下のような警告が出ます。警告メッセージにもある通り、上記サンプルコード中でコメントした箇所が問題です。

C:\Foo\main.py:32: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  data['icon_data'][i] = icon_data

https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

pandas.DataFrameの各行に、表示させたいアイコンの情報をdictオブジェクトとして設定する仕様です。

修正方法

とりあえず以下のようにすれば回避できました。

data["icon_data"] = None
for i in data.index:
    data.iat[i, -1] = icon_data

icon_dataの列番号を厳密に指定したければこのように。

data["icon_data"] = None
icon_data_col = data.columns.get_loc("icon_data")
for i in data.index:
    data.iat[i, icon_data_col] = icon_data

うまくいかなかった方法

pandasでforループを回すのは大概負けですので、他にスッキリした書き方が無いものかと思っているのですが、今のところ見つけられていません・・・。

pydeck IconLayerの仕様上、dictオブジェクト icon_data をそのままに要素として格納しなければならないようで、それがpandasの要素・列Series代入の仕様も相まってうまくいかない要因になっている気がします。

ダメだった案を列挙:

data.loc[:, "icon_data"] = icon_data

data.iloc[:, -1] = icon_data

data["icon_data"] = json.dumps(icon_data)

Discussion