Mapping Electricity Prices with R

R
energy
API
mapping
Author
Published

June 4, 2025

Introduction

I recently saw an article from the EIA about residential electricity prices, which got me interested in making a map of the prices for the US. In this post I’ll show how you can use R to make a choropleth map of the average price of electricity for each state.

Load Libraries
library(httr)
library(tidyverse)
library(tigris)
options(tigris_use_cache = TRUE)
library(DT)
library(ggiraph)
1
Get data from API
2
Data Wrangling and Plotting
3
Get state geometries
4
Display nice data tables
5
Make figures interactive

Getting the data

The data is from the the EIA; specifically the electricity retail-sales data. I went to their data browser and selected the average price for the residential sector. This gave me the URL to use to request the data from their API.

Note you will need to register for a free API key from the EIA.

Tip
I find the easiest way to store an API key in your .Renviron file is with the {usethis} package. Simply call the `usethis::edit_r_environ()` function to open the .Renviron file, add your key in the form *API_KEY="xxxxx"*, and save. After you restart R, you will be able to load the key in your code using `Sys.getenv("API_KEY")`.
  • For the map, I’ll just use the prices for February 2025.

  • Some of the data are for regions, but I want to keep just the states. I’ll use the handy state.abb data set in R to filter the data to just US states.

Get data from API
# my API key for EIA data
api_key <- Sys.getenv("EIA_KEY")

req_url <- "https://api.eia.gov/v2/electricity/retail-sales/data/?frequency=monthly&data[0]=price&facets[sectorid][]=RES&start=2025-02&sort[0][column]=period&sort[0][direction]=desc&offset=0&length=5000"

resp <- httr::GET(paste0(req_url, "&api_key=", api_key))

resp_parsed <- jsonlite::fromJSON(httr::content(resp,"text"))

elec_prices <- resp_parsed$response$data |>
  filter(period == "2025-02") |>
  mutate(price = as.numeric(price)) |>
  filter(stateid %in% state.abb)

rm(resp_parsed, resp)

elec_prices |>
  DT::datatable(options = list(pageLength = 5),
                rownames = FALSE)
Table 1: Table of residential electricity prices for US states, from EIA.

State Geometries

Now we have the price data, but we need the states geometries to make the map. I’ll use the {tigris}(Walker 2024) package to download the states.

  • I also use the shift_geometry() function from {tigris} to move and re-scale Alaska and Hawaii so they fit better on the map.
Get US states geometries
us_states <- tigris::states() |>
  select(STUSPS, geometry) |>
  filter(STUSPS %in% state.abb) |>
  tigris::shift_geometry()

head(us_states)
Simple feature collection with 6 features and 1 field
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -91843.51 ymin: -1354601 xmax: 2042471 ymax: 1323908
Projected CRS: USA_Contiguous_Albers_Equal_Area_Conic
  STUSPS                       geometry
