Gradio + Plotly 交互地图实战:用 gr.Plot 构建纽约 Airbnb 房源筛选应用
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
导读
本文以仓库中的 map_airbnb 演示 为主体,讲解如何用 Gradio 的gr.Plot组件承载 Plotly 的Scattermapbox散点地图,并配合gr.Number、gr.CheckboxGroup、gr.Button实现"按价格区间 + 按行政区"联动过滤 Airbnb 房源的可交互 Web 应用。读完本文你将掌握:从 Hugging Face Datasets 加载真实表格数据、用 pandas 做多条件筛选、用 Plotly 配置带 hover 详情的气泡地图,以及在gr.Blocks中通过demo.load与按钮click事件驱动图表刷新的完整链路。
一、演示功能与定位
仓库中 demo/map_airbnb/DESCRIPTION.md 用一句话点明了该 demo 的核心目标:
Display an interactive map of AirBnB locations with Plotly. Data is hosted on HuggingFace Datasets.
即:用 Plotly 渲染 Airbnb 房源的交互式地图,数据托管在 Hugging Face Datasets 上。落实到具体代码上,它实现了如下三层能力:
- 数据接入:通过
datasets.load_dataset拉取纽约市 Airbnb 开放数据并转为 pandas DataFrame; - 交互过滤:前端输入最低价、最高价和行政区集合,后端对 DataFrame 做布尔筛选,重新生成地图;
- 地图展示:以
gr.Plot为输出组件承载 Plotly Figure,无需自行编写任何前端 JavaScript。
这与仓库中官方教程 plot-component-for-maps 指南 所述主题一致——Gradio 的gr.Plot组件本身支持 Matplotlib、Bokeh、Plotly、Altair 四类图(见 plot.py),本例选用的正是 Plotly。
二、运行前准备与启动方式
demo 目录结构非常简单,仅包含三个文件:
- run.py:完整应用代码;
- requirements.txt:第三方依赖;
- DESCRIPTION.md:功能说明。
其中 requirements.txt 内容为:
plotly datasetsplotly用于构建Scattermapbox地图对象,datasets用于从 Hugging Face Hub 加载数据。启动前需确保当前 Python 环境已安装 gradio 本身,以及上述两个依赖,随后在仓库根目录执行:
pip install plotly datasets python demo/map_airbnb/run.py两点运行前提需要留意(由代码行为可推断):
- 需要联网:首次启动时
load_dataset会从 Hugging Face Hub 下载gradio/NYC-Airbnb-Open-Data数据集,之后由datasets库本地缓存,离线重启时不再重复下载; - 依赖首次调用加载:
from datasets import load_dataset属于模块顶层导入,若当前环境未安装该库,应用会在启动阶段直接报ImportError。
三、数据接入:从 Hugging Face Datasets 读取并转 DataFrame
import gradio as gr import plotly.graph_objects as go from datasets import load_dataset dataset = load_dataset("gradio/NYC-Airbnb-Open-Data", split="train") df = dataset.to_pandas()代码在模块顶层一次性完成数据加载:从数据集gradio/NYC-Airbnb-Open-Data的train分片读取数据,再调用to_pandas()将 Arrow Table 转换为 pandas DataFrame,后续的过滤、取列都基于df完成。
从后面filter_map对列名的引用可以看出,该 DataFrame 至少包含以下与本例直接相关的字段:
| 字段 | 含义 | 在本例中的用途 |
|---|---|---|
name | 房源名称 | 作为 hover 气泡的标题文本 |
price | 每晚价格(美元) | 价格上下限过滤 |
neighbourhood_group | 所在行政区 | 行政区集合过滤 |
latitude/longitude | 经纬度 | 地图 marker 的坐标 |
四、核心过滤逻辑:filter_map 函数拆解
def filter_map(min_price, max_price, boroughs): filtered_df = df[(df['neighbourhood_group'].isin(boroughs)) & (df['price'] > min_price) & (df['price'] < max_price)] names = filtered_df["name"].tolist() prices = filtered_df["price"].tolist() text_list = [(names[i], prices[i]) for i in range(0, len(names))]该函数签名与前端组件一一对应:min_price、max_price来自两个gr.Number,boroughs来自gr.CheckboxGroup返回的被勾选选项列表。筛选采用 pandas 布尔索引,三个条件同时成立:
df['neighbourhood_group'].isin(boroughs):行政区 ∈ 用户勾选集合(多选);df['price'] > min_price:价格严格大于下限;df['price'] < max_price:价格严格小于上限(两端均为开区间)。
随后从筛选结果中取出name、price两列,按行打包成text_list二元组列表,作为地图气泡的自定义数据(customdata),用于 hover 时展示房源名称与价格。
实操建议(基于上面区间语义推断):若希望边界值也被包含,需把比较符改为>=/<=;此外价格过滤是"开区间"设计,当min_price与max_price过于接近或筛选条件过严时,可能出现筛选结果为空的情形,此时地图上不会有任何 marker,属于符合过滤逻辑的正常表现,可在真实业务中额外提示"无匹配房源"。
五、绘制 Plotly Scattermapbox 地图并配置 hover 详情
fig = go.Figure(go.Scattermapbox( customdata=text_list, lat=filtered_df['latitude'].tolist(), lon=filtered_df['longitude'].tolist(), mode='markers', marker=go.scattermapbox.Marker( size=6 ), hoverinfo="text", hovertemplate='<b>Name</b>: %{customdata[0]}<br><b>Price</b>: $%{customdata[1]}' ))关键配置项逐条说明:
lat/lon:房源经纬度列表,决定每个 marker 的地理位置;mode='markers':绘制散点;marker.size=6控制气泡大小;customdata=text_list:携带每行的(名称, 价格)附加数据,不直接展示、仅供 hover 模板引用;hoverinfo="text"+hovertemplate:hover 时按模板渲染。%{customdata[0]}取名称、%{customdata[1]}取价格,<b>/<br>实现加粗与换行,效果即"名称 + $价格"的悬浮信息卡。
接着通过update_layout设置地图的底图风格与视野:
fig.update_layout( mapbox_style="open-street-map", hovermode='closest', mapbox=dict( bearing=0, center=go.layout.mapbox.Center( lat=40.67, lon=-73.90 ), pitch=0, zoom=9 ), ) return figmapbox_style="open-street-map":选用 OpenStreetMap 免费底图,因此无需配置 Mapbox access token 即可直接渲染,这是该 demo 能"零密钥开箱即用"的关键;center将视野中心固定到纽约市区附近(lat 40.67 / lon -73.90),zoom=9控制缩放级别;bearing=0、pitch=0保持地图正北朝向、无俯仰角;hovermode='closest'让鼠标贴近某个气泡时只高亮并展示最近的那个,避免重叠 marker 干扰阅读。
六、Blocks 页面布局与组件组装
with gr.Blocks() as demo: with gr.Column(): with gr.Row(): min_price = gr.Number(value=250, label="Minimum Price") max_price = gr.Number(value=1000, label="Maximum Price") boroughs = gr.CheckboxGroup(choices=["Queens", "Brooklyn", "Manhattan", "Bronx", "Staten Island"], value=["Queens", "Brooklyn"], label="Select Boroughs:") btn = gr.Button(value="Update Filter") map = gr.Plot()页面结构与各组件职责:
gr.Column/gr.Row布局:价格输入放在同一行(Row),整块 UI 垂直排布(Column);- 两个
gr.Number:提供价格上下限。默认值分别为 250 与 1000(value=250、value=1000),即应用打开时默认只展示 $250~$1000 区间的房源; gr.CheckboxGroup:提供纽约五大行政区多选。choices为 ["Queens", "Brooklyn", "Manhattan", "Bronx", "Staten Island"],默认勾选value=["Queens", "Brooklyn"]。从 checkboxgroup.py 的源码注释可见,choices决定可选项、value决定默认选中项,组件默认以type="value"返回被选中的选项值列表,正好对应filter_map的boroughs参数;gr.Button:文本为 "Update Filter" 的触发按钮;gr.Plot:地图输出容器,不接受用户输入、仅承载函数返回的 Plotly Figure。
七、事件绑定:首屏加载与按钮点击双触发
demo.load(filter_map, [min_price, max_price, boroughs], map) btn.click(filter_map, [min_price, max_price, boroughs], map) if __name__ == "__main__": demo.launch()demo.load(fn, inputs, outputs):页面加载完成即执行一次filter_map,让用户无需点击就能先看到"默认条件(Queens + Brooklyn、$250~$1000)"下的初始地图;btn.click(fn, inputs, outputs):点击 "Update Filter" 按钮时,把当前三个组件的值实时传给filter_map并刷新map;- 两次事件绑定的
inputs/outputs完全一致([min_price, max_price, boroughs]→map),因此初始渲染与按钮刷新共用同一套过滤-绘图链路; - 末尾
demo.launch()启动本地服务;若需要临时的公网分享地址,可参照仓库指南为launch()追加share=True。
八、底层原理:gr.Plot 如何承载 Plotly 地图
弄清gr.Plot在服务端做了什么,有助于理解为什么返回一个普通go.Figure就能被前端渲染成可交互地图。核心实现在 gradio/components/plot.py:
- 组件声明其数据模型为
PlotData(plot.py),其字段为type(取值限定为"altair" | "bokeh" | "plotly" | "matplotlib")与plot(图内容的序列化字符串); - 函数返回值会经过
postprocess(plot.py)处理:通过检测对象所属模块("bokeh"、"altair"、"matplotlib"等关键字)自动判别图类型,再各自序列化。对 Plotly 图走value.to_json()分支,将 Figure 序列化为 JSON 字符串装入PlotData; - 前端拿到
PlotData后依据type="plotly"使用 Plotly 运行时还原交互图形,因此 Scattermapbox 的缩放、拖拽、hover 行为在浏览器中原生生效; - 组件文档(plot.py)同时注明 Plot 几乎不充当输入组件,本 demo 中它仅作输出使用,符合组件定位。
换句话说,filter_map每次返回的新go.Figure都会在postprocess中被转成 Plotly JSON 推送到前端替换旧图,由此实现"改参数 → 重筛数据 → 重绘地图"的完整交互闭环。
九、总结与扩展思路
整个 demo 呈现了一条非常典型的 Gradio 数据可视化应用范式:真实外部数据集 + pandas 过滤函数 + Plotly 地图 + Blocks 组件编排 + 双事件刷新。示例展示的是 Airbnb 房源分布,但同一套骨架可平移复用到房价分布、POI 分布、订单地理可视化等任何带经纬度字段的 Plotly 地图场景。
若基于本 demo 做进一步扩展,可优先考虑的方向包括:为gr.Number设置minimum/maximum约束输入合法性;增加"全部行政区"快捷选项或在行政区全不选时给出提示;用gr.Slider替代Number提供更顺滑的价格区间操作;或在go.Scattermapbox中用marker.color按价格给气泡着色,将价格维度从 hover 文本升级为视觉编码。这些改造都只需要在 run.py 的组件定义与filter_map内部增量完成,不需要改动组件架构,足见该模板的扩展弹性。
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考