Sven D
Sven D

Reputation: 11

Geoplot Choropleth map - AttributeError: 'Point' object has no attribute 'exterior'

I am trying to create a choropleth map using Geoplot. The data comes in the form of a CSV and I am trying to create a choropleth map of a grid created over a specific area, in this case over the Tampa Florida region. I am wanting the final map to look like this

This is what I am attempting to create

where the color scheme is determined by the value of the column ['InitialResponse']. Each row represents a point coordinate that has a lat / long attribute in the Tampa area.

When I run

gplt.choropleth(dfsjoin, hue="InitialResponse", linewidth=.1, scheme=scheme, cmap='inferno_r', legend=True, edgecolor='black', ax=ax );

I get the following error:

`---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File D:\Anaconda\lib\site-packages\geoplot\geoplot.py:982, in choropleth.<locals>.ChoroplethPlot.draw(self)
    981 try:  # Duck test for MultiPolygon.
--> 982     for subgeom in geom:
    983         feature = GeopandasPolygonPatch(
    984             subgeom, facecolor=color, **self.kwargs
    985         )

TypeError: 'Point' object is not iterable

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
Cell In[94], line 1
----> 1 gplt.choropleth(dfsjoin, 
      2     hue="InitialResponse", 
      3     linewidth=.1,
      4     scheme=scheme, cmap='inferno_r',
      5     legend=True,
      6     edgecolor='black',
      7     ax=ax
      8 )

File D:\Anaconda\lib\site-packages\geoplot\geoplot.py:1001, in choropleth(df, projection, hue, cmap, norm, scheme, legend, legend_kwargs, legend_labels, legend_values, extent, figsize, ax, **kwargs)
    993         return ax
    995 plot = ChoroplethPlot(
    996     df, figsize=figsize, ax=ax, extent=extent, projection=projection,
    997     hue=hue, scheme=scheme, cmap=cmap, norm=norm,
    998     legend=legend, legend_values=legend_values, legend_labels=legend_labels,
    999     legend_kwargs=legend_kwargs, **kwargs
   1000 )
-> 1001 return plot.draw()

File D:\Anaconda\lib\site-packages\geoplot\geoplot.py:988, in choropleth.<locals>.ChoroplethPlot.draw(self)
    986                 ax.add_patch(feature)
    987         except (TypeError, AssertionError):  # Shapely Polygon.
--> 988             feature = GeopandasPolygonPatch(
    989                 geom, facecolor=color, **self.kwargs
    990             )
    991             ax.add_patch(feature)
    993 return ax

File D:\Anaconda\lib\site-packages\geopandas\plotting.py:120, in _PolygonPatch(polygon, **kwargs)
    116 from matplotlib.patches import PathPatch
    117 from matplotlib.path import Path
    119 path = Path.make_compound_path(
--> 120     Path(np.asarray(polygon.exterior.coords)[:, :2]),
    121     *[Path(np.asarray(ring.coords)[:, :2]) for ring in polygon.interiors],
    122 )
    123 return PathPatch(path, **kwargs)

AttributeError: 'Point' object has no attribute 'exterior'
`

I have imported the CSV file as a pandas dataframe, and then converted it into a geopandas dataframe.

df_call_details = pd.read_csv("Model Rerun 50k Calls Masked.csv", index_col=False) df_call_details.keys()

Index(['Date', 'NatureCode', 'Address', 'CallID', 'FirstIn', 'ProcessingTime',
       'InitialResponse', 'FullComplement', 'IsFullOverwhelm',
       'IsPartialOverwhelm', 'HasResponses', 'Remarks', 'Level1Cause',
       'Level2Cause', 'CustomContent1', 'RegionName', 'DispatchRule',
       'ResponsePlan', 'XCoordinate', 'YCoordinate', 'Longitude', 'Latitude',
       'Call (Custom)', 'FirstResponding (Custom)', 'FirstArrival (Custom)',
       'FullComplement (Custom)', 'STEMI_Timestamp (Custom)', 'Grid (Custom)',
       'EMS_Area (Custom)', 'City (Custom)', 'FDID (Custom)',
       'Downgrade_ (Custom)', 'First_Due_Station (Custom)',
       'Area_Chief (Custom)', 'PD_Determinant (Custom)', 'PD_Acuity (Custom)',
       'PD_AnatPath (Custom)', 'Priority_Dispatch_Code (Custom)',
       'COVID_Flag (Custom)', 'RIP_Flag (Custom)', 'Downgrade_Time (Custom)',
       'Brain_Attack_Time (Custom)', 'STEMI_Time (Custom)',
       'Alarm_Count (Custom)'],
      dtype='object')

