Databricksノートブックで地図ウィジェットを活用する

以下で説明されているノートブックをウォークスルーした内容になっています。

https://qiita.com/taka_yayoi/items/afa752b1441165cb5b18#%E9%AB%98%E5%BA%A6%E3%81%AA%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB-ipywidgets%E3%81%AB%E3%82%88%E3%82%8B%E5%9C%B0%E5%9B%B3

ノートブックはこちらにあります。

https://github.com/taka-yayoi/public_repo/tree/main/ipywidgets_advance

このノートブックでは、ファーマーマーケットのデータセットを探索するためにJupyterのウィジェットを活用します。Jupyterのウィジェットはデータを探索可能にし、ノートブックでの作業をより簡単かつインタラクティブなものにします。

# ipyleafletのインストール

はじめに[ipyleaflet](https://ipyleaflet.readthedocs.io/en/latest/) Pythonパッケージをインストールします。あとでマップ上に地理情報データを表示する際に使用します。

```py
%pip install ipyleaflet==0.17.0
```

# データの読み込み

`databricks-datasets`からpandasデータフレームとしてファーマーマーケットのデータを読み込みます。

```py:Python
import pandas as pd
market_data = pd.read_csv("/dbfs/databricks-datasets/data.gov/farmers_markets_geographic_data/data-001/market_data.csv")
display(market_data)
```
![Screen Shot 2022-08-10 at 14.36.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/177118ee-15f6-9b95-f143-57434562673b.png)

# ipywidgetsの活用

データセットは8000行以上、約60個のカラムがあるので、これら全てを一度に参照するのは実用的ではありませんが、データのサブセットを表示するためにipywidgetsを活用することができます。

```py:Python
import ipywidgets as w
column_widget = w.SelectMultiple(options=market_data.columns, value=["MarketName", "city", "County", "State", "Fruits", "Coffee", "x", "y"])

@w.interact(first_row=(0, len(market_data), 25), columns=column_widget)
def display_data(first_row=0, columns=["MarketName", "city", "County", "State", "Fruits", "Coffee", "x", "y"]):
  return market_data.loc[first_row:first_row+25, columns]
```
スライダーで表示行、マルチセレクトボックスで表示列をコントロールできます。
![Screen Shot 2022-08-10 at 14.46.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/f223e075-7ea7-eb6d-1253-7a6f6017f1e3.png)

# ipyleafletの活用

これでテーブルにどのような種類があるのか理解を深めることができます。サンフランシスコにどのようなマーケットがあって、それらがどこにあるのかを知りたいものとします。インタラクティブな地図上にサンフランシスコのすべてのファーマーズマーケットの場所を表示するためにipyleafletのmapウィジェットを活用することができます。そして、ズームインすることでそれぞれのマーケットの正確な場所を確認することができます。

```py:Python
from ipyleaflet import Map, Marker, MarkerCluster

city_map = Map(center=(37.76, -122.45), zoom=12)
local_markets = market_data[market_data.city == "San Francisco"]
locations = [Marker(location=(y, x), draggable=False) for (x, y) in zip(local_markets.x, local_markets.y)]
cluster = MarkerCluster(markers=locations)
city_map.add(cluster)
city_map
```
![Screen Shot 2022-08-10 at 14.47.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/767abc0e-aa9a-46c5-b556-92cd53555617.png)

# HTMLウィジェットによる地図の拡張

この地図は素晴らしいものですが、地図上で見ているのがどのマーケッなのかを知ることが少し大変です。これらをクリックした際に情報がポップアップされるようにマーカーを再定義することができます。このためには、それぞれのマーケットの名前と時間を表示するHTMLウィジェットを使います。以下のコードでは上の地図を変更しており、以下のセルを実行したあとに変更を確認するために、上にスクロールしてください。

```py:Python
market_desc = """<div style="white-space: pre">Name: {name}
Season: {season}
Time: {time}</div>
"""

def createMarker(row):
  "Create a marker with an appropriate description from a row in our dataset"
  description = market_desc.format(name=row.MarketName, season=row.Season1Date, time=row.Season1Time)
  return Marker(location=(row.y, row.x), popup=w.HTML(description), draggable=False)

cluster.markers = [createMarker(row) for idx, row in local_markets.iterrows()]
```
![Screen Shot 2022-08-10 at 14.48.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/e975fe5b-514f-300c-2da9-ecaeb8df8a7e.png)

# 地図におけるフィルタリング

あなたの好きな食べ物を売っているマーケットを特定するために、買いたいものに基づいてマーケットをフィルタリングするマルチセレクトのウィジェットを地図に追加します。

```py:Python
import operator
import functools
from ipyleaflet import WidgetControl

# オプション "" はフィルターなしを表現します
item_list = ["Any product", "Bakedgoods", "Cheese", "Crafts", "Flowers", "Eggs", "Seafood", "Herbs", 
             "Vegetables", "Honey", "Jams", "Meat", "Nuts", "Plants", "Poultry", "Prepared",
             "Soap", "Wine", "Coffee", "Beans", "Fruits"]
item_filter = w.SelectMultiple(options=item_list, rows=len(item_list))

def update_markers(*args):
    selected_items = item_filter.value
    if len(selected_items) == 0 or "Any product" in selected_items:
        filtered_markets = local_markets
    else:
        filter = functools.reduce(operator.and_, (local_markets[item] == "Y" for item in selected_items))
        filtered_markets = local_markets[filter]
    cluster.markers = [createMarker(row) for idx, row in filtered_markets.iterrows()]

item_filter.observe(update_markers, names="value")
city_map.add(WidgetControl(widget=item_filter, position="bottomright"))
```
![Screen Shot 2022-08-10 at 14.49.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d706652f-9fac-7b9b-db5d-ca4b94946c4f.png)

この時点で、あなたは今週末に訪れるマーケットを特定できたことになります。そしてこのプロセスでは、小規模なデータアプリケーションを構築しました。他の領域でも使えるように、このアプリケーションをより一般的なものにすることができます。以下のセルでは、地図上で表示される範囲にマッチするマーカーをアップデートするためのリフレッシュボタンを追加しています。

```py:Python
import numpy as np

max_markers = 200

refresh = w.Button(description="Refresh")
current_bounds = (city_map.north, city_map.south, city_map.east, city_map.west)

def update_local_markets(*args):
    global local_markets
    global current_bounds
    bounds = (city_map.north, city_map.south, city_map.east, city_map.west)
    if bounds == current_bounds:
        return
    else:
        current_bounds = bounds
    local_markets = market_data[market_data.x.between(city_map.west, city_map.east) & market_data.y.between(city_map.south, city_map.north)]
    # 場所が多い場合には、地図の中心に最も近いものを取得します
    if len(local_markets) > max_markers:
        dist = np.linalg.norm(local_markets[["y", "x"]] - city_map.center, axis=1)
        closest = dist.argpartition(max_markers)[:max_markers]
        local_markets = local_markets.iloc[closest]
        # 表示されていないマーケットが存在する場合にはボタンに赤枠を表示します
        refresh.layout.border = "1px solid red"
    else:
        refresh.layout.border = ""
    update_markers()
  
refresh.on_click(update_local_markets)
city_map.add(WidgetControl(widget=refresh, position="bottomleft"))
```
![Screen Shot 2022-08-10 at 14.50.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/2e949a22-e2a1-996b-8d3f-7083830d3f98.png)


### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)