folium Essentials¶
Interactive Leaflet maps from Python. folium makes point markers, choropleths, and heatmaps easy to build and share as HTML — ideal for location-based data science results.
Table of Contents¶
- Installation / Setup
- Core Syntax
- Common Operations
- Practical Examples
- Common Patterns
- Best Practices
- Common Mistakes
- Troubleshooting
- Related Cheat Sheets
- Official Documentation
Installation / Setup¶
folium renders inline in Jupyter; in scripts it produces an HTML file you open in a browser. No API keys needed for OpenStreetMap tiles.
Core Syntax¶
Create a map¶
m = folium.Map(location=[51.5072, -0.1276], # [lat, lng]
zoom_start=10,
tiles="OpenStreetMap", # or "CartoDB positron", "CartoDB dark_matter", "Stamen Terrain"
width="100%", height="70%",
control_scale=True)
m
# or save: m.save("map.html")
Markers & popups¶
folium.Marker(
location=[51.5, -0.12],
popup=folium.Popup("<b>Office</b><br>Open 9-5", max_width=250),
tooltip="Click me",
icon=folium.Icon(color="red", icon="info-sign"), # green, red, blue, orange...
).add_to(m)
folium.CircleMarker(
location=[51.51, -0.13], radius=8,
color="crimson", fill=True, fill_color="crimson",
popup="circle",
).add_to(m)
Marker clusters & heatmaps (plugins)¶
cluster = plugins.MarkerCluster().add_to(m)
for lat, lng, name in zip(df["lat"], df["lng"], df["name"]):
folium.Marker([lat, lng], popup=name).add_to(cluster)
plugins.HeatMap(df[["lat", "lng", "count"]].values, radius=15).add_to(m)
Layer control¶
folium.TileLayer("CartoDB positron").add_to(m)
folium.LayerControl().add_to(m) # toggle tile layers / overlays in the corner
Common Operations¶
Choropleth (fill regions by a value)¶
# geo_data: GeoJSON with region shapes; data: DataFrame with the value
folium.Choropleth(
geo_data="world.geojson", # path/URL to GeoJSON
data=df, # DataFrame
columns=["country", "value"], # key column + value column
key_on="feature.properties.name", # GeoJSON property matching the key
fill_color="YlOrRd", # colormap
fill_opacity=0.7, line_opacity=0.2,
legend_name="value",
highlight=True,
).add_to(m)
# tooltips show per-region values when hovering
folium.features.GeoJsonTooltip(fields=["name"], aliases=["Country"]).add_to(...)
Polylines & shapes¶
folium.PolyLine([[51.5, -0.12], [51.51, -0.13], [51.52, -0.11]],
color="blue", weight=3, opacity=0.8).add_to(m)
folium.Polygon([[51.5, -0.12], [51.51, -0.13], [51.52, -0.11]],
color="green", fill=True).add_to(m)
Fullscreen & search (handy for reports)¶
Save & embed¶
Practical Examples¶
1. Plot store locations with revenue popups¶
import folium
import pandas as pd
stores = pd.read_csv("stores.csv") # columns: name, lat, lng, revenue
m = folium.Map(location=[stores["lat"].mean(), stores["lng"].mean()], zoom_start=6)
for _, s in stores.iterrows():
folium.Marker(
[s["lat"], s["lng"]],
popup=f"<b>{s['name']}</b><br>Revenue: ${s['revenue']:,.0f}",
icon=folium.Icon(color="green" if s["revenue"] > 100_000 else "orange"),
).add_to(m)
m.save("stores.html")
2. Density heatmap of customer activity¶
heat = plugins.HeatMap(
df[["lat", "lng"]].values, radius=12, blur=10, min_opacity=0.3
)
heat.add_to(m)
folium.LayerControl().add_to(m)
3. Choropleth of values by country¶
folium.Choropleth(
geo_data="https://raw.githubusercontent.com/python-visualization/folium/main/examples/data/world-countries.json",
data=df, columns=["country", "metric"],
key_on="feature.properties.name",
fill_color="BuPu", legend_name="metric",
).add_to(m)
Common Patterns¶
# Map centered on data bounds (automatically)
m = folium.Map(location=[df.lat.mean(), df.lng.mean()], zoom_start=5)
# Fit map to markers
m.fit_bounds([[df.lat.min(), df.lng.min()], [df.lat.max(), df.lng.max()]])
# Styled popup from a row
folium.Marker([lat, lng],
popup=folium.Popup(f"{name} — {value:.1f}", show=False),
tooltip=name).add_to(m)
# Always keep base map + data layers separable for LayerControl
Best Practices¶
- Confirm lat/lng order — folium uses
[latitude, longitude], the reverse of many GeoJSON tools. - Cluster many markers (
MarkerCluster) — hundreds of raw markers slow the map. - Use light tiles (
CartoDB positron) when overlaying heatmaps/choropleths for contrast. - Keep
key_onconsistent — choropleth joins on GeoJSON property names, which must match yourcolumnsvalues exactly. - Export
m.save("map.html")for sharing; notebooks render inline automatically. - For heavy spatial analysis (joins, CRS), hand off to geopandas.md; folium is for display.
Common Mistakes¶
| Mistake | Consequence | Correct approach |
|---|---|---|
[lng, lat] ordering |
Markers in the ocean | Always [lat, lng] |
| Key/value name mismatch in choropleth | Regions silently unfilled | Check GeoJSON property names vs DataFrame values |
| Raw markers for 10k points | Frozen browser | MarkerCluster or HeatMap |
| Choropleth legend in %/USD unclear | Misleading map | legend_name with units; check scale |
Missing fill=True on circles |
Invisible fills | Set fill=True, fill_color=... |
GeoJson vs Choropleth confusion |
No value mapping | Choropleth for value-filled regions |
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
| Blank map in notebook | Tiles blocked (offline/network) | Check internet; try a different tiles provider |
| Choropleth all one color | key_on doesn't match data keys |
Print one GeoJSON feature.properties to inspect |
| HeatMap values ignored | Wrong column order | Pass [lat, lng, weight] columns in that order |
| Map too tall/short | Default sizing | Set width/height (e.g. "100%", 600) |
| Save produces empty HTML | m.save before adding layers |
Call save after all .add_to(m) |
Related Cheat Sheets¶
- geopandas.md — spatial data handling before mapping
- pandas.md — preparing lat/lng tables
- matplotlib.md — static map exports via GeoPandas + Matplotlib
- Data Visualization category index · Home · Index
Official Documentation¶
Last Updated¶
2026-09-06 — Initial version.