1

I'm new to python and plotly.graph_objects. I created some maps similar to the example found here: United States Choropleth Map I'd like to combine the maps into one figure with a common color scale. I've looked at lots of examples of people using shared scales on subplots but they are using different graphing libraries. Is the functionality I want supported? If so, how is it done?

Here is the code I am using:

import plotly.graph_objects as go
import pandas as pd

df_shootings = pd.read_csv('https://raw.githubusercontent.com/washingtonpost/data-police-shootings/master/fatal-police-shootings-data.csv')

state_count = df_shootings.groupby(['state', 'race']).size().reset_index(name='total')

races = pd.DataFrame({'W': 'White, non-Hispanic',
    'B': 'Black, non-Hispanic',
    'A': 'Asian',
    'N': 'Native American',
    'H': 'Hispanic'}, index=[0])

for race in races:
    result = state_count[['state', 'total']][state_count.race == race]
    fig = go.Figure(data=go.Choropleth(
        locations=result.state,
        z = result.total,
        locationmode = 'USA-states', # set of locations match entries in `locations`
        marker_line_color='white',
        colorbar_title = "Shooting deaths",
    ))

    fig.update_layout(
        title_text = races[race][0],
        geo_scope='usa', # limite map scope to USA
    )
    fig.data[0].hovertemplate =  'State: %{location}<br>Shooting deaths: %{z:.2f}<extra></extra>'
    fig.show()

This is what I would like to get: enter image description here

Right now I get individual maps with their own color scale which is different for each map.

JSWilson
  • 1,113
  • 1
  • 11
  • 28

2 Answers2

2

I just solved this problem for my own project. I rewrote your code and split it up with comments.

No changes here, except adding a couple common packages and maybe the location of some code.

import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
import numpy as np

df_shootings = pd.read_csv('https://raw.githubusercontent.com/washingtonpost/data-police-shootings/master/fatal-police-shootings-data.csv')

state_count = df_shootings.groupby(['state', 'race']).size().reset_index(name='total')

races = pd.DataFrame({'W': 'White, non-Hispanic',
    'B': 'Black, non-Hispanic',
    'A': 'Asian',
    'N': 'Native American',
    'H': 'Hispanic'}, index=[0])

Declare a figure of subplots. You need to specify the number of rows and columns, you need to prepare the subplots to receive choropleths specifically, and we'll use your races dataframe to declare your subplot titles.

rows = 2
cols = 3
fig = make_subplots(
    rows=rows, cols=cols,
    specs = [[{'type': 'choropleth'} for c in np.arange(cols)] for r in np.arange(rows)],
    subplot_titles = list(races.loc[0,:]))

I use enumerate in order to calculate the correct row and column of each subplot graph. I don't know if this is pythonic. The value i is just counting from 0 as we go through races. Each time through the loop we're going to add a trace to the appropriate row and column. In order to make sure that we use the same colorscale for each subplot, we set zmin and zmax to 0 and the maximum state_count['total'] respectively. The calculations for row and col use the count from enumerate to determine which row and column we graph this in.

for i, race in enumerate(races):
    result = state_count[['state', 'total']][state_count.race == race]
    fig.add_trace(go.Choropleth(
        locations=result.state,
        z = result.total,
        locationmode = 'USA-states', # set of locations match entries in `locations`
        marker_line_color='white',
        zmin = 0,
        zmax = max(state_count['total']),
        colorbar_title = "Shooting deaths",
    ), row = i//cols+1, col = i%cols+1)

I made up what I thought was an appropriate title for the entire figure. The next line was a heck of a trick for me, but that's what sets each subplot to a plot of the USA instead of the default map of the entire world. Without it, only the first subplot would use the USA map. I'll give you my best understanding of what was going on, and I couldn't find or figure out a better solution. Basically, each subplot has a kinda sorta location that are named, in order, geo, geo2, geo3, geo4, etc. Each one has to have its scope set to usa. 'Magic' underscores didn't work, so I had to put together a **kwargs (keyword argument) that was equivalent to geo_scope = 'usa', geo2_scope = 'usa', geo3_scope = 'usa', geo4_scope = 'usa', geo5_scope = 'usa'. Granted that wasn't so bad to type out, but in my project, I had 50 subplots, so I coded it. Basically I made a dictionary and then converted it to kwargs with **. The fact that the keyword argument list starts with geo instead of geo0 or even geo1 is why that line is as complicated as it is.

fig.update_layout(
    title_text = 'Shooting Deaths by Race',
    **{'geo' + str(i) + '_scope': 'usa' for i in [''] + np.arange(2,rows*cols+1).tolist()},
    )

for index, trace in enumerate(fig.data):
    fig.data[index].hovertemplate = 'State: %{location}<br>Shooting deaths: %{z:.2f}
<extra></extra>'
fig.show()

And here's the result: Shooting Deaths by Race

wex52
  • 475
  • 1
  • 4
  • 11
1

I was working on a similar project and got this far See picture Couldn't find a way how to name the subplots

import plotly.graph_objects as go
import pandas as pd

df_shootings = pd.read_csv('https://raw.githubusercontent.com/washingtonpost/data-police-shootings/master/fatal-police-shootings-data.csv')

state_count = df_shootings.groupby(['state', 'race']).size().reset_index(name='total')

races = pd.DataFrame({'W': 'White, non-Hispanic',
    'B': 'Black, non-Hispanic',
    'A': 'Asian',
    'N': 'Native American',
    'H': 'Hispanic'}, index=[0])
races
fig = go.Figure()
layout = dict(
    title_text = "Fatal Police Shootings Data",
    geo_scope='usa',
)

for index, race in enumerate(races):
    result = state_count[['state', 'total']][state_count.race == race]
    geo_key = 'geo'+str(index+1) if index != 0 else 'geo'  
    fig.add_trace(
        go.Choropleth(
            locations=result.state,
            z = result.total,
            locationmode = 'USA-states', # set of locations match entries in `locations`
            marker_line_color='white',
            colorbar_title = "Shooting deaths",
            geo=geo_key,
            name=races[race].values[0],
            coloraxis = 'coloraxis',
        )
    )
    
    layout[geo_key] = dict(
        scope = 'usa',
        domain = dict( x = [], y = [] ),
    )

layout
z = 0
COLS = 3
ROWS = 2
for y in reversed(range(ROWS)):
    for x in range(COLS):
        geo_key = 'geo'+str(z+1) if z != 0 else 'geo'
        layout[geo_key]['domain']['x'] = [float(x)/float(COLS), float(x+1)/float(COLS)]
        layout[geo_key]['domain']['y'] = [float(y)/float(ROWS), float(y+1)/float(ROWS)]
        z=z+1
        if z > 4:
            break
            
fig.update_layout(layout)   
fig.show()
  • Welcome to Stack Overflow. Thank you for answering an older question. While code-only answers are sometimes enough, I think it'd be great if you could provide additional context and explanation for your solution. – As you're starting out here, please take the [tour](https://stackoverflow.com/tour), and also refer to [How do I write a good answer?](https://stackoverflow.com/help/how-to-answer). – Ivo Mori Jul 31 '20 at 14:09