python - Python Congressional Plotly TypeError:MultiPolygon 类型的对象不是国会选区的 JSON 可序列化对象
问题描述
import pandas as pd
from census import Census
import geopandas as gpd
import numpy as np
import plotly.io as pio
import plotly.express as px
pio.renderers.default='browser'
file_path = "Path"
# Load Census Median Age by District Data
c = Census("KEY")
district_df = c.acs1.state_congressional_district(['NAME', 'B01002_001E'], '*', '*')
district_df = pd.DataFrame(district_df)
district_df.rename(columns={'B01002_001E': 'median_age'},
inplace=True)
district_df['GEOID'] = district_df['state'] + district_df['congressional district']
district_df.sort_values(by=['GEOID'], ascending=True)
district_df['id'] = np.arange(1,438)
# Import District Geojson
geojson_path = 'https://raw.githubusercontent.com/CivilServiceUSA/us-house/master/us-house/geojson/us-house.geojson'
geojson_file = gpd.read_file(geojson_path)
# Transformations
# Fill at-large districts nans with 0 to align with census
geojson_file[['district']] = geojson_file[['district']].fillna(value=0)
geojson_file['district'] = geojson_file.district.astype(float)
# drop congressional districts that have no voting power (State Number 98)
district_df['congressional district'] = district_df['congressional district'].astype(float)
# district_df = district_df.loc[district_df['state'] != 98]
# Create index state_name to inner join
new = district_df['NAME'].str.split(", ", n = 1, expand = True)
district_df['district_name'] = new[0]
district_df['state_name'] = new[1]
district_df.drop(columns = 'NAME', inplace = True)
# Inner Join
merged_data = pd.merge(district_df, geojson_file,
left_on=['state_name', 'congressional district'],
right_on=['state_name', 'district'])
# Plot
fig = px.choropleth(merged_data,
geojson=merged_data.geometry,
locations=merged_data.index,
color='median_age',
color_continuous_scale='Viridis',
scope='usa',
projection='mercator',
basemap_visible=True)
fig.update_geos(fitbounds="locations", visible=True)
fig.update_layout(title_text='Map')
fig.show()
当我运行此代码时,出现以下错误
追溯:
Traceback (most recent call last):
File "/Users/colby/Desktop/Colby's Folder/Congressional Demo ETL Project/scripts/etl.py", line 81, in <module>
fig.show()
File "/Users/colby/opt/anaconda3/lib/python3.8/site-packages/plotly/basedatatypes.py", line 3398, in show
return pio.show(self, *args, **kwargs)
File "/Users/colby/opt/anaconda3/lib/python3.8/site-packages/plotly/io/_renderers.py", line 404, in show
renderers._perform_external_rendering(fig_dict, renderers_string=renderer, **kwargs)
File "/Users/colby/opt/anaconda3/lib/python3.8/site-packages/plotly/io/_renderers.py", line 341, in _perform_external_rendering
renderer.render(fig_dict)
File "/Users/colby/opt/anaconda3/lib/python3.8/site-packages/plotly/io/_base_renderers.py", line 747, in render
html = to_html(
File "/Users/colby/opt/anaconda3/lib/python3.8/site-packages/plotly/io/_html.py", line 141, in to_html
jdata = to_json_plotly(fig_dict.get("data", []))
File "/Users/colby/opt/anaconda3/lib/python3.8/site-packages/plotly/io/_json.py", line 124, in to_json_plotly
return json.dumps(plotly_object, cls=PlotlyJSONEncoder, **opts)
File "/Users/colby/opt/anaconda3/lib/python3.8/json/__init__.py", line 234, in dumps
return cls(
File "/Users/colby/opt/anaconda3/lib/python3.8/site-packages/_plotly_utils/utils.py", line 59, in encode
encoded_o = super(PlotlyJSONEncoder, self).encode(o)
File "/Users/colby/opt/anaconda3/lib/python3.8/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/Users/colby/opt/anaconda3/lib/python3.8/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/Users/colby/opt/anaconda3/lib/python3.8/site-packages/_plotly_utils/utils.py", line 136, in default
return _json.JSONEncoder.default(self, obj)
File "/Users/colby/opt/anaconda3/lib/python3.8/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type MultiPolygon is not JSON serializable
我尝试过的解决方案:
1:我也尝试过根据文档使用 geojson=geometry 和 locations=index
2:我已经取出geojson作为参数(因为它包含在df中),并命名为locations='congressional district'。这在浏览器中给了我一个空白的白色图。
3:我什至使用人口普查中的国会选区 shp 数据,但这也不起作用。我怀疑是因为我不确定如何使用 gpd df 正确输入参数。
解决方案
- 我不清楚您的geojson是否有效。鉴于您正在绘制美国人口普查数据,不妨使用美国人口普查地图数据https://www.census.gov/geographies/mapping-files/time-series/geo/carto-boundary-file.html
- 无需合并geopandas数据框和pandas数据框。只需要两个GEOID之间的链接
- 下面的完整解决方案。(需要定义
KEY
为你的关键)平移和缩放默认不是很好
import requests
import urllib
from pathlib import Path
from zipfile import ZipFile
import geopandas as gpd
import pandas as pd
from census import Census
import plotly.express as px
# get geometry data as a geopandas dataframe
# fmt: off
# https://www.census.gov/geographies/mapping-files/time-series/geo/carto-boundary-file.html
src = [{"name": "counties", "suffix": ".shp", "url": "https://www2.census.gov/geo/tiger/GENZ2018/shp/cb_2018_us_cd116_20m.zip",},]
data = {}
for s in src:
f = Path.cwd().joinpath(urllib.parse.urlparse(s["url"]).path.split("/")[-1])
if not f.exists():
r = requests.get(s["url"],stream=True,)
with open(f, "wb") as fd:
for chunk in r.iter_content(chunk_size=128): fd.write(chunk)
fz = ZipFile(f)
fz.extractall(f.parent.joinpath(f.stem))
data[s["name"]] = gpd.read_file(f.parent.joinpath(f.stem).joinpath([f.filename for f in fz.infolist() if Path(f.filename).suffix == s["suffix"]][0]))
gdf = pd.concat(data.values()).to_crs("EPSG:4326")
# fmt: on
# get census measures...
c = Census(KEY)
district_df = (
pd.DataFrame(c.acs1.state_congressional_district(["NAME", "B01002_001E"], "*", "*"))
.rename(columns={"B01002_001E": "median_age"})
.assign(
GEOID=lambda d: d.loc[:, ["state", "congressional district"]]
.astype(str)
.apply("".join, axis=1)
)
)
# plot
fig = px.choropleth(
district_df,
geojson=gdf.set_index("GEOID").geometry,
locations="GEOID",
color="median_age",
color_continuous_scale="Viridis",
# scope="usa",
projection="mercator",
basemap_visible=True,
)
fig.update_geos(fitbounds="geojson", visible=True)
fig.update_layout(title_text="Map")
地图框
我更喜欢 Mapbox。数据来源相同,情节API 非常相似
px.choropleth_mapbox(
district_df,
geojson=gdf.set_index("GEOID").geometry,
locations="GEOID",
color="median_age",
color_continuous_scale="Viridis",
).update_layout(
mapbox={
"style": "carto-positron",
"zoom": 3,
"center": {"lat": 39.50, "lon": -98.35},
},
title_text="Map",
)
原始geojson
- 这仅部分有效。缺少一些州
- geojson 没有能力直接构建FIPS。因此,状态 alpha2 代码到数字 FIPS 代码的来源
import requests
import urllib
from pathlib import Path
from zipfile import ZipFile
import geopandas as gpd
import pandas as pd
from census import Census
import plotly.express as px
res = requests.get("https://raw.githubusercontent.com/CivilServiceUSA/us-house/master/us-house/geojson/us-house.geojson")
gdf = gpd.GeoDataFrame.from_features(res.json())
df_fips = pd.read_html(
"https://www.nrcs.usda.gov/wps/portal/nrcs/detail/?cid=nrcs143_013696"
)[0]
df_fips = df_fips.dropna().assign(
FIPS=lambda d: d["FIPS"].apply(lambda x: str(int(x)).zfill(2))
)
gdf = gdf.merge(df_fips, left_on="state_code", right_on="Postal Code").assign(
GEOID=lambda d: d.apply(lambda r: r["FIPS"] + str(r["district"]).zfill(2), axis=1)
)
# get census measures...
c = Census(KEY)
district_df = (
pd.DataFrame(c.acs1.state_congressional_district(["NAME", "B01002_001E"], "*", "*"))
.rename(columns={"B01002_001E": "median_age"})
.assign(
GEOID=lambda d: d.loc[:, ["state", "congressional district"]]
.astype(str)
.apply("".join, axis=1)
)
)
# plot
px.choropleth_mapbox(
district_df,
geojson=gdf.set_index("GEOID").geometry,
locations="GEOID",
color="median_age",
color_continuous_scale="Viridis",
).update_layout(
mapbox={
"style": "carto-positron",
"zoom": 3,
"center": {"lat": 39.50, "lon": -98.35},
},
title_text="Map",
)
推荐阅读
- reactjs - 如何使用反应路由器在 URL 中使用哈希
- javascript - JS中以毫秒为单位的时间计算
- docker - 如何给容器一个静态 ip,或者如何链接两个 Docker 容器?
- java - 詹金斯在运行 jar 文件后挂起
- python - Selenium 测试自动化,driver.get(url) 发生在网站启动之前
- c# - 如何将 Home/End 按钮按下传播到 WPF ScrollViewer 内的 UWP RichEditBox?
- matlab - 向量中的数据排列
- python - 在 while 循环 discord.py 之后执行 if 语句
- vue.js - Vue2Leaflet 没有在地图中放置自定义图标
- .net - 如果所有迁移都被意外删除了怎么办?