1     WV MULTIPOLYGON (((1321631 974...
2     FL MULTIPOLYGON (((1318717 -13...
3     IL MULTIPOLYGON (((600886 -267...
4     MN MULTIPOLYGON (((255077.7 87...
5     MD MULTIPOLYGON (((1714036 393...
6     RI MULTIPOLYGON (((2002871 667...

Joining the data

The last step before making the map is to join the price data with the dataframe containing the states geometries.

Join data
dat_joined <- 
  us_states |>
  left_join(elec_prices, by = c("STUSPS" = "stateid"))

head(dat_joined,5)
Simple feature collection with 5 features and 7 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -91843.51 ymin: -1354601 xmax: 1802679 ymax: 1323908
Projected CRS: USA_Contiguous_Albers_Equal_Area_Conic
  STUSPS  period stateDescription sectorid  sectorName price
1     WV 2025-02    West Virginia      RES residential 14.82
2     FL 2025-02          Florida      RES residential 14.98
3     IL 2025-02         Illinois      RES residential 16.49
4     MN 2025-02        Minnesota      RES residential 14.62
5     MD 2025-02         Maryland      RES residential 18.32
              price-units                       geometry
1 cents per kilowatt-hour MULTIPOLYGON (((1321631 974...
2 cents per kilowatt-hour MULTIPOLYGON (((1318717 -13...
3 cents per kilowatt-hour MULTIPOLYGON (((600886 -267...
4 cents per kilowatt-hour MULTIPOLYGON (((255077.7 87...
5 cents per kilowatt-hour MULTIPOLYGON (((1714036 393...

Static Map

Now with the data and geometries all together, we can start making the map. First I’ll make a static map (Figure 1) using {ggplot2}.

Make static map
dat_joined |>
  ggplot() +
  geom_sf(aes(fill = price)) +
  scale_fill_viridis_c() +
  theme_void() +
  labs(title = "US Electricity Prices - February 2025",
       caption = "Data source: EIA",
       fill = "cents/kWh") 
Figure 1: Choropleth map of average residential electricity prices for US states. Data from EIA.

Interactive map

The static choropleth map shows the spatial variability in prices, but it would be nice to also be able to see the actual price for a state. One way to do this is to use the {ggiraph}(Gohel and Skintzos (2025)) package to add some interactivity, so that when you hover over the map the value for that state would be displayed. Unfortunately including the interactive map exceeded the size limit for my (free) blog site, but I included the code below if you are interested:

Make interactive map
int_map <- 
dat_joined |>
  ggplot() +
  ggiraph::geom_sf_interactive(data = dat_joined,
                      aes(fill = price,
                          tooltip = price,
                          data_id = stateDescription)
                      ) +
  scale_fill_viridis_c() +
  theme_void() +
  labs(title = "US Electricity Prices - February 2025",
       caption = "Data source: EIA",
       fill = "cents/kWh") 


ggiraph::girafe(ggobj = int_map)

Summary

In this post we:

-Downloaded data on electricity prices from the EIA API using {httr}(Wickham 2023)

-Joined the price data with state geometries from {tigris}

-Made a static choropleth map with {ggplot2}(Wickham 2016)

-Showed how to make the map interactive with {ggiraph}

I hope you found it interesting, and maybe are inspired to adapt this analysis to make your own maps or spatial analysis!

SessionInfo

R version 4.4.1 (2024-06-14)
Platform: x86_64-apple-darwin20
Running under: macOS 15.5

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Denver
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] ggiraph_0.8.12  DT_0.33         tigris_2.1      lubridate_1.9.4
 [5] forcats_1.0.0   stringr_1.5.1   dplyr_1.1.4     purrr_1.0.4    
 [9] readr_2.1.5     tidyr_1.3.1     tibble_3.2.1    ggplot2_3.5.1  
[13] tidyverse_2.0.0 httr_1.4.7     

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       xfun_0.51          bslib_0.9.0        htmlwidgets_1.6.4 
 [5] tzdb_0.5.0         vctrs_0.6.5        tools_4.4.1        crosstalk_1.2.1   
 [9] generics_0.1.3     curl_6.0.0         proxy_0.4-27       pkgconfig_2.0.3   
[13] KernSmooth_2.23-26 uuid_1.2-1         lifecycle_1.0.4    farver_2.1.2      
[17] compiler_4.4.1     munsell_0.5.1      htmltools_0.5.8.1  class_7.3-23      
[21] sass_0.4.9         yaml_2.3.10        pillar_1.10.1      jquerylib_0.1.4   
[25] classInt_0.4-11    cachem_1.1.0       tidyselect_1.2.1   digest_0.6.37     
[29] stringi_1.8.7      sf_1.0-20          labeling_0.4.3     fastmap_1.2.0     
[33] grid_4.4.1         colorspace_2.1-1   cli_3.6.4          magrittr_2.0.3    
[37] e1071_1.7-16       withr_3.0.2        scales_1.3.0       rappdirs_0.3.3    
[41] timechange_0.3.0   rmarkdown_2.29     hms_1.1.3          evaluate_1.0.3    
[45] knitr_1.50         viridisLite_0.4.2  rlang_1.1.5        Rcpp_1.0.14       
[49] glue_1.8.0         DBI_1.2.3          renv_1.0.9         rstudioapi_0.17.1 
[53] jsonlite_2.0.0     R6_2.6.1           systemfonts_1.2.1  units_0.8-7       

References

Gohel, David, and Panagiotis Skintzos. 2025. “Ggiraph: Make ’Ggplot2’ Graphics Interactive.” https://CRAN.R-project.org/package=ggiraph.
Walker, Kyle. 2024. “Tigris: Load Census TIGER/Line Shapefiles.” https://github.com/walkerke/tigris.
Wickham, Hadley. 2016. “Ggplot2: Elegant Graphics for Data Analysis.” https://ggplot2.tidyverse.org.
———. 2023. “Httr: Tools for Working with URLs and HTTP.” https://CRAN.R-project.org/package=httr.