首页 > 解决方案 > 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 正确输入参数。

标签: pythonpandasplotlychoropleth

解决方案


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",
)

推荐阅读