I am trying to make a choropleth using folium & geopandas in python. I do receive a Unicode error and can not fix it.
My data frame looks like this:
choropleth_df =
zipcode
color_bin
7557
1
3993
2
4879
3
9788
2
The shapefile I use is from here. I converted this data to a geojson data file!
An example of the choropleth is found here:
The code I am using is:
m = folium.Map(location=[lat, lon], zoom_start=8)
geojson_path = r'C:\xxxxx\CBS-PC4-2020-v1\CBS_pc4_2020_v1.geojson'
folium.Choropleth(
geo_data=geojson_path,
name="choropleth",
data=choropleth_df,
columns=["zipcode", "color_bin"],
key_on="feature.id",
fill_color="YlGn",
fill_opacity=0.7,
line_opacity=0.2,
legend_name="# per zipcode",
).add_to(m)
folium.LayerControl().add_to(m)
m
The error I receive is:
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 140: character maps to
Nou I found out by reviewing this question that the geojson file I created probably uses another unicode, but how can I found out which one it uses?
Thank you in advance.
I created a reproducible example:
import pandas as pd
import geopandas as gpd
import folium
import os
choropleth_dict = {'PC4': [1233, 2879, 3881, 9287], 'color_bin': [3, 2, 3, 1]}
choropleth_df = pd.DataFrame(data= choropleth_dict)
def load_shapes(filename):
gdf_zipcodes = gpd.read_file(filename)
zipcodes = gdf_zipcodes[['PC4', 'geometry']]
zipcodes_in_latlon = zipcodes.to_crs(epsg=4326)
return zipcodes_in_latlon
shapes_filename = r'\path\CBS_pc4_2020_v1.shp' ## here I use the shapefile (from the link)
shapes = load_shapes(shapes_filename)
m = folium.Map(location=[52, 6], zoom_start=8)
folium.Choropleth(
geo_data= shapes,
data = choropleth_df,
columns=['PC4', 'color_bin'],
fill_color='YlOrRd',
nan_fill_color="White", #Use white color if there is no data available for the county
fill_opacity=0.7,
line_opacity=0.2,
legend_name='## per zipcode ', #title of the legend
highlight=True,
line_color='black',
encoding = 'UTF-8').add_to(m)
folium.LayerControl().add_to(m)
m.save('per_zipcode.html')
print("Saved to file .html")
When using only the .shp file, I am not receiving an error, however, I only see a black map, not the choropleth. When using the geojson file the same happens.
I think the reason for the black fill is that the key argument that relates the user data to the geometry is not specified. Therefore, it is necessary to specify the zip code in the geojson property value with 'key_on'. Or you need to specify geo_data as geojson or shapes.__geo_interface__. I have changed some of your data because the zip code did not exist. Finally, you can check the encoding of the file by using pip install chardet library, for example.
import pandas as pd
import geopandas as gpd
import folium
import os
choropleth_dict = {'PC4': [5151, 2585, 3881, 9287], 'color_bin': [3, 5, 3, 6]}
choropleth_df = pd.DataFrame(data= choropleth_dict)
choropleth_df['PC4'] = choropleth_df['PC4'].astype(str)
def load_shapes(filename):
gdf_zipcodes = gpd.read_file(filename)
zipcodes = gdf_zipcodes[['PC4', 'geometry']]
zipcodes_in_latlon = zipcodes.to_crs(epsg=4326)
return zipcodes_in_latlon
shapes_filename = r'./Paht/CBS_pc4_2020_v1.shp'
shapes = load_shapes(shapes_filename)
m = folium.Map(location=[52, 6], zoom_start=8)
folium.Choropleth(
geo_data=shapes,#.__geo_interface__,
data=choropleth_df,
columns=['PC4', 'color_bin'],
key_on='feature.properties.PC4',
fill_color='YlOrRd',
nan_fill_color="White",
fill_opacity=0.9,
line_opacity=0.2,
legend_name='## per zipcode ',
highlight=True,
line_color='black',
encoding = 'UTF-8').add_to(m)
folium.LayerControl().add_to(m)
#m.save('per_zipcode.html')
print("Saved to file .html")
m
Related
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)
I am trying to create a choropleth map using folium. However my code below keeps saying:
ValueError: Cannot render objects with any missing geometries: type features
The code used is:
url='https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-DV0101EN-SkillsNetwork/Data%20Files/Canada.xlsx'
df_can=pd.read_excel(url, sheet_name='Canada by Citizenship', skiprows=range(20), skipfooter=2)
df_can.drop(['AREA','REG','DEV','Type','Coverage'], axis=1, inplace=True)
df_can.rename(columns={'OdName':'Country', 'AreaName':'Continent','RegName':'Region'}, inplace=True)
df_can.columns=list(map(str, df_can.columns))
df_can['Total']=df_can.sum(axis=1)
years=list(map(str, range(1980,2014)))
JSON='https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-DV0101EN-SkillsNetwork/Data%20Files/world_countries.json'
world_geo=pd.read_json(JSON)
world_map=folium.Map(location=[0,0], zoom_start=2)
world_map.choropleth(
geo_data=world_geo,
data=df_can,
columns=['Country', 'Total'],
key_on='feature.properties.name',
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
legend_name='Immigration to Canada'
)
# display map
world_map
Please help me to understand what is going wrong?
I think the cause of the error is that the geojson loading is a data frame. Normally, you would associate the geojson file with the data frame you want to visualize. The format of that association needs to be in dictionary format.
import json
from urllib.request import urlopen
JSON='https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-DV0101EN-SkillsNetwork/Data%20Files/world_countries.json'
with urlopen(JSON) as f:
world_geo = json.load(f)
# world_geo=pd.read_json(JSON)
world_map = folium.Map(location=[0,0], zoom_start=2)
folium.Choropleth(
geo_data=world_geo,
data=df_can,
columns=['Country', 'Total'],
key_on='feature.properties.name',
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
legend_name='Immigration to Canada'
).add_to(world_map)
# display map
world_map
I have this code:
import pandas as pd
import numpy as np
from geopandas import GeoDataFrame
import geopandas
from shapely.geometry import LineString, Point
import matplotlib.pyplot as plt
import contextily
''' Do Something'''
df = start_stop_df.drop('track', axis=1)
crs = {'init': 'epsg:4326'}
gdf = GeoDataFrame(df, crs=crs, geometry=geometry)
ax = gdf.plot()
contextily.add_basemap(ax)
ax.set_axis_off()
plt.show()
Basically, this generates a background map that is in Singapore. However, when I run it, I get the following error: HTTPError: Tile URL resulted in a 404 error. Double-check your tile url:http://tile.stamen.com/terrain/29/268436843/268435436.png
However, it still produces this image:
How can I change the Tile URL? I would still like to have the map of Singapore as the base layer.
EDIT:
Also tried including this argument to add_basemap:
url ='https://www.openstreetmap.org/#map=12/1.3332/103.7987'
Which produced this error:
OSError: cannot identify image file <_io.BytesIO object at 0x000001CC3CC4BC50>
First make sure that your GeoDataframe is in Web Mercator projection (epsg=3857). Once your Geodataframe is correctly georeferenced, you can achieve this by Geopandas reprojection:
df = df.to_crs(epsg=3857)
Once you have this done, you easily choose any of the supported map styles. A full list can be found in contextily.sources module, at the time of writing:
### Tile provider sources ###
ST_TONER = 'http://tile.stamen.com/toner/tileZ/tileX/tileY.png'
ST_TONER_HYBRID = 'http://tile.stamen.com/toner-hybrid/tileZ/tileX/tileY.png'
ST_TONER_LABELS = 'http://tile.stamen.com/toner-labels/tileZ/tileX/tileY.png'
ST_TONER_LINES = 'http://tile.stamen.com/toner-lines/tileZ/tileX/tileY.png'
ST_TONER_BACKGROUND = 'http://tile.stamen.com/toner-background/tileZ/tileX/tileY.png'
ST_TONER_LITE = 'http://tile.stamen.com/toner-lite/tileZ/tileX/tileY.png'
ST_TERRAIN = 'http://tile.stamen.com/terrain/tileZ/tileX/tileY.png'
ST_TERRAIN_LABELS = 'http://tile.stamen.com/terrain-labels/tileZ/tileX/tileY.png'
ST_TERRAIN_LINES = 'http://tile.stamen.com/terrain-lines/tileZ/tileX/tileY.png'
ST_TERRAIN_BACKGROUND = 'http://tile.stamen.com/terrain-background/tileZ/tileX/tileY.png'
ST_WATERCOLOR = 'http://tile.stamen.com/watercolor/tileZ/tileX/tileY.png'
# OpenStreetMap as an alternative
OSM_A = 'http://a.tile.openstreetmap.org/tileZ/tileX/tileY.png'
OSM_B = 'http://b.tile.openstreetmap.org/tileZ/tileX/tileY.png'
OSM_C = 'http://c.tile.openstreetmap.org/tileZ/tileX/tileY.png'
Keep in mind that you should not be adding actual x,y,z tile numbers in your tile URL (like you did in your "EDIT" example). ctx will take care of all this.
You can find a working copy-pastable example and further info at GeoPandas docs.
import contextily as ctx
# Dataframe you want to plot
gdf = GeoDataFrame(df, crs= {"init": "epsg:4326"}) # Create a georeferenced dataframe
gdf = gdf.to_crs(epsg=3857) # reproject it in Web mercator
ax = gdf.plot()
# choose any of the supported maps from ctx.sources
ctx.add_basemap(ax, url=ctx.sources.ST_TERRAIN)
ax.set_axis_off()
plt.show()
Contextily's default crs is epsg:3857. However, your data-frame is in different CRS. Use the following,refer the manual here:
ctx.add_basemap(ax, crs='epsg:4326', source=ctx.providers.Stamen.TonerLite)
Please, refer to this link for using different sources such as Stamen.Toner, Stamen.Terrain etc. (Stamen.Terrain is used as default).
Also, you can cast your data frame to EPSG:3857 by using df.to_crs(). In this case, you should skip crs argument inside ctx.add_basemap() function.
im too new to add a comment but I wanted to point out to those saying in the comments that they get a 404 error. Check you capitaliations, etc. Stamen's urls are specifc on this. For instance there is not an all caps call. It is only capitalize the first letter. For example:
ctx.add_basemap(ax=ax,url=ctx.providers.Stamen.Toner, zoom=10)
I have created a geoDataFrame using, and would like to create a Folium Map, plotting the population eat for each country. Do I have to create the Json file, or I can directly use the geoDataFrame file?
import folium
import fiona
import geopandas as gpd
world = fiona.open(gpd.datasets.get_path('naturalearth_lowres'))
world = gpd.GeoDataFrame.from_features([feature for feature in world])
world = world[(world.pop_est > 0) & (world.name != "Antarctica")]
I used folium.map and geojson function, but it failed to create correct JSON files.
Thanks for the help!
The m.cholopleth() code in #joris's answer is now deprecated. The following code produces the same result using the new folium.Chloropleth() function:
m = folium.Map()
folium.Choropleth(world, data=world,
key_on='feature.properties.name',
columns=['name', 'pop_est'],
fill_color='YlOrBr').add_to(m)
folium.LayerControl().add_to(m)
m
In recent releases of folium, you don't need to convert the GeoDataFrame to geojson, but you can pass it directly. Connecting the population column to color the polygons is still somewhat tricky to get correct:
m = folium.Map()
m.choropleth(world, data=world, key_on='feature.properties.name',
columns=['name', 'pop_est'], fill_color='YlOrBr')
m
I am trying to plot a map of the US and mark the various cities across the country. I got the map to work. But I am having two issues: the first is, I am getting this error message:
AttributeError: 'NoneType' object has no attribute 'longitude'
Secondly, I have tried to enlarge the graph using the plt.figsize attribute however my map still stays the same size.
Lastly, this is not really an issue but what if i wanted to label the dots with the city names How can i do so?
Here is my code for the map:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from geopy.geocoders import Nominatim
import math
city_list = list(flight_data["OriginCityName"].unique())
cities = city_list
scale = 1
map = Basemap(width=10000000,height=6000000,projection='lcc',
resolution=None,lat_1=45.,lat_2=55,lat_0=50,lon_0=-107.)
plt.figure(figsize=(19,20))
map.bluemarble()
# Get the location of each city and plot it
geolocator = Nominatim()
for city in cities:
loc = geolocator.geocode(city)
if not loc:
print("Could not locate {}".format(city))
continue
x, y = map(loc.longitude, loc.latitude)
map.plot(x,y,marker='o',color='Red',markersize=5)
plt.annotate(city, xy = (x,y), xytext=(-20,20))
plt.show()
I guess there is something in your city_list Nominatim can't resolve. I added a check for that below.
You have to call figure(num=1,figsize=(8,9)) before you plot anything (here: the map).
You can use plt.annotate, see below.
Hope this helps.
for city in cities:
loc = geolocator.geocode(city)
if not loc:
print("Could not locate {}".format(city))
continue
x, y = map(loc.longitude, loc.latitude)
map.plot(x,y,marker='o',color='Red',markersize=int(math.sqrt(count))*scale)
plt.annotate(city, xy = (x,y), xytext=(-20,20))