Step-by-step: How to plot a map with slider to represent time evolution of murder rate in the US using Plotly


This post in based on this other one I posted a few days ago, where I'm exploring a new data set about murder rates in the US. I decided to write a plot detailing how to plot a map of said murder rates in the US, but also adding a slider to explore the different years included in the data set.

The data is gathered and published by the FBI, but I'm gonna be using this other version, that I minimally modified to make it easier to use.

Also, I am using this post to practice how to embed my own python code in a way that is useful and looks nice. I am gonna do this by converting the particular snippet of code into html here.

Ok, so, let's start with the data, which looks like this:



We have one row per year and per state, and in each one of them, we have the population of the state, as well as the total number of violent crimes committed, the breakdown by type of crime, and the murder rate per 100,000 people.

I'm gonna start by plotting a single year of data on a map of the US, and then I'll build from there.

First, some imports:

import pandas as pd
import plotly
import plotly.graph_objs as go

import plotly.offline as offline
from plotly.graph_objs import *
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

init_notebook_mode(connected=True)



I load the data file, and select the focus year (I'll also remove data for District of Columbia, for now):

df_merged = pd.read_csv('../CrimeStatebyState_1960-2014.csv')

year = 1960

df_sected_crime = df_merged[(df_merged['State']!= 'District of Columbia' ) &\
                            (df_merged['Year']== year )]



Then I specify the color scheme for the map plot, and I create a new column that will include the mouse-hovering text for each state:


scl = [[0.0, '#ffffff'],[0.2, '#ff9999'],[0.4, '#ff4d4d'], \
       [0.6, '#ff1a1a'],[0.8, '#cc0000'],[1.0, '#4d0000']] # reds


for col in df_sected_crime.columns:
    df_sected_crime[col] = df_sected_crime[col].astype(str)
 
df_sected_crime['text'] = df_sected_crime['State'] + \
    'Pop: ' + df_sected_crime['Population'] + \
    'Tot. Violent crimes: '+df_sected_crime['Violent_crime_total'] +\
    'Murder rate: '+df_sected_crime['Murder_per100000']

I decided to go with a red color scale, as it has an almost-intuitive association with violence. But you can pick different color schemes if you prefer. I recommend the color brewer site or the color picker site. In particular, the color brewer site not only gives you the hexadecimal or RGB codes for the colors you like, but also, it suggests pleasant color schemes for a given number of colors, as well as black & white printer-friendly, or color blind-friendly color schemes etc.

The data object for plotting need to be a list of dictionaries set up as follows:


data = [ dict(
            type='choropleth', # type of map-plot
            colorscale = scl,
            autocolorscale = False,
            locations = df_sected_crime['State_code'], # the column with the state
            z = df_sected_crime['Murder_per100000'].astype(float), # the variable I want to color-code
            locationmode = 'USA-states',
            text = df_sected_crime['text'], # hover text
            marker = dict(     # for the lines separating states
                        line = dict (
                                  color = 'rgb(255,255,255)', 
                                  width = 2) ),               
            colorbar = dict(
                        title = "Murder rate per 100,000 people")
            ) 
       ]


Then I take care of the layout, create the figure object and I plot it :

layout = dict(
        title = year,
        geo = dict(
            scope='usa',
            projection=dict( type='albers usa' ),
#              showlakes = True,  # if you want to give color to the lakes
#             lakecolor = 'rgb(73, 216, 230)'  
            ),
             )
    
fig = dict( data=data, layout=layout )

plotly.offline.iplot(fig)

Or, if you want to plot it in a different window on your browser:

offline.plot(fig, auto_open=True, image = 'png', image_filename="map_us_crime_"+str(year),
             image_width=2000, image_height=1000, 
             filename='/your_path/'+"map_us_crime_"+str(year)+'.html', validate=True)



This is the output:


We clearly observe how in 1960, the South was a considerable more violent region of the US than the North. It would be interesting to see how that pattern changes over time.


Thus, now we make the necessary modifications to add a slider to go over the different years in the data set. The main conceptual differences are that now the data object now is going to be a list of dictionaries, and also that I need to create a 'steps', and a 'slider' object that will go as an argument for the layout command.

After loading the data set and defining the color scheme, I create an empty list for the data, that I will populate with dictionaries (one per year):



#### input data set:
df_merged = pd.read_csv('../CrimeStatebyState_1960-2014.csv')

### colorscale:    

scl = [[0.0, '#ffffff'],[0.2, '#ff9999'],[0.4, '#ff4d4d'], \
       [0.6, '#ff1a1a'],[0.8, '#cc0000'],[1.0, '#4d0000']] # reds


### create empty list for data object:    
data_slider = []



Now, I populate the data object, one dictionary per year that will be displayed with the slider, by iterating over the different years in the data set:


#### I populate the data object
for year in df_merged.Year.unique():
 
    # I select the year (and remove DC for now)
    df_sected_crime = df_merged[(df_merged['State']!= 'District of Columbia' ) &\
                            (df_merged['Year']== year )]
    
    
    for col in df_sected_crime.columns:  # I transform the columns into string type so I can:
        df_sected_crime[col] = df_sected_crime[col].astype(str)

    ### I create the text for mouse-hover for each state, for the current year    
    df_sected_crime['text'] = df_sected_crime['State'] + '<br>' +\
    'Pop: ' + df_sected_crime['Population'] + '<br>' +\
    'Tot. Violent crimes: '+df_sected_crime['Violent_crime_total'] + '<br>' +\
    'Murder rate: '+df_sected_crime['Murder_per100000']

  
    ### create the dictionary with the data for the current year
    data_one_year = dict(
                        type='choropleth',
                        locations = df_sected_crime['State_code'],
                        z=df_sected_crime['Murder_per100000'].astype(float),
                        locationmode='USA-states',
                        colorscale = scl,
                        text = df_sected_crime['text'],
                        )
    
    data_slider.append(data_one_year)  # I add the dictionary to the list of dictionaries for the slider
    


Next, I create the 'steps' and the 'slider' objects:

##  I create the steps for the slider
steps = []
for i in range(len(data_slider)):
    step = dict(method='restyle',
                args=['visible', [False] * len(data_slider)],
                label='Year {}'.format(i + 1960)) # label to be displayed for each step (year)
    step['args'][1][i] = True
    steps.append(step)

##  I create the 'sliders' object from the 'steps' 
sliders = [dict(active=0, pad={"t": 1}, steps=steps)]  


Finally, we create the layout, and plot it (both on the notebook and as a separate browser window):


# I set up the layout (including slider option)
layout = dict(geo=dict(scope='usa',
                       projection={'type': 'albers usa'}),
              sliders=sliders)

# I create the figure object:
fig = dict(data=data_slider, layout=layout) 

# to plot in the notebook
plotly.offline.iplot(fig)

# to plot in a separete browser window
offline.plot(fig, auto_open=True, image = 'png', image_filename="map_us_crime_slider" ,image_width=2000, image_height=1000, 
              filename='/your_path/map_us_crime_slider.html', validate=True


And that's it! See how it looks:




As we slide from one year to the next, we observe how the South-North differences get less accentuated. Note also that the color scale is relative to each year (that is something I'll fix later on).

Comments

Dustin said…
Hi Julia,

Great post. I've used your instructions to create my own plotly map with a slider. I'm really curious about the last sentence regarding the color scale. Have you found a way to set a specific color scale that doesn't change each year? I've spent the past few days looking for an answer with no luck.

Thanks again for your post.

-Dustin
Hi, Dustin! Thank you for reading! :)

Yeah, I struggle with Plotly's color scale stubbornness quite a bit myself :(

I haven't been able to fix the colors to compare across different years...
I ended up going in a different direction: when plotting crime at the city level, I used colors for grouping cities by yearly crime ranking (at least, that is comparable across years).

You can check out my notebook here:
https://github.com/juliettapc/US_crime/blob/master/City_crime_ranked_with_slider.ipynb

And the dataset here:
https://doi.org/10.21985/N2R193

Thanks again for your interest!

Julia.

Ganesh said…
Hi Julia,

I really liked this post. Would you be able to tell me how you went about embedding the interactive plotly plot into your website?
Alan C said…
Thank you for helping me get into the scary world of interactive Plotly! Great post
Hey, Ganesh!
If you upload your interactive plot to plotly, then it provides you a link for embedding. The downside is that the free Plotly account only allows you to save a small number of plots.
Anonymous said…
Hello Julia

Do you know if there is a way to produce a map like this in plotly r studio?

Thanks

Lael