Analyzing Trends in Heating and Cooling Degree days using R

energy
R
weather
climate
Author
Published

December 12, 2023

Modified

December 20, 2024

Introduction

Degree days are useful as a measure of building heating and cooling demands. A degree day is calculated as the difference between the average temperature (the average of the high and low temperature for the day) and a reference temperature (in the US 65°F is used). For example, if the average temperature today is 40°F, that would be 25 heating degree days (HDD). A summer day with an average temperature of 85°F would have 20 cooling degree days (CDD). Degree days are usually well correlated with the amount of energy used to heat or cool a home.

I was interested in obtaining and analyzing degree day data; in particular I wanted to see if there were any noticeable trends over time. Given an overall increase in earth’s average temperature due to climate change, I would hypothesize that there might be an overall increase in CDD and a decrease in HDD.

Changes in heating or cooling degree days would have implications for the amount of energy needed in the future to heat and cool residential or commercial buildings, resulting changes in demand on the electric grid, and implications for related carbon emissions (either for the power grid or from burning fossil fuels to heat buildings).

Data

I obtained heating and cooling degree day data from the U.S. Energy Information Administration for the US. Note these data are weighted by population, to reflect the effect of both temperature and population on energy demands for cooling and heating. You can see details of how the EIA data are calculated here.

I’ll only use years we have complete data for (1997-2022). Table 1 shows the data after being loaded and cleaned up.

Code
region <- "u_s"

dd <- read_csv(paste0('data/EIA_DegreeDays_Monthly_',region,'.csv'), 
               skip = 4,
               show_col_types = FALSE) |>
  janitor::clean_names() |>
  rename(CDD = paste0('cooling_degree_days_',region,
                      '_cooling_degree_days_cooling_degree_days')) |>
  rename(HDD = paste0('heating_degree_days_',region,
                      '_heating_degree_days_heating_degree_days')) |>
  mutate(date = lubridate::my(month)) |>
  select(-month) |>
  mutate(month = lubridate::month(date)) |>
  mutate(year = lubridate::year(date)) |>
  filter(year > 1996, year < 2023) # keep only complete years
Code
dd |>
  DT::datatable(options = list(pageLength = 5), rownames = FALSE)
Table 1: Table of monthly degree day data for US

I’ll also make a dataframe of the yearly totals (Table 2)

Code
dd_yearly <- dd |>
  filter(year > 1996) |>
  group_by(year) |>
  summarise(HDD = sum(HDD, na.rm = TRUE),
            CDD = sum(CDD, na.rm = TRUE)
            )

dd |>
  DT::datatable(options = list(pageLength = 5), rownames = FALSE)
Table 2: Table of yearly degree days for the US

Analysis

Heating Degree Days

Figure 1 shows the distribution (using a boxplot) of US heating degree days for each month. Not surprisingly HDD tends to be higher in winter months, although there is a decent amount of variability between years.

HDD Per Month

Code
dd |>
  mutate(month_name = lubridate::month(date, label = TRUE)) |>
  ggplot(aes(month_name, HDD, group = month_name)) +
  geom_boxplot() +
  labs(title = 'Monthly Heating Degree Days for US (1997-2022)',
       x = 'Month',
       y = "Heating Degree Days")
Figure 1: Boxplot of US heating degree days for each month

Cooling Degree Days

CDD Per Month

Figure 5 shows the distribution of US heating degree days for each month. Not surprisingly CDD tends to be higher in summer months, although there is a decent amount of variability between years.

Code
dd |>
  mutate(month_name = lubridate::month(date, label = TRUE)) |>
  ggplot(aes(month_name, CDD, group = month_name)) +
  geom_boxplot() +
  labs(title = 'Monthly Cooling Degree Days for US',
       x = 'Month',
       y = "Cooling Degree Days")
Figure 5: Boxplot of US cooling degree days for each month

Summary

Annual and monthly heating and cooling degree days for the US 1997-2022 were analyzed. A linear regression was applied to the annual data, and to each month, to determine if there was a trend. Model fits with a p-value less than 0.05 were considered significant.

  • There is a negative trend in annual HDD (Figure 2, Table 3), with HDD decreasing at a rate of 13.2 HDD per year.

  • There is a positive trend in annual CDD (Figure 6, Table 6), with CDD increasing at a rate of 12.28 CDD per year.

  • There are significant negative trends in HDD for the months of June, August, September, and October (Table 5).

  • There are significant positive trends in CDD for the months of July, August, September, October, and December (Table 7).

Future Research Questions

Implications for energy use

  • How much actual energy use (kWh, therms, etc.) corresponds to a degree day? This will depend on many factors, but we could make a rough estimate by comparing degree days to energy demand or consumption.

SessionInfo

To enhance reproducibility, my sessionInfo is included below:

Code
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   DT_0.33         broom_1.0.5     janitor_2.2.0  
 [5] lubridate_1.9.3 forcats_1.0.0   stringr_1.5.1   dplyr_1.1.4    
 [9] purrr_1.0.2     readr_2.1.5     tidyr_1.3.1     tibble_3.2.1   
[13] ggplot2_3.5.1   tidyverse_2.0.0

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

References

Robinson, David, Alex Hayes, and Simon Couch. 2023. “Broom: Convert Statistical Objects into Tidy Tibbles.” https://CRAN.R-project.org/package=broom.
Wickham, Hadley, and Lionel Henry. 2023. “Purrr: Functional Programming Tools.” https://CRAN.R-project.org/package=purrr.
Wickham, Hadley, Davis Vaughan, and Maximilian Girlich. 2023. “Tidyr: Tidy Messy Data.” https://CRAN.R-project.org/package=tidyr.