Create interactive maps of GIS data with tooltips, hover text, and legends in one short and simple line of code. Plus get extras like side-by-side sliders and multiple maps in sync.
R has some excellent mapping options. There is the leaflet R package, which I use when I want to do a lot of customization. There is tmap, which I like for its nice balance between power and ease of use. And recently Iโve started also using mapview.
mapviewโs raison dโรชtre is exploratory visualization โ more specifically, generating useful default maps with very little code. As little code as:
mapview(mydata, zcol = "mycolumn")
One function, two arguments, done. That makes it extremely easy to explore geospatial data, or to create a fast prototype. Plus mapview has a few cool syntax options for viewing multiple maps.
mapview in action
For this demo Iโll use a shapefile of US states and data about population changes by state in the last 20 years.ย If you want to follow along, download the data zip file:
As usual, first a little bit of data prep. The code below loads four packages, downloads a GIS file defining state polygon borders, then joins that with state populations in 2000, 2010, and 2020.ย ย
library(tigris)
library(mapview)
library(dplyr)
library(sf)
us_geo <- tigris::states(cb = TRUE, resolution = '20m')
pop_data <- readr::read_csv("state_population_data.csv")
all_data <- inner_join(us_geo, pop_data, by = c("GEOID" = "GEOID"))
With my data ready, this single line of code is all I need to create an interactive map to explore my data, colored by percent change between 2010 and 2020:
mapview(all_data, zcol = "PctChange10_20")
A screenshot of the default map is shown below, including a pop-up table which appears if you click on a state and hover text which appears if you hover/mouse over a state.ย
Screenshot by Sharon Machlis
Default mapview map generated with one small line of code.
Notice whatโs not in the code that generated this map. I didnโt have to specify in any way that Iโm analyzing polygons or that I want a map colored by polygon;ย mapview() chose defaults based on the type of geospatial file. The codeย mapview(all_data, zcol = "PctChange10_20") is all you need to generate an interactive choropleth map โ including hover text and pop-ups.
The default pop-up includes every field in my data, and it is probably not what Iโd want an end user to see. However, itโs useful for exploring my data. And the pop-up is customizable, which Iโll get to in a bit.ย
If your data set doesnโt have row names, mapview() uses the row number for the pop-up tableโs top row. You canย add row names to your data set with base Rโs row.names() function to get more user-friendly table titles.
By the way, if your table doesnโt look as nicely formatted as the one in this map, try updating GDAL on your system. I updated the rgdal package on my system and it solved a table formatting problem.
More mapview features
If you look very carefully at the top left of the map in the screenshot above, you should see some text showing the longitude and latitude of where my mouse was at the time I captured the image, as well as the leaflet map zoom level. Both of those change as you interact with the map.
This default map also includes scale in kilometers and miles at the bottom left. And, at the bottom right, there is a button with the name of the data set and column. If you move the map around or zoom in or out, clicking that button brings you back to the map starting point.ย
Visualize points with mapview
Adding points to a map is just as easy as polygons. For points, Iโll use a CSV file of state capitals with their longitude and latitude.
In the code below, I use the rio package to read the CSV, but you could use another option such as readr::read_csv(). To use latitude and longitude data for GIS work in R (not only for mapview), you then need to turn the data frame into a spatial object. The sf packageโs st_as_sf() function does this.ย
capitals <- rio::import("us-state-capitals.csv")
capitals_geo <- st_as_sf(capitals, coords = c("longitude", "latitude"),
crs = 4326)
st_as_sf() needs as arguments the data frame, a vector defining which columns have longitude and latitude info, and your desired coordinate reference system, in this case one used by a lot of background map tiles.
Once the data is transformed, I can use it to add a points layer to my map with another call toย mapview():
mapview(all_data, zcol="PctChange10_20") +
mapview(capitals_geo)
I didnโt have to tell mapview that capitals_geo contains points, or which columns hold latitude and longitude data. In fact, once I create my first mapview object, I can add layers to the map without calling mapview() again; I can just use the point objectโs name:
mapview(all_data, zcol = "PctChange10_20") + capitals_geo
The map now looks like this:
Screenshot by Sharon Machlis
Map with polygons and points.
Invoke automatic visualizations
You can also ask mapview to automatically visualize geospatial objects in your R session. The packageโsย startWatching() function creates a map of any sf object you add to or change in your R session after the function is invoked. You can see how it works in the video embedded at the top of this article.ย
Customize R maps with mapview
There are mapview() arguments to customize map options such asย color for polygon boundary lines,ย col.regions for polygon fill colors,ย and alpha.regions for opacity.
You can rename a layer with the layer.name argument if you want a more user-friendly layer name. This appears on the legend, the bottom right button, and when opening the layer button toward the top left.
In this next code block, I change the polygon colors and opacity using the โGreensโ palette from the RColorBrewer package and an opacity of 1 so the polygons are opaque. (Note you will need the RColorBrewer package installed if you want to run this code on your system.)
mapview(all_data, zcol = "PctChange10_20",
col.regions = RColorBrewer::brewer.pal(9, "Greens"),
alpha.regions = 1)
The Greens palette has a maximum of nine discrete colors. mapview complains if you donโt give it a palette with the number of colors it needs, as in the warning below, but it will do the interpolating work for you.
Warning message:
Found less unique colors (9) than unique zcol values (41)!
Interpolating color vector to match number of zcol values.
You can use a diverging palette in your map, too, such as the RdYlGn palette:
mapview(all_data, zcol = "PctChange10_20",
col.regions = RColorBrewer::brewer.pal(11, "RdYlGn"), alpha.regions = 1)
Screenshot by Sharon MachlisThis mapโs dark background appeared automatically, because mapview determined the map included a lot of light colors. You can turn off that basemap behavior with
mapviewOptions(<span class="hljs-string">"basemaps.color.shuffle"</span> = <span class="hljs-literal">FALSE</span>)
Visualize two maps together
Now to a couple of those cool syntax options I mentioned at the beginning. Here Iโm creating two maps, one for the 2010 to 2020 population change and the other for 2000 to 2010:
map2020 <- mapview(all_data, zcol = "PctChange10_20",
col.regions = RColorBrewer::brewer.pal(9, "Greens"), alpha.regions = 1,
layer.name = "Pct change 2010-2020"
)
map2010 <- mapview(all_data, zcol = "PctChange00_10",
col.regions = RColorBrewer::brewer.pal(9, "Greens"), alpha.regions = 1,
layer.name = "Pct change 2000-2010"
)
You can place the maps side by side and have them move in sync with the leafsync package and the sync() function.
library(leafsync)
sync(map2010, map2020)
Screenshot by Sharon Machlis
These two maps pan, zoom, and move in sync together.
Or, you can put two maps on the same layer and have a side-by-side slider to compare the two, thanks to the leaflet.extras2 package and the | (Unix pipe, not R pipe) character.
map2010 | map2020
Screenshot by Sharon Machlis
The map slider can move left to right to show either the left or right map version.
Donโt want legends, pop-ups, or hover text on a map? Those can be turned off with
mapview(all_data, zcol = "PctChange10_20",
legend = FALSE, label = FALSE, popup = FALSE)
You can also turn off the background map tiles by using a data setโs custom projection. One case where thatโs useful is if you want a map of the US showing Alaska and Hawaii as insets, instead of where they actually are geographically, for a more compact display.
The first four lines of code below use the albersusa package to generate a GIS file with Alaska and Hawaii as insets. But the resulting default mapview map of this data still shows default background tiles, resulting in Alaska and Hawaii appearing overlayed onto Mexico.
library(albersusa)
us_geo50 <- usa_sf("lcc") %>% mutate(GEOID = as.character(fips_state))
pop_data50 <- readr::read_csv("data/state_population_data50.csv")
all_data50 <- inner_join(us_geo50, pop_data50, by = c("GEOID" = "GEOID"))
mapview(all_data50, zcol = "PctChange10_20")
Screenshot by Sharon Machlis
Sometimes you would like to use a custom projection and remove the background map tiles.
If I tell mapview to use the dataโs native projection, though, the projection is accurate and the background no longer includes map tiles.
mapview(all_data50, zcol = "PctChange10_20",
native.crs = TRUE)
Screenshot by Sharon Machlis
Using a custom projection can allow for more flexible displays, such as this map of the US with Alaska and Hawaii as insets.
More R mapview customizations
You can customize your bin breakpoints with the at argument. In the code below, I set breaks using base Rโs seq() function, going from -4 to 20 by increments of 2. The map colors and legend will show the new breaks.
mapview(all_data, zcol = "PctChange10_20",
at = seq(-4, 20, 2))
You can customize your pop-ups and hover text using the same techniques as with the leaflet R package. Iโm sure there are multiple ways to do this, but this is my usual process:
First, create a vector of character strings โ with HTML code if you want formatting โย using the full dataframe$column_name syntax for variables. I find the glue package useful for this, although you could paste() as well. For example:
mylabel <- glue::glue("{all_data$State} {all_data$PctChange10_20}%")
Second, apply the htmltools packageโs HTML() function to the vector with lapply() so you end up with a list โ because you need a list โ such as:
mypopup <- glue::glue("{all_data$State}<br />
Change 2000-2010: {all_data$PctChange00_10}%<br />
Change 2010-2020: {all_data$PctChange10_20}%") %>%
lapply(htmltools::HTML)
mylabel <- glue::glue("{all_data$State} {all_data$PctChange10_20}%") %>%
lapply(htmltools::HTML)
My pop-up list now looks something like this:
head(mypopup, 3)
[[1]] Washington<br />
Change 2000-2010: 14.1%<br />
Change 2010-2020: 14.6%
[[2]] Puerto Rico<br />
Change 2000-2010: -2.2%<br />
Change 2010-2020: -11.8%
[[3]] South Dakota<br />
Change 2000-2010: 7.9%<br />
Change 2010-2020: 8.9%
Third, use the customized pop-ups or label hover text within mapview with the popup or label (for hover text) arguments:ย
mapview(all_data, zcol = "PctChange10_20",
popup = mypopup, label = mylabel)
One of the things youโll probably do fairly often is customize the color palette. If youโve got a favorite palette, you donโt have to add it to mapview() every time you create a map. The mapview package lets you set options for your full R session, includingย a vector.palette argument.ย There are a lot more default options you can set. Check the mapviewOptions help file withย ?mapviewOptions.
To set a default palette, you probably want to create a palette function instead of using a palette with a set number of colors. This will head off the mapview warning about doing color interpolation. In the code below, I use base Rโs colorRampPalette() to create a palette function from the nine colors in RColorBrewer Greens:
mapviewOptions(vector.palette = colorRampPalette(RColorBrewer::brewer.pal(9, "Greens")))
That palette function takes an integer as its argument and generates a specific number of colors from the original palette. You can save a palette function to a variable and then use it to see how it works.
mypalette <- colorRampPalette(RColorBrewer::brewer.pal(9, "Greens"))
mypalette(12)
[1] "#F7FCF5" "#E9F6E5" "#D7EFD1" "#C0E6B9" "#A4DA9E" "#84CB83" "#61BA6C" "#3EA85A" "#289049"
[10] "#0F7A37" "#006127" "#00441B"
Just keep in mind that, while all this customization is nice to know, you donโt need it for quick prototyping. One of the most important takeaways about mapview is that you can do exploratory analysis of geographic data with a single short line of code.
If you want to move beyond simple prototypes and learn even more about mapviewโs capabilities, package creator Tim Applehans gave a workshop last year that lasted about 2.5 hours. The videos are available on YouTube:
Using your map
If you want to use your map outside of RStudio, there are several options. As with any R object, you can save it to an R file with save() or saveRDS() and then use it by loading the object into an R Markdown document. You can also use mapviewโs mapshot() function to save a map as an HTML file with syntax like
mapshot(mymap, url = "mymap.html")
For more R tips, head to theย InfoWorld Do More With R page.


