Folium Choropleth map marker is not working - python

I'm trying to follow the blog post from Domino lab, Creating interactive crime maps with Folium. And I found that the code base is too old to run the Folium's Choropleth map marker. Although older version on Domino platform seems working (2015), the latest Ipython notebook doesn't work. So I'm guessing Folium changed something on markers? I tried to find the update but I can't find it. Are anyone familiar with this library? If so please give me advices.
My code below:
from IPython.display import HTML
def display(m, height=500):
"""Takes a folium instance and embed HTML."""
m._build_map()
srcdoc = m.HTML.replace('"', '"')
embed = HTML('<iframe srcdoc="{0}" '
'style="width: 100%; height: {1}px; '
'border: none"></iframe>'.format(srcdoc, height))
return embed
import folium
import pandas as pd
SF_COORDINATES = (37.76, -122.45)
crimedata = pd.read_csv('data/SFPD_Incidents_-_Current_Year__2015_.csv')
#for speed purposes
MAX_RECORDS = 1000
#create empty map zoomed in on San Francisco
map = folium.Map(location=SF_COORDINATES, zoom_start=12)
#add a marker for every record in the filtered data, use a clustered view
for each in crimedata[0:MAX_RECORDS].iterrows():
map.simple_marker(
location = [each[1]['Y'],each[1]['X']],
clustered_marker = True)
display(map)
#definition of the boundaries in the map
district_geo = r'data/sfpddistricts.json'
#calculating total number of incidents per district
crimedata2 = pd.DataFrame(crimedata['PdDistrict'].value_counts().astype(float))
crimedata2.to_json('data/crimeagg.json')
crimedata2 = crimedata2.reset_index()
crimedata2.columns = ['District', 'Number']
#creation of the choropleth
map1 = folium.Map(location=SF_COORDINATES, zoom_start=12)
map1.geo_json(geo_path = district_geo,
data_out = 'data/crimeagg.json',
data = crimedata2,
columns = ['District', 'Number'],
key_on = 'feature.properties.DISTRICT',
fill_color = 'YlOrRd',
fill_opacity = 0.7,
line_opacity = 0.2,
legend_name = 'Number of incidents per district')
display(map1)

Not sure if you mean markers (popups) or the choropleth method itself isn't working?
The map1.geo_json() method is deprecated (see here).
Instead, try map1.choropleth(geo_path = district_geo,
data_out = 'data/crimeagg.json',
data = crimedata2,
columns = ['District', 'Number'],
key_on = 'feature.properties.DISTRICT',
fill_color = 'YlOrRd',
fill_opacity = 0.7,
line_opacity = 0.2,
legend_name = 'Number of incidents per district')
The map.choropleth method worked for me, but don't know if they fixed the popup issue for choropleth maps. Hope this helps!

The mapObject.choropleth method is being depricated.
folium.GeoJson is the suggested method as per this github issue: https://github.com/python-visualization/folium/issues/589
A comment in that issue links to this example, which shows how to build the choropleth:
http://nbviewer.jupyter.org/github/python-visualization/folium/blob/master/examples/GeoJSON_and_choropleth.ipynb?flush_cache=true
TLDR
replace geo_json with GeoJson
and for the args like fill_color, use fillColor: <hex_color> in the style_function dictionary kwarg.

Related

Folium Color Issues

I'm working with Folium for the first time, and attempting to make a Choropleth map of housing values in North Carolina using Zillow data as the source. I've been running into lots of issues along the way, and right now I'm a bit stuck on how to add in colors to the map; if the property value is >100k make it green, and slowing increasing the gradient to orange if it's <850k.
At the moment the map does generate the zip code data fine, but all of the polygons are a black-grey color. It's also not showing a color key or map name, and I have a feeling some of my earlier code could be off.
import folium
import pandas as pd
import requests
import os
working_directory = os.getcwd()
print(working_directory)
path = working_directory + '/Desktop/NCHomes.csv'
df = pd.read_csv(path)
df.head()
df['Homes'].min(), df['Homes'].max()
INDICATOR = 'North Carolina Home Values by Zip Code'
data = df[df['RegionName'] == INDICATOR]
max_value = data['Homes'].max()
data = data[data['Homes'] == max_value]
data.head()
geojson_url = 'https://raw.githubusercontent.com/OpenDataDE/State-zip-code-GeoJSON/master/nc_north_carolina_zip_codes_geo.min.json'
response = requests.get(geojson_url)
geojson = response.json()
geojson
geojson['features'][0]
map_data = data[['RegionName', 'Homes']]
map_data.head()
M = folium.Map(location=[20, 10], zoom_start=2)
folium.Choropleth(
geo_data=geojson,
data=map_data,
columns=['RegionName', 'Homes'],
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
legend_name=INDICATOR
).add_to(M)
M
You can specify the threshold_scale parameter as follows:
folium.Choropleth(
geo_data=geojson,
data=map_data,
columns=['RegionName', 'Homes'],
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
threshold_scale=[100000, 850000],
legend_name=INDICATOR
).add_to(M)

