Trends in US Electricity Generation and CO2 Emissions

energy
R
visualization
Author
Published

November 19, 2023

Modified

December 20, 2024

Introduction

In this analysis I will look at trends in US electricity generation and associated CO2 emissions, using a nice data set available from Ember. Ember provides monthly and yearly data on US electricity generation and emissions for the total US as well as by state, both in total and broken down by fuel types. In this post, I will limit my analysis to yearly data and total (all fuel types) US generation and emissions.

Data

Data was downloaded on Nov. 7, 2023 from https://ember-climate.org/data-catalogue/us-electricity-data/ as a csv file and read into R. Some unnecessary columns were dropped, and the data is sub-setted into separate data frames for total US generation (Table 1) and emissions (Table 2).

Code
suppressPackageStartupMessages(library(tidyverse))
suppressPackageStartupMessages(library(gt))
theme_set(theme_gray(base_size = 16))
suppressPackageStartupMessages(library(plotly))

# read yearly data
# Data only contains USA, so we can drop country column and some other un-needed ones

df_yearly <- readr::read_csv('data/us_yearly_full_release_long_format-2.csv', 
                      show_col_types = FALSE) |> 
  select(-c(Country,`Country code`,`State code`,
            `YoY % change`,`YoY absolute change`))


# make separate dataframe for just Electricity generation data
# This contains both actual generation (gWh) and % of total gen for each fuel type

df_gen_yearly_UStot <- df_yearly |> 
  filter(Category == "Electricity generation") |> 
  filter(Variable == "Total Generation") |> 
  filter(State == 'US Total') |> 
  select(-c("State","State type")) |> 
  rename(generation = Value)


# make separate dataframe for just Power sector emissions data (not generation)
df_emis_yearly_UStot <- df_yearly |> 
  filter(Category == "Power sector emissions") |> 
  filter(State == 'US Total') |> 
  filter(Variable == 'Total emissions') |> 
  rename(emissions = Value)

Generation Data

Code
df_gen_yearly_UStot |>
  gt::gt() |> 
  opt_row_striping() |> 
  opt_interactive(use_highlight = TRUE, page_size_default = 5)
Table 1: Annual electricity generation data for USA

Emissions Data

Code
df_emis_yearly_UStot |> 
  gt::gt() |> 
  opt_row_striping() |> 
  opt_interactive(use_highlight = TRUE, page_size_default = 5)
Table 2: Annual US power-sector emissions.

Electricity Generation

First I’ll look at the time series of US electricity generation (Figure 1). We can see that generation has generally increased over time, though it appears to have been relatively flat from about 2008-2017. There was a significant decrease in 2009 (possibly related to the 2008 recession), and also in 2020 (possibly related to the Covid-19 pandemic).

Code
g <- df_gen_yearly_UStot |> 
  ggplot(aes(Year, generation)) +
  geom_point(size = 4) +
  ggtitle("Total US Yearly Electricity Generation") +
  ylab("[GWh]")

plotly::ggplotly(g)
Figure 1: Timeseries of yearly US electricity generation

Emissions

Next I’ll look at the time series of emissions from the power sector. Figure 2 shows that US power sector emissions were increasing up to about 2007, but have been steadily decreasing over time since then.

Code
g <- df_emis_yearly_UStot |> 
  ggplot(aes(Year, emissions)) +
  geom_point(size = 4) +
  geom_smooth(formula = y ~ x, method = "loess") +
  ggtitle("Total CO2 Emissions From Electricity Generation") +
  ylab("Emissions [ktCO2]")

plotly::ggplotly(g)
Figure 2: Timeseries of yearly US emissions from electricity generation

Emissions vs Generation

So far we have sen that electricity generation has been generally increasing over time, while the associated CO2 emissions have been decreasing. Next I will join the generation and emissions data to compare them directly.

Code
df1 <- df_emis_yearly_UStot |> 
  select(Year, emissions)

df2 <- df_gen_yearly_UStot |> 
  select(Year, generation) 

df_comb <- df1 |> 
  inner_join(df2,by = join_by(Year)) |> 
  mutate(emis_per_gen = emissions/generation)

