Ошибка типа Python Congressional Plotly: Объект типа MultiPolygon не сериализуется в формате JSON для округов Конгресса

#python #pandas #plotly #choropleth

Вопрос:

 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=геометрия и местоположения=индекс на основе документации

2: Я вынул geojson в качестве аргумента (поскольку он включен в df) и назвал местоположения=»округ конгресса». Это дает мне пустой белый график в браузере.

3: Я даже использовал данные для shp округа Конгресса из переписи, но это тоже не сработало. Я подозреваю, потому что я не уверен, как правильно вводить аргументы с помощью gpd df.

Комментарии:

1. У вас есть примеры данных, которые вы можете опубликовать? Если это так, я думаю, что вы можете получить быстрые ответы от многих респондентов.

2. @r-начинающие Данные, которые я использую, являются полностью общедоступными, данные карты получены из переписи населения (district_df), а путь geojson_path получен из общедоступного репозитория github. На данный момент я пытаюсь понять, почему я получаю эту ошибку. Надеюсь, это ответ на ваш вопрос.

3. Я увидел ваш ответ и понял, что информация является общедоступной. Спасибо, что нашли время прокомментировать.

Ответ №1:

  • мне не ясно, действителен ли ваш geojson. Учитывая, что вы строите данные переписи населения США, можете также использовать картографические данные переписи населения США https://www.census.gov/geographies/mapping-files/time-series/geo/carto-boundary-file.html
  • нет необходимости объединять фрейм данных geopandas и фрейм данных pandas. Просто требуется связь между двумя геоидами
  • полное решение ниже. (Нужно определить 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")
 

картобокс

Я предпочитаю картографический ящик. Источники данных идентичны, сюжетно 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 нет возможности напрямую создавать 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",
)
 

Комментарии:

1. Как я смогу проверить, верны ли мои данные geojson? Я спрашиваю, потому что я хотел бы применить эту карту с местами для исследований и разработок, которые подробно описаны в этом наборе данных. Если посмотреть на ссылку, она кажется мне действительной на основе других файловых структур geojson (она также регулярно обновляется). Спасибо за вашу помощь!

2. есть обновленный ответ. это не полностью работает, но дает вам начало