Meet Plotly Mapbox. Best Choice for Geographic Data Visualization | by Kefei Mo | Towards Data Science

I have come across several projects that include geographic data, and I have been searching for the best tool for geo-visualization. As a Python user, I have tried Pyplot’s basemap, GeoPandas, Folium, and Bohem, but none of them has given me a satisfying experience.
Finally, my search ended after I found Plotly. To be more specific, Plotly’s Mapbox package. Today in this post, I will demonstrate a quick start for geographic data visualization using Plotly Mapbox and show it why you should consider using it as well.
Visually appealing. You have to try hard to make it look ugly 😜.
Choose the granularity of control. You have broad freedom to control the visualization effects.
Interactive is fun. It can be handy for both EDA and dashboard presentations.
Varies product lines in different languages (R, Python, Javascript), Stable released versions.
Not very steep learning rate. Arguably, but judge by yourself.
Eyes on bigdata. With datashader, Plotly can handle data visualization with millions of rows.
Open-source. It is FREE!!!
Come from my experience, if
you are a Python user
you are familiar with data visualization tools, like plt, sns
you need interactive data visualization, or you want to try it
you like free stuff
then, Plotly Maxbox is most likely the right choice for you.
I would like to mention one more thing about interactive data visualization before we jump into the code. Some people think interactive data visualization is nothing more about the cool effect, and it is solely for presentation. For other applications, like exploratory data analysis (EDA), a static plot should be sufficient.
However, for geo-visualization, either a data scientist or an audience often needs to change the scale of the map (for example, by zoom in and out) to understand the information fully; a static plot is just not enough.
Without further ado, let’s make geo-visualization.
# import packagesimport pandas as pdimport plotly.express as pximport numpy as np# get dataurl = 'https://raw.githubusercontent.com/kefeimo/DataScienceBlog/master/2.geo_plot/df_mapbox_demo.csv'df_plot_tmp = pd.read_csv(url)df_plot_tmp.head()# two-line codefig = px.scatter_mapbox(df_plot_tmp, lat="latitude", lon="longitude", color="gender", zoom=3, mapbox_style='open-street-map')fig.show()
TIPS: Click on the ‘EDIT CHART’ button to have a better interactive experience.
In the above example, we used ‘open-street-map’ as the back-end tilemap. (Recall that we defined mapbox_style=’open-street-map’.) But actually, I found the Plotly default Mapbox base map is more appealing. To do that, we need to use a Mapbox access token. (A Mapbox access token would be something like this: pk.eyJ1Ijoibxxxxxxxxxxxxx. Here to apply the Public Access Token for free.)
mapbox_access_token =  'Your token...'px.set_mapbox_access_token(mapbox_access_token)fig = px.scatter_mapbox(df_plot_tmp, lat="latitude", lon="longitude", color="gender", zoom=3,#                        mapbox_style='open-street-map',                       )fig.show()
TIPS: if the token is wrong, you will get an empty plot. (it is there but you are not able to see it.) To solve this problem, double check your token validation or go back to use mapbox_style=’open-street-map’. (also, when using mapbox make sure the internet is connected.)
wrong_token = 'xxx'px.set_mapbox_access_token(wrong_token)fig = px.scatter_mapbox(df_plot_tmp, lat="latitude", lon="longitude", color="gender", zoom=3,#                        mapbox_style='open-street-map',                       )fig.show()
We can further specify the parameters as desired (see how similar it is to seaborn).
Things I think worth to play with are:
color (similar to hue in seaborn)
size
zoom, center, (control the first view when the plot is rendered)
width, height
While things didn’t give me a satisfying outcome is:
text (the scatter just disappear somehow when using this. ‘hover_name’ can be an alternative though)
Notes:
There is no shape control
Also, when you play with color, depends on whether it is continuous or categorical, you might wanna furth color_continuous_scale(similar to palette) or specify category_orders, color_discrete_sequence
When you play with size, you can play with size_max as well
You can choose to open the plot in the notebook (by default) or another window (renderer=’browser’) by setting the renderer.
# px.set_mapbox_access_token(mapbox_access_token)fig = px.scatter_mapbox(df_plot_tmp, lat="latitude", lon="longitude",                         color="gender",                         size='hour', size_max=6,                         category_orders={'gender':list(df_plot_tmp.gender.unique())},                        color_discrete_sequence=['#2b83ba', '#fdae61'],#                         hover_data=1,#                         hover_name='hour',#                         text='hour',                                                zoom=3.3,#                         zoom=2.8,                        center=dict(lon=105, lat=38),                        width=900*1.5, height=500*1.5,)fig.show()fig.show(renderer="browser")
To solidify our understanding, let’s use age_cate as the color specifier.
In this example
See how to specify the category order
See how to specify the color palette(note in scatter_mapbox the color need to specify in hex, so we need to parse the color to hex by some sort)
TIPS: While Plotly has its own palette, but for python, I found the easy way to select a palette is using seaborn sns.color_palette(), all the available pallet can be found in colorbrewer.
from matplotlib.colors import to_heximport seaborn as sns# px.set_mapbox_access_token(mapbox_access_token)fig = px.scatter_mapbox(df_plot_tmp, lat="latitude", lon="longitude", color="age_cate", #                         size="hour", size_max=15,                        category_orders={'age_cate':['A22-', 'A23-26', 'A27-28', 'A29-31', 'A32-40', 'A40+']},                        color_discrete_sequence=[to_hex(c) for c in sns.color_palette('BrBG_r', 6)],                                                  zoom=2.5,                        center=dict(lon=105, lat=38, ),                        width=900, height=600,)fig.show()
Next, play with the continuous/numerical color.
# play with the color: continous/numerical# px.set_mapbox_access_token(mapbox_access_token)fig = px.scatter_mapbox(df_plot_tmp, lat="latitude", lon="longitude", color="hour", #                         size="hour",                        color_continuous_scale=px.colors.cyclical.Twilight,                                                 zoom=2.5,                        center=dict(lon=105, lat=38, ),                       width=900,                        height=600,)fig.show()
So far, you should have mastered the basic stuff of interactive geo data scatter plot using Plotly. Now let’s discuss some common issues and potential roundabouts.
One issue is about overlapping.
Sometimes, overlapping results from the dataset itself. You might have noticed the coordinates data (i.e., latitude, longitude) is at 0.01. When plotting on the tilemap, this easily causes overlapping. Note that there is no jitter option. We might have to add jitter to the data manually. (Somehow, I think jitter is not trivial for interactive scatter visualization since the jitter parameter has to change accordingly as the scale change (e.g., zoom in and zoom out). We might need an adaptive jitter method if were to apply jitter to an interactive plot.)
In my opinion, to overcome overlapping, a ready-to-go solution is to adjust the scatter size (e.g., decrease the size of scatter to mitigate overlapping). But for px.scatter_mapbox, in 4.8.1 Plotly version, we cannot specify size using an int (no matter what says in the documentary). But there are ways to hack that, for instance, create a column in the data frame designated for scatter size.
Try the following code:
‘Hack ’size parameter.
# px.set_mapbox_access_token(mapbox_access_token)fig = px.scatter_mapbox(df_plot_tmp, lat="latitude", lon="longitude",                         color="gender",                         size='size_for_plot', size_max=2,                         category_orders={'gender':list(df_plot_tmp.gender.unique())},                        color_discrete_sequence=['#2b83ba', '#fdae61'],#                         hover_data=1,#                         hover_name='hour',#                         text='hour',                                                zoom=2.8,                        center=dict(lon=105, lat=38),                        width=900, height=600,                        title='modified scatter size')fig.show()
Manually add jitter
# px.set_mapbox_access_token(mapbox_access_token)fig = px.scatter_mapbox(df_plot_tmp, lat="lat_jitter", lon="lon_jitter", color="age_cate", #                         size="size_for_plot", size_max=3,                        category_orders={'age_cate':['A22-', 'A23-26', 'A27-28', 'A29-31', 'A32-40', 'A40+']},                        color_discrete_sequence=[to_hex(c) for c in sns.color_palette('BrBG_r', 6)],                                                  zoom=10,                        center=dict(lon=116.4, lat=39.9, ),                        width=900, height=600,                        title='add jitter')fig.show()
Try Plotly Mapbox, since it renders appealing visualization, it is fun, it is free, it is not hard to start.
px stands for Plotly.express, it is easier to start from there.
How to tackle the problem of overlapping.
Try other Mapbox packages, for instance px.choropleth_mapbox
Take a look at go (‘go’ stands for ‘plotly.graph_objs’)
Try ‘go.Scattermapbox()’, which gives you more control
Here is an example that combines Scattermapbox and dropdown list