Figure 3 shows a scatter plot of total US yearly emissions vs electricity generation, with the color corresponding to the year. This shows a trend of decreasing emissions over time, even as generation has increased.

Code
g <- df_comb |> 
  ggplot(aes(generation, emissions)) +
  geom_point(size = 5, aes(color = Year)) +
  xlab("Generation [GWh]") +
  ylab("Emissions [ktCO2]")

plotly::ggplotly(g)
Figure 3: Scatterplot of total US yearly emissions vs electricity generation

An easier way to visualize this trend is to plot the emissions/generation (i.e the CO2 intensity) over time, shown in Figure 4. In this figure, the size of the points are also proportional to the value of generation.

Code
g <- df_comb |> 
  ggplot(aes(Year, emis_per_gen)) +
  geom_point(aes(size = generation), color = 'lightblue', alpha = 1) +
  ylab("ktCO2 / GWh") +
  ggtitle("Yearly US Emissions/Generation") +
  scale_size(range = c(0,10))

plotly::ggplotly(g)
Figure 4: Timeseries of total US yearly Emssions/Generation, sized by value of generation

Summary

  • Total electricity generation has continued to increase (Figure 1)
  • At the same time, total CO2 emissions from electricity generation have decreased (Figure 2)
  • The amount of Emissions per generation (i.e. carbon intensity) has decreased over time (Figure 4), likely due in large part to a combination of a transition from coal to natural gas, and an increase in renewable energy. In a future analysis I will further break down the data by fuel type to get a better understanding of what is driving this trend.

Further Exploration:

Some of the areas I plan to explore in continuing analysis of this data include:

  • Breaking down data by fuel types

  • Looking at monthly data

  • Examine correlation with weather/temperature

  • Looking for changes in seasonal patterns of energy generation associated with home electrification (e.g. electric heat pumps replacing gas furnaces).

  • Break down by individual states

SessionInfo

To make this analysis more reproducible, my SessionInfo is listed below.

Code
utils::sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: x86_64-apple-darwin20
Running under: macOS Sonoma 14.6.1

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] plotly_4.10.4   gt_0.10.1       lubridate_1.9.3 forcats_1.0.0  
 [5] stringr_1.5.1   dplyr_1.1.4     purrr_1.0.2     readr_2.1.5    
 [9] tidyr_1.3.1     tibble_3.2.1    ggplot2_3.5.1   tidyverse_2.0.0

loaded via a namespace (and not attached):
 [1] gtable_0.3.5      xfun_0.46         htmlwidgets_1.6.4 lattice_0.22-6   
 [5] tzdb_0.4.0        vctrs_0.6.5       tools_4.4.1       crosstalk_1.2.1  
 [9] generics_0.1.3    parallel_4.4.1    fansi_1.0.6       pkgconfig_2.0.3  
[13] Matrix_1.7-0      data.table_1.15.4 lifecycle_1.0.4   compiler_4.4.1   
[17] farver_2.1.1      munsell_0.5.1     htmltools_0.5.8.1 sass_0.4.9       
[21] yaml_2.3.10       lazyeval_0.2.2    pillar_1.9.0      crayon_1.5.3     
[25] nlme_3.1-165      tidyselect_1.2.1  digest_0.6.36     stringi_1.8.4    
[29] labeling_0.4.3    splines_4.4.1     fastmap_1.2.0     grid_4.4.1       
[33] colorspace_2.1-0  cli_3.6.3         magrittr_2.0.3    utf8_1.2.4       
[37] withr_3.0.1       reactR_0.5.0      scales_1.3.0      bit64_4.0.5      
[41] timechange_0.3.0  rmarkdown_2.27    httr_1.4.7        bit_4.0.5        
[45] hms_1.1.3         evaluate_0.24.0   knitr_1.48        viridisLite_0.4.2
[49] mgcv_1.9-1        rlang_1.1.4       glue_1.7.0        reactable_0.4.4  
[53] xml2_1.3.6        renv_1.0.4        rstudioapi_0.16.0 vroom_1.6.5      
[57] jsonlite_1.8.8    R6_2.5.1