As you may know from a previous post I am interested in electric-vehicle (EV) trends and the transition to a more electrified transportation fleet. I wanted to do some mapping and spatial analysis, and I recently took the Creating Maps with R course by Charlie Joey Hadley, so I decided to use some of the skills I learned to create some maps of EV charging station data for Colorado.
Goal
My goal in this post is to create choropleth map(s) showing the number of EV charging stations per county in Colorado.
Simple feature collection with 6 features and 12 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -109.0603 ymin: 36.99902 xmax: -104.6606 ymax: 39.92525
Geodetic CRS: NAD83
STATEFP COUNTYFP COUNTYNS AFFGEOID GEOID NAME NAMELSAD
7 08 037 00198134 0500000US08037 08037 Eagle Eagle County
150 08 059 00198145 0500000US08059 08059 Jefferson Jefferson County
151 08 067 00198148 0500000US08067 08067 La Plata La Plata County
165 08 077 00198154 0500000US08077 08077 Mesa Mesa County
174 08 035 00198133 0500000US08035 08035 Douglas Douglas County
209 08 043 00198137 0500000US08043 08043 Fremont Fremont County
STUSPS STATE_NAME LSAD ALAND AWATER geometry
7 CO Colorado 06 4362754228 18970639 MULTIPOLYGON (((-107.1137 3...
150 CO Colorado 06 1979735379 25071495 MULTIPOLYGON (((-105.0558 3...
151 CO Colorado 06 4376255277 25642579 MULTIPOLYGON (((-108.3796 3...
165 CO Colorado 06 8621348071 31991710 MULTIPOLYGON (((-109.0603 3...
174 CO Colorado 06 2176020279 6795841 MULTIPOLYGON (((-105.3274 3...
209 CO Colorado 06 3972613670 2235542 MULTIPOLYGON (((-106.0123 3...
Zip codes
I have the EV station data and the county shape files, so the next step is to join them together. However, I have a problem: the EV stations data does not contain the county name or code, so I can’t join them yet without a common column. There are probably a lot of different solutions to this problem (for example the EV data contains addresses so I could geo-code these to get the county). In this case, I decided the easiest solution was to download the zip code database from the USPS (free for personal use), which contains both zip codes and their corresponding county (Table 1).
Now we can finally join the data we want to plot (# EV stations per county) in ev_county_counts to our sf object (co_counties) with the county spatial data, and we are ready to make some maps.
Code
co_ev_counts<-co_counties%>%left_join(ev_county_counts, by =c("NAMELSAD"="county"))co_ev_counts<-sf::st_transform(co_ev_counts, 4326)head(co_ev_counts)
Simple feature collection with 6 features and 13 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -109.0603 ymin: 36.99902 xmax: -104.6606 ymax: 39.92525
Geodetic CRS: WGS 84
STATEFP COUNTYFP COUNTYNS AFFGEOID GEOID NAME NAMELSAD
1 08 037 00198134 0500000US08037 08037 Eagle Eagle County
2 08 059 00198145 0500000US08059 08059 Jefferson Jefferson County
3 08 067 00198148 0500000US08067 08067 La Plata La Plata County
4 08 077 00198154 0500000US08077 08077 Mesa Mesa County
5 08 035 00198133 0500000US08035 08035 Douglas Douglas County
6 08 043 00198137 0500000US08043 08043 Fremont Fremont County
STUSPS STATE_NAME LSAD ALAND AWATER n geometry
1 CO Colorado 06 4362754228 18970639 106 MULTIPOLYGON (((-107.1137 3...
2 CO Colorado 06 1979735379 25071495 172 MULTIPOLYGON (((-105.0558 3...
3 CO Colorado 06 4376255277 25642579 39 MULTIPOLYGON (((-108.3796 3...
4 CO Colorado 06 8621348071 31991710 53 MULTIPOLYGON (((-109.0603 3...
5 CO Colorado 06 2176020279 6795841 62 MULTIPOLYGON (((-105.3274 3...
6 CO Colorado 06 3972613670 2235542 6 MULTIPOLYGON (((-106.0123 3...
Mapping
I’m going to make choropleth maps using two of the more popular mapping packages: ggplot2(Wickham 2016) and leaflet(Cheng, Karambelkar, and Xie 2023). I think they both make good-looking maps; the main advantage to leaflet is that the map is interactive.
ggplot
ggplot2 makes it relatively easy to plot spatial data in an sf object with the geom_sf function
Using leaflet requires a little more code but allows you to create an interactive map that can be more useful to the reader.
In the map below (Figure 2) I’ve set the popup to display the county name and number of stations when you click on the map.
You can also drag the map around and zoom in/out.
It’s also very easy with Leaflet to add a basemap (OpenStreetMap in this case) layer under the choropleth. I decided to add this here to give readers a better sense of context, and also because I wanted to highlight that the counties close to major highways (I-70 east-west and I-25 north-south) appear to have higher numbers of chargers.
Note I’ve also included some code using from the Creating Maps in R course to fix an issue in the legend where the NA entry overlaps with the other entries.
Code
# create color palettepal_ev<-leaflet::colorNumeric(palette ="viridis", domain =co_ev_counts$n)co_ev_map<-leaflet()%>%addTiles()%>%# adds OpenStretMap basemapaddPolygons(data =co_ev_counts, weight =1, color ="black", popup =paste(co_ev_counts$NAME, "<br>"," EV Stations: ", co_ev_counts$n, "<br>"), fillColor =~pal_ev(n), fillOpacity =0.6)%>%addLegend(data =co_ev_counts, pal =pal_ev, values =~n, opacity =1, title ="# of EV Stations <br> Per County")# legend fix --------------------------------------------------------------# for issue with na in legendhtml_fix<-htmltools::tags$style(type ="text/css", "div.info.legend.leaflet-control br {clear: both;}")co_ev_map%>%htmlwidgets::prependContent(html_fix)
Future Analysis
Now that I have the basic framework set up for mapping the EV data, there are a lot of other interesting questions I would like to investigate.
Look at breakdown by charger level/type/network etc..
Look at trends over time.
Look at relationship between demographics (population, income etc. ) and chargers. The tidycensus package would probably be useful for this.
Extend to other states or similar analysis at state level.
SessionInfo
In order to improve the reproducibility of this analysis, I include the sessionInfo below at the time this post was rendered.
Cheng, Joe, Bhaskar Karambelkar, and Yihui Xie. 2023. “Leaflet: Create Interactive Web Maps with the JavaScript ’Leaflet’ Library.”https://CRAN.R-project.org/package=leaflet.
---title: "Mapping the Number of EV Charging Stations by County in Colorado Using R" author: "Andy Pickering" image: image.png format: html: code-link: true code-fold: show tbl-cap-location: bottomdate: "2023-08-01"date-modified: todaycategories: [EV, R, visualization, mapping, API]toc: truebibliography: references.bibfreeze: auto---# IntroductionAs you may know from a [previous post](https://andypicke.quarto.pub/portfolio/posts/EV_Stations/) I am interested in electric-vehicle (EV) trends and the transition to a more electrified transportation fleet. I wanted to do some mapping and spatial analysis, and I recently took the [Creating Maps with R](https://www.linkedin.com/learning-login/share?forceAccount=false&redirect=https%3A%2F%2Fwww.linkedin.com%2Flearning%2Fcreating-maps-with-r%3Ftrk%3Dshare_ent_url%26shareId%3DQgGBGCunSQyanayy1A%252Fffg%253D%253D) course by [Charlie Joey Hadley](https://www.linkedin.com/learning/instructors/charlie-joey-hadley), so I decided to use some of the skills I learned to create some maps of EV charging station data for Colorado.## Goal- My goal in this post is to create [choropleth](https://en.wikipedia.org/wiki/Choropleth_map) map(s) showing the number of EV charging stations per county in Colorado.# Data & Analysis```{r }#| label: load-libraries#| code-fold: true#| code-summary: Load LibrariessuppressPackageStartupMessages(library(tidyverse))library(httr)suppressPackageStartupMessages(library(jsonlite))ggplot2::theme_set(theme_grey(base_size = 15))library(leaflet)suppressPackageStartupMessages(library(tigris)) # to get county shapefiles for mapslibrary(ggspatial) # for adding basemaps to ggplot2 mapslibrary(DT) # make nice data tables```## EV Stations dataData on EV stations is obtained from the [Alternative Fuels Data Center](https://afdc.energy.gov/)'s Alternative Fuel Stations [database](https://developer.nrel.gov/docs/transportation/alt-fuel-stations-v1/). See my [previous post](https://andypicke.quarto.pub/portfolio/posts/EV_Stations/){.uri} for more details on getting the data from the [API](https://developer.nrel.gov/docs/transportation/alt-fuel-stations-v1/all/).```{r }#| label: load-data#| code-fold: true#| code-summary: Load EV stations data from API# API key is stored in my .Renviron fileapi_key <- Sys.getenv("AFDC_KEY")target <- "https://developer.nrel.gov/api/alt-fuel-stations/v1"# Return data for all electric stations in Coloradoapi_path <- ".json?&fuel_type=ELEC&state=CO&limit=all"complete_api_path <- paste0(target,api_path,'&api_key=',api_key)response <- httr::GET(url = complete_api_path)if (response$status_code != 200) { print(paste('Warning, API call returned error code', response$status_code))}ev_dat <- jsonlite::fromJSON(httr::content(response,"text"))ev <- ev_dat$fuel_stations# filter out non-EV related fieldsev <- ev %>% select(-dplyr::starts_with("lng")) %>% select(-starts_with("cng")) %>% select(-starts_with("lpg")) %>% select(-starts_with("hy")) %>% select(-starts_with("ng")) %>% select(-starts_with("e85")) %>% select(-starts_with("bd")) %>% select(-starts_with("rd")) %>% filter(status_code == 'E')```## County dataNext I need shape files for the Colorado counties to make the map; these are obtained from the [tigris](https://github.com/walkerke/tigris)[@tigris] package.```{r }#| label: get-county-dataoptions(tigris_use_cache = TRUE)co_counties <- tigris::counties("CO",cb = TRUE, progress_bar = FALSE)head(co_counties)```## Zip codesI have the EV station data and the county shape files, so the next step is to join them together. However, I have a **problem**: the EV stations data does not contain the county name or code, so I can't join them yet without a common column. There are probably a lot of different solutions to this problem (for example the EV data contains addresses so I could geo-code these to get the county). In this case, I decided the easiest solution was to download the [zip code database](https://www.unitedstateszipcodes.org/zip-code-database/){.uri} from the USPS (free for personal use), which contains both zip codes and their corresponding county (@tbl-zip-codes).```{r }#| label: tbl-zip-codes#| tbl-cap: Zip code data from USPSzips <- readr::read_csv("data/zip_code_database.csv", show_col_types = FALSE) %>% filter(state == "CO") %>% select(zip, primary_city, county)zips |> DT::datatable(options = list(pageLength = 5), rownames = FALSE)```Next I compute the number of stations per zip code in the EV data, and join to the zip code database to add the county column (@tbl-ev-county-counts).```{r }#| label: tbl-ev-county-counts#| tbl-cap: Number of EV charging stations per countyev_county_counts <- ev %>% select(id,zip,city) %>% left_join(zips, by = "zip") %>% dplyr::count(county) %>% arrange(desc(n))ev_county_counts |> DT::datatable(options = list(pageLength = 5), rownames = FALSE)```## Combining dataNow we can finally join the data we want to plot (# EV stations per county) in *ev_county_counts* to our sf object (*co_counties*) with the county spatial data, and we are ready to make some maps.```{r Combine Data}co_ev_counts <- co_counties %>% left_join(ev_county_counts, by = c("NAMELSAD" = "county"))co_ev_counts <- sf::st_transform(co_ev_counts, 4326)head(co_ev_counts)```# MappingI'm going to make choropleth maps using two of the more popular mapping packages: *ggplot2* [@ggplot2] and *leaflet* [@leaflet]. I think they both make good-looking maps; the main advantage to leaflet is that the map is interactive.## ggplot- ggplot2 makes it relatively easy to plot spatial data in an sf object with the *geom_sf* function- The *scales* [@scales] package is used to format the numbers in the legend- The *ggspatial* [@ggspatial] package is used to add a base map showing some of the major cities and roads```{r }#| label: fig-ggplot-choropleth#| fig-cap: Choropleth map of number of EV charging stations by county, made with ggplot2ggplot() + ggspatial::annotation_map_tile(progress = "none") + geom_sf(data = co_ev_counts, aes(fill = n), alpha = 0.5) + scale_fill_viridis_c(labels = scales::number_format(big.mark = ","), name = '# Ev Stations') + ggtitle("Number of EV Stations by Colorado County") + theme_void()```## LeafletUsing leaflet requires a little more code but allows you to create an interactive map that can be more useful to the reader.\- In the map below (@fig-leaflet-choropleth) I've set the *popup* to display the county name and number of stations when you **click on the map**.- You can also **drag the map around and zoom in/out**.- It's also very easy with Leaflet to add a basemap (OpenStreetMap in this case) layer under the choropleth. I decided to add this here to give readers a better sense of context, and also because I wanted to highlight that the counties close to major highways (I-70 east-west and I-25 north-south) appear to have higher numbers of chargers.- Note I've also included some code using from the [Creating Maps in R](https://www.linkedin.com/learning-login/share?forceAccount=false&redirect=https%3A%2F%2Fwww.linkedin.com%2Flearning%2Fcreating-maps-with-r%3Ftrk%3Dshare_ent_url%26shareId%3DQgGBGCunSQyanayy1A%252Fffg%253D%253D) course to fix an issue in the legend where the NA entry overlaps with the other entries.```{r }#| label: fig-leaflet-choropleth#| fig-cap: Interactive choropleth map of number of EV charging stations by county# create color palettepal_ev <- leaflet::colorNumeric(palette = "viridis", domain = co_ev_counts$n)co_ev_map <- leaflet() %>% addTiles() %>% # adds OpenStretMap basemap addPolygons(data = co_ev_counts, weight = 1, color = "black", popup = paste(co_ev_counts$NAME, "<br>", " EV Stations: ", co_ev_counts$n, "<br>"), fillColor = ~pal_ev(n), fillOpacity = 0.6) %>% addLegend(data = co_ev_counts, pal = pal_ev, values = ~n, opacity = 1, title = "# of EV Stations <br> Per County" )# legend fix --------------------------------------------------------------# for issue with na in legendhtml_fix <- htmltools::tags$style(type = "text/css", "div.info.legend.leaflet-control br {clear: both;}") co_ev_map %>% htmlwidgets::prependContent(html_fix)```# Future AnalysisNow that I have the basic framework set up for mapping the EV data, there are a lot of other interesting questions I would like to investigate.- Look at breakdown by charger level/type/network etc..- Look at trends over time.- Look at relationship between demographics (population, income etc. ) and chargers. The [*tidycensus*](https://walker-data.com/tidycensus/) package would probably be useful for this.- Extend to other states or similar analysis at state level.# SessionInfoIn order to improve the reproducibility of this analysis, I include the *sessionInfo* below at the time this post was rendered.```{r}sessionInfo()```