and then created a geo data frame

gdf_call_details = gpd.GeoDataFrame(df_call_details, geometry=gpd.points_from_xy(df_call_details['Longitude'],df_call_details['Latitude']))
gdf_call_details.crs='EPSG:4326'

Which appears to correctly create a geometry column with WKT.

I have also created a grid polygon in QGIS that I have imported using

gridData = gpd.read_file("shp\Grid Polygon 4326.shp")

I performed a spatial join between the dataframe containing the point polygons and the attribute I want to display and the grid

dfsjoin = gpd.sjoin(gdf_call_details, gridData, how='inner')

When I ran the code, I was expecting the grid to darken the areas of the grid based on the values of the Initial Response value for each row.

My code:

import pandas as pd, matplotlib, datetime, re, numpy as np, geopandas as gpd, geoplot as gplt, matplotlib.pyplot as plt, contextily as cx, folium, seaborn as sns
import cartopy.crs as ccrs, mapclassify as mc
import cartopy.io.img_tiles as cimgt
from IPython.display import display, HTML
display(HTML("<style>.container { width:95% !important; }</style>"))

gridData = gpd.read_file("shp\Grid Polygon 4326.shp")
gplt.polyplot(gridData, edgecolor='darkgrey', facecolor='lightgrey', linewidth=.3,
figsize=(12, 8))

df_call_details = pd.read_csv("Model Rerun 50k Calls Masked.csv", index_col=False)
df_call_details.keys()

gdf_call_details = gpd.GeoDataFrame(df_call_details, geometry=gpd.points_from_xy(df_call_details['Longitude'],df_call_details['Latitude']))
gdf_call_details.crs='EPSG:4326'
gdf_call_details.keys()

sns.histplot(responses['InitialResponse'], kde=True, stat='count')

dfsjoin = gpd.sjoin(gdf_call_details, gridData, how='inner')
dfsjoin.head(2)

scheme = mc.Quantiles(dfsjoin['InitialResponse'], k=15)

gplt.choropleth(dfsjoin, 
    hue="InitialResponse", 
    linewidth=.1,
    scheme=scheme, cmap='inferno_r',
    legend=True,
    edgecolor='black',
    ax=ax
);

I can see that it is taking issue with the point data, however I cannot figure out if I need to try and make it a polygon with points or if I made a mistake with the spatial join.

Upvotes: 0

Views: 436

Answers (1)

Sven D
Sven D

Reputation: 11

I figured out a solution, but I think it's clunky and it runs slow.

gdfjoin = gpd.sjoin(gdf_call_details, gridData, how='inner', predicate='intersects')
gdfjoin.drop(columns='index_right', inplace=True)
gdf_shapes = gdfjoin.merge(gridData, on='id')
gdf_shapes = gpd.GeoDataFrame(gdf_shapes, geometry= gdf_shapes['geometry_y'])
gdf_shapes.drop(columns='geometry_y', inplace=True)
gdf_shapes.head(2)

I found that by performing a regular dataframe merge between the newly created dataframe that was created on the .sjoin and the dataframe containing my shape grid to merge the polygon geometry into my desired dataframe that I renamed gdf_shapes.

However, I couldn't find a way to perform a merge while remaining a Geopandas dataframe, instead it became a regular pandas dataframe so I recreated a geodataframe with geometry = geometry (the new column created when I merged the shapefile dataframe into my data dataframe).

If anyone has suggestions on how to perform these steps in the .sjoin function without having to perform a merge, drop columns, and recreate the geopandas dataframe I would love to know!

Upvotes: 0

Related Questions