Altair choropleth map visualization issue

I'm quite the beginner, so it might be something obvious.
I tried making a choropleth map, using this guide with Python's Altair package.
All code runs fine, except the very last one. Basically the Altair chart titled "choro" that should "fill" the administrative divisons on my map with colours, doesn't do anything. The resulting map if I overlay it on the background, is identical to the background itself, except for a bit different border strokes. I do not need labels, so I left out that one.
This is my code to be precise:
choro = alt.Chart(choro_data).mark_geoshape(
fill='lightgray',
stroke='black'
).encode(
alt.Color('properties.unemp',
type='quantitative',
scale=alt.Scale(scheme='bluegreen'),
title = "Unemployment by county")
).project(
type= 'mercator',
scale= 2000,
center= [20,47],
clipExtent= [[0, 0], [400, 300]]
)
The type of 'unemp' is float, if it is relevant.
What is the problem? Is the guide dated? Do I miss something obvious?
have used California geometry and unemployment data
two issues I found with altair
skip fill='lightgray'
projection generates errors whenever passing anything other than type
import geopandas as gpd
import pandas as pd
import requests
import altair as alt
# california counties
gdf = gpd.read_file(
"https://raw.githubusercontent.com/codeforgermany/click_that_hood/main/public/data/california-counties.geojson"
)
# california un employment
df = pd.json_normalize(
requests.get("https://data.edd.ca.gov/resource/e6gw-gvii.json").json()
).loc[lambda d: d["area_type"].eq("County") & d["seasonally_adjusted_y_n"].eq("N")]
# make names consistent between geometry and unemployment data
df["name"] = df["area_name"].str.split(" ").apply(lambda l: " ".join(l[0:-1]))
df["unemp"] = pd.to_numeric(df["unemployment_rate"])
# join geometry and unemployment data
gdf = gdf.merge(df, on="name")
geojson = gdf.loc[:, ["name", "unemp", "geometry"]].__geo_interface__
choro_data = alt.Data(values=geojson["features"])
choro = (
alt.Chart(choro_data)
.mark_geoshape(stroke="black")
.encode(
alt.Color(
"properties.unemp",
type="quantitative",
scale=alt.Scale(scheme="bluegreen"),
title="Unemployment by county",
)
)
.project(
type="mercator",
# scale=2000, center=[20, 47], clipExtent=[[0, 0], [400, 300]]
)
)
choro

Overlaying traces in Mapbox for Python

