Site icon R-bloggers

Mapping the Number of EV Charging Stations by County in Colorado Using R

[This article was first published on Andy Pickering, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
< section id="introduction" class="level1">

Introduction

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.

< section id="goal" class="level2">

Goal

< section id="data-analysis" class="level1">

Data & Analysis

< details open="">< summary>Code
suppressPackageStartupMessages(library(tidyverse))
library(httr)
suppressPackageStartupMessages(library(jsonlite))
ggplot2::theme_set(theme_grey(base_size = 15))
library(leaflet)
suppressPackageStartupMessages(library(tigris))
< section id="ev-stations-data" class="level2">

EV Stations data

Data on EV stations is obtained from the Alternative Fuels Data Center’s Alternative Fuel Stations database. See my previous post for more details on getting the data from the API.

< details open="">< summary>Code
# API key is stored in my .Renviron file
api_key <- Sys.getenv("AFDC_KEY")

target <- "https://developer.nrel.gov/api/alt-fuel-stations/v1"
# Return data for all electric stations in Colorado
api_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 fields
ev <- 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')
< section id="county-data" class="level2">

County data

Next I need shape files for the Colorado counties to make the map; these are obtained from the tigris package.

< details open="">< summary>Code
options(tigris_use_cache = TRUE)
co_counties <- tigris::counties("CO",cb = TRUE)
Retrieving data for the year 2021
< details open="">< summary>Code
head(co_counties)
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...
< section id="zip-codes" class="level2">

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.

< details open="">< summary>Code
zips <- readr::read_csv("data/zip_code_database.csv",
                        show_col_types = FALSE) %>% 
  filter(state == "CO") %>% 
  select(zip, primary_city, county)

head(zips)
# A tibble: 6 × 3
  zip   primary_city county          
  <chr> <chr>        <chr>           
1 80001 Arvada       Jefferson County
2 80002 Arvada       Jefferson County
3 80003 Arvada       Jefferson County
4 80004 Arvada       Jefferson County
5 80005 Arvada       Jefferson County
6 80006 Arvada       Jefferson County

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.

< details open="">< summary>Code
ev_county_counts <- ev %>% 
  select(id,zip,city) %>% 
  left_join(zips, by = "zip") %>% 
  dplyr::count(county) %>% 
  arrange(desc(n))

head(ev_county_counts)
            county   n
1    Denver County 320
2   Boulder County 303
3   Larimer County 145
4 Jefferson County 142
5  Arapahoe County 118
6     Eagle County 103
< section id="combining-data" class="level2">

Combining data

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.

< details open="">< summary>Code
co_ev_counts <- co_counties %>% 
  left_join(ev_county_counts, by = c("NAMELSAD" = "county"))

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:  NAD83
  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 103 MULTIPOLYGON (((-107.1137 3...
2     CO   Colorado   06 1979735379 25071495 142 MULTIPOLYGON (((-105.0558 3...
3     CO   Colorado   06 4376255277 25642579  30 MULTIPOLYGON (((-108.3796 3...
4     CO   Colorado   06 8621348071 31991710  47 MULTIPOLYGON (((-109.0603 3...
5     CO   Colorado   06 2176020279  6795841  57 MULTIPOLYGON (((-105.3274 3...
6     CO   Colorado   06 3972613670  2235542   6 MULTIPOLYGON (((-106.0123 3...
< section id="mapping" class="level1">

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.

< section id="ggplot" class="level2">

ggplot

< details open="">< summary>Code
ggplot() +
  geom_sf(data = co_ev_counts,
          aes(fill = n)) +
  scale_fill_viridis_c(labels = scales::number_format(big.mark = ","),
                       name = '# Ev Stations') +
  ggtitle("Number of EV Stations by Colorado County") +
  theme_void()

Choropleth map of number of EV charging stations by county
< section id="leaflet" class="level2">

Leaflet

Using leaflet requires a little more code but allows you to create an interactive map that can be more useful to the reader.

< details open="">< summary>Code
# create color palette
pal_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"
            )
Warning: sf layer has inconsistent datum (+proj=longlat +datum=NAD83 +no_defs).
Need '+proj=longlat +datum=WGS84'
< details open="">< summary>Code
# legend fix --------------------------------------------------------------
# for issue with na in legend
html_fix <- htmltools::tags$style(type = "text/css", "div.info.legend.leaflet-control br {clear: both;}") 

co_ev_map %>% 
  htmlwidgets::prependContent(html_fix)
To leave a comment for the author, please follow the link and comment on their blog: Andy Pickering.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Exit mobile version