I am currently working on a Geography project, for which I have to do some research on migration flows.
I want to represent migration flows using Python and Mapbox, based on a worldwide GeoJSON I previously downloaded. However, I am having some issues regarding the quality of the work, and can't find a proper solution.
I first uploaded the world GeoJSON:
countries = json.load(open("countries_without_antartica.geojson"))
I then extracted the coordinates with a function and grouped them into a list named countries_coords, with countries_lons, countries_lats = zip(*countries_coords).
I then start creating the figure.
Firstly, I initiate it:
fig = go.Figure()
Then, I put the information I extracted before into a ScatterMapbox environment:
fig.add_trace(go.Scattermapbox(
mode='lines',
name='Countries',
fill='toself',
fillcolor='lightgray',
line=dict(color='black', width=1),
lat=countries_lats,
lon=countries_lons,
opacity=1,
showlegend=False,
hoverinfo='skip',
))
I then specify the Mapbox style with: fig.update_layout(mapbox=dict(style='white-bg'))
That leaves the map with the GeoJSON data alone, as seen in this image:
The problem, however, starts right here: I then try to add a line to the map, indicating the first migration flow (in this case, from Spain to Australia). I do this with the following code:
fig.add_trace(
go.Scattermapbox(
name='flow1',
lon = [134.340916, -3.704239],
lat = [-25.039402, 40.415887],
mode = 'lines',
line = dict(width = 8,color = 'green')
)
)
However, the resulted figure is this:
I have several problems with that, as the migration flow line should be a somewhat curved line and not a straight one.
I realized the solution to THAT (and only THAT) problem was to use go.Scattergeo instead of go.Scattermapbox to represent the line, and so I did:
fig.add_trace(
go.Scattergeo(
name='flow1',
lon = [134.340916, -3.704239],
lat = [-25.039402, 40.415887],
mode = 'lines',
line = dict(width = 8,color = 'green')
)
)
BUT the line is now "behind" the map itself, so it is not visible (resulting in IMAGE 1 again).
The line with go.Scattergeo IS curved, and it DOES represent what I wanted it to represent, but it is not visible because it is "layered" behind the go.ScatterMapbox figure with the map.
How can I change the order of the traces? Is there a way to prevent the first trace from being "above" the second trace? I tried changing the order of appearance, but nothing worked.
EDIT 1
Following the solutions provided by #NikolasStevenson-Molnar and #BasvanderLinden, I rendered both the world and the migration flow by using go.Scattergeo. Code here:
fig.add_trace(go.Scattergeo(
mode='lines',
name='Countries',
fill='toself',
fillcolor='lightgray',
line=dict(color='black', width=1),
lat=countries_lats,
lon=countries_lons,
opacity=1,
showlegend=False,
hoverinfo='skip',
))
fig.add_trace(
go.Scattergeo(
name='flow1',
lon = [134.340916, -3.704239],
lat = [-25.039402, 40.415887],
mode = 'lines',
line = dict(width = 8,color = 'green')
)
)
Here, the result:
As you can see, the map is not as "great" as it should be. Some issues regarding it's quality are:
The countries are filled with the same colors as the background (i.e. the oceans). I cannot find a way to fill only de countries. While using go.Scattermapbox this was easily done by specifying the desired style (fig.update_layout(mapbox=dict(style='white-bg'))). However, 'go.Scattergeo' does not have that functionality.
The map seems to be outstretched horizontally (see how all the countries are way more wide in IMAGE 3 compared to IMAGE 1). This is particularly visible in the northern hemisphere.
It then occurred to me that issue 1 should be solved by "turning off" the filling atributes, so I coded:
fig.add_trace(go.Scattergeo(
mode='lines',
name='Countries',
line=dict(color='black', width=1),
lat=countries_lats,
lon=countries_lons,
opacity=1,
showlegend=False,
hoverinfo='skip',
))
Result is, again, not desirable, because the GeoJSON is plot above the default map that 'go.Scattergeo` provides. For example, when I zoom in into Spain, I get: Clearly, the two traces (default and GeoJSON) are operating at the same time, making the final result not-so-tidy. On top of that, the default trace just shows "territory", but not "political division", so -for example- Portugal is not drawn in the default trace but it is in the GeoJSON.
Hope this extra information is valuable to reach a proper solution.
Thank you in advance, for any help, advice, or solution you might give me.
you can go back to basics and calculate your own great circle line
https://geographiclib.sourceforge.io/html/python/examples.html#basic-geodesic-calculations has an example of how to achieve this
bringing it together
source countries GeoJSON and create a geopandas dataframe
use centroid capability to have data for the centre of a country
build utility function to calculate a great circle trace
finally show it in action with lines between three pairs of countries
import requests
import geopandas as gpd
import plotly.express as px
from pathlib import Path
from zipfile import ZipFile
import json, io
from geographiclib.geodesic import Geodesic
import math
# source geojson for country boundaries so we can calc centroids
geosrc = pd.json_normalize(
requests.get(
"https://pkgstore.datahub.io/core/geo-countries/7/datapackage.json"
).json()["resources"]
)
fn = Path(geosrc.loc[geosrc["name"].eq("geo-countries_zip"), "path"].values[0]).name
if not Path.cwd().joinpath(fn).exists():
r = requests.get(
geosrc.loc[geosrc["name"].eq("geo-countries_zip"), "path"].values[0],
stream=True,
)
with open(fn, "wb") as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)
zfile = ZipFile(fn)
with zfile.open(zfile.infolist()[0]) as f:
geojson = json.load(f)
gdf = gpd.GeoDataFrame.from_features(geojson).set_index("ISO_A3")
# centroids...
gdf["lon"] = gdf.apply(lambda r: r.geometry.centroid.x, axis=1)
gdf["lat"] = gdf.apply(lambda r: r.geometry.centroid.y, axis=1)
def worldcircleline(gdf, country1, country2, fig=None, color="blue"):
geod = Geodesic.WGS84 # define the WGS84 ellipsoid
l = geod.InverseLine(
gdf.loc[country1, "lat"],
gdf.loc[country1, "lon"],
gdf.loc[country2, "lat"],
gdf.loc[country2, "lon"],
Geodesic.LATITUDE | Geodesic.LONGITUDE,
)
da = 1
n = int(math.ceil(l.a13 / da))
da = l.a13 / n
lat = [
l.ArcPosition(
da * i, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL
)["lat2"]
for i in range(n + 1)
]
lon = [
l.ArcPosition(
da * i, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL
)["lon2"]
for i in range(n + 1)
]
tfig = px.line_mapbox(
lat=lat,
lon=lon,
mapbox_style="carto-positron",
zoom=1,
).update_traces(line={"color":color})
if fig is None:
return tfig.update_layout(margin={"l": 0, "r": 0, "b": 0, "t": 0})
else:
return fig.add_traces(tfig.data)
fig = worldcircleline(gdf, "ESP", "AUS")
worldcircleline(gdf, "GBR", "SGP", fig=fig, color="red")
worldcircleline(gdf, "IRL", "USA", fig=fig, color="green")

How to get data about state which is currently hovered? Plotly Choropleth - USA map

I created an interactive map of individual US states. The map will contain information on electric vehicles in the US. Currently, it is colored depending on the range (average in kilometers) of a given vehicle.
Here is my code:
import plotly.graph_objects as go
import pandas as pd
df = pd.read_csv('https://gist.githubusercontent.com/AlbertKozera/6396b4333d1a9222193e11401069ed9a/raw/ab8733a2135bcf61999bbcac4f92e0de5fd56794/Pojazdy%2520elektryczne%2520w%2520USA.csv')
for col in df.columns:
df[col] = df[col].astype(str)
df['range'] = pd.to_numeric(df['range'])
df_range = df.drop(columns = ['state', 'brand', 'model', 'year of production', 'type']).groupby('code', as_index=False)
df_range_mean = df_range.agg({'range':'mean'})
fig = go.Figure(data=go.Choropleth(
locations=df['code'].drop_duplicates(keep='first').reset_index(drop=True),
z = round(df_range_mean['range'], 2),
locationmode='USA-states',
colorscale='Reds',
autocolorscale=False,
marker_line_color='black',
))
fig.update_layout(
geo = dict(
scope='usa',
projection=go.layout.geo.Projection(type = 'albers usa'),
showlakes=True, # lakes
lakecolor='rgb(255, 255, 255)'),
)
fig.show()
It looks like this:
Here is my question:
I need to dynamically return information about the given state in which the mouse cursor is currently located. Unfortunately, I don't know how to do it and whether it is possible at all. I have to implement a method that will display a different image (chernoff face) depending on what state is currently highlighted by the user.
Can anyone tell me if there is any method that will return data about the currently highlighted state? Or maybe, unfortunately - I will have to write my own listener.
I was searching such a method in documentation but I couldn't find it.
The argument locations=df['state'] into the go.Choropleth function should return the abbreviation of the state you refer whenever you point the cursor.

Python: marker label on the map using gmap.marker_layer option not working using hover_text

I am trying to label the markers i am plotting on google maps using gmap. The markers are plotted but the label (their name) are not. I using hover_text option for name to show as i hover over the marker.
Its a simple file with 3 columns --> name, lat, long.. Below is the code. Im running it on jupyter notebook
import gmaps
locations = df[['latitude', 'longitude']]
name = list(map(str, list(df["name"])))
fig = gmaps.figure(map_type = "TERRAIN", center = (-34, -59), zoom_level = 2)
markers = gmaps.marker_layer(locations, hover_text = name)
fig.add_layer(markers)
gmaps.configure(api_key = 'MY API KEY')
fig
The map plots correctly except no label.
Thanks
File:
name latitude longitude
123 -34.000000 -59.166672
124 -32.233330 -64.433327
125 -40.166672 44.133331
126 -51.216671 5.083330
127 -51.333328 4.250000
I am not sure how your data is laid out, but I have encountered a similar problem which I resolved by running the following code:
name = [name['name'] for name in marker_locations]
My data is laid in the following way:
marker_locations = [
{'name':'Hamburg, Germany',.....},
{'name':'London, England',.....},
]
Hope this helps.
The documentation gives an example on what you want:
marker_layer = gmaps.marker_layer(
locations,
hover_text=['Atucha', 'Embalse', 'Armenia', 'Br']

Categories