Using the PVWatts API with R

R
energy
API
solar
Author
Published

December 16, 2024

Introduction

This post shows an example of how you can estimate electricity production from a PV array using the PVWatts API from NREL. The PVWatts model uses typical weather data for a specified location to estimate performance of PV systems with a variety of configurations. I’ll show an example of how to use the API for a location in Denver, CO.

Note if you don’t want to code there is a nice PVWatts calculator online where you can get the same estimates.

Load libraries
library(glue)
library(here)
library(dplyr)
library(ggplot2)
theme_set(theme_grey(base_size = 16)) # increase default ggplot font size
library(httr)
library(plotly)
1
Project organization
2
Data wrangling
3
Plotting
4
API functions
5
Interactive plots

Getting data from the API

First you’ll need to obtain a free API key for the NREL developer network at https://developer.nrel.gov/signup/

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")`.

You can view the full PVWatts API documentation at : https://developer.nrel.gov/docs/solar/pvwatts/v8/

There are some required input parameters for the API (see API documentation for more details):

  • *format” : json or xml

  • api_key : Your NREL developer API key

  • system_capacity : “Nameplate capacity” [kWh]

  • module_type

  • losses : System losses (percent)

  • array_type

  • tilt : Tilt angle (degrees)

  • azimuth : Azimuth angle (degrees)

There are also a number of additional optional parameters that can be passed in.

Input parameters

First we need to make a list of requuired input parameters to use:

Code
# set defaults for required parameters
req_defaults <- list(system_capacity = 4, # Nameplate capacity (kW).
       module_type = 0, # Standard
       losses = 14,     # System losses (percent)
       array_type = 1,  #   Fixed - Roof Mounted
       tilt = 30,       # 30 deg tilt
       azimuth = 180,   # facing South
       address = "Denver,co",
       timeframe = "hourly")

API request

Then we can build the full request url from the input parameters and make the API request using the {httr} package (Wickham 2023):

Make API Reqeust
api_key <- Sys.getenv("AFDC_KEY")

base_url <- "https://developer.nrel.gov/api/pvwatts/v8.json"

# build query string from defaults list
query_str <- ""
for (i in seq_along(req_defaults)) {
  a <- paste0("&", names(req_defaults)[i], "=", req_defaults[i])
  query_str <- paste0(query_str, a)
}

# full API request url
req_url <- paste0(base_url, "?api_key=", api_key, query_str)

# make request
resp <- httr::GET(req_url)

# check status code (should = 200 if it worked)
glue("API response status code: {resp$status_code}")
API response status code: 200

Parse the response object

Next we parse the json response into a list:

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

str(resp_parsed)
List of 7
 $ inputs      :List of 8
  ..$ system_capacity: chr "4"
  ..$ module_type    : chr "0"
  ..$ losses         : chr "14"
  ..$ array_type     : chr "1"
  ..$ tilt           : chr "30"
  ..$ azimuth        : chr "180"
  ..$ address        : chr "Denver,co"
  ..$ timeframe      : chr "hourly"
 $ errors      : list()
 $ warnings    : list()
 $ version     : chr "8.3.0"
 $ ssc_info    :List of 3
  ..$ version: int 280
  ..$ build  : chr "Linux 64 bit GNU/C++ Oct 18 2023 07:13:03"
  ..$ module : chr "pvwattsv8"
 $ station_info:List of 11
  ..$ lat                : num 39.7
  ..$ lon                : num -105
  ..$ elev               : num 1604
  ..$ tz                 : num -7
  ..$ location           : chr "484137"
  ..$ city               : chr ""
  ..$ state              : chr "Colorado"
  ..$ country            : chr "United States"
  ..$ solar_resource_file: chr "484137.csv"
  ..$ distance           : int 1516
  ..$ weather_data_source: chr "NSRDB PSM V3 GOES tmy-2020 3.2.0"
 $ outputs     :List of 16
  ..$ ac_monthly     : num [1:12] 454 491 608 593 601 ...
  ..$ poa_monthly    : num [1:12] 136 149 191 193 200 ...
  ..$ solrad_monthly : num [1:12] 4.38 5.3 6.15 6.45 6.44 ...
  ..$ dc_monthly     : num [1:12] 476 515 639 624 631 ...
  ..$ ac_annual      : num 6588
  ..$ solrad_annual  : num 5.84
  ..$ capacity_factor: num 18.8
  ..$ ac             : num [1:8760] 0 0 0 0 0 ...
  ..$ poa            : num [1:8760] 0 0 0 0 0 ...
  ..$ dn             : num [1:8760] 0 0 0 0 0 0 0 0 701 870 ...
  ..$ dc             : num [1:8760] 0 0 0 0 0 ...
  ..$ df             : num [1:8760] 0 0 0 0 0 0 0 0 37 49 ...
  ..$ tamb           : num [1:8760] -2 -2 -2 -2 -2 -3 -3 -2 0 3 ...
  ..$ tcell          : num [1:8760] -2 -2 -2 -2 -2 ...
  ..$ wspd           : num [1:8760] 2.2 2.3 2.2 2.2 2.2 2.4 2.9 3.9 4.7 5.2 ...
  ..$ alb            : num [1:8760] 0.12 0.12 0.12 0.12 0.12 0.12 0.12 0.12 0.12 0.12 ...
Code
outputs <- resp_parsed$outputs

Output Format

Depending on the timeframe input, the API returns monthly and/or hourly data. The data returned does not include date or time vectors, so I add them manually assuming the data is for 365 days starting on Jan 1 00:00.

The fields returned include the estimated output of the PV array, as well as data on the solar radiation.

Data Analysis/Visualization

Annual

The estimated total annual (AC) output of this array is 6588 kWh.

Monthly

I’ll put the monthly data into a separate dataframe so we can easily work with it.

Code
df_monthly <- tibble::tibble(output_ac = round(outputs$ac_monthly,2),
                             output_dc = round(outputs$dc_monthly,2),
                             poa = round(outputs$poa_monthly,2), 
                             solrad = round(outputs$solrad_monthly,2)
                             ) |>
  mutate(month = as.factor(seq(1:12)))

df_monthly |>
  DT::datatable(rownames = FALSE, options = list(pageLength = 5))
Table 1: Monthly data returned by PV watts API

Monthly Output of PV system

Code
df_monthly |>
  ggplot(aes(month, output_ac)) +
  geom_col(color = "gray") +
  labs(title = "Monthly (AC) Output",
       x = "Month",
       y = "[kWh]")
Figure 1: Monthly AC output of PV system estimated from PVWatts API

Hourly Data

Next I parse the hourly data (if it was requested). The hourly data response has 365*24=8760 values, so we need to add date and time fields again.

With the hourly data we can look at various aspects such as hourly output for the whole year (Figure 2), average hourly output for each month (Figure 3), and estimated hourly output for a single day (Figure 4).

Note

Note that the hourly output is in units of W, not kWh

Make dataframe of hourly data
# make hour and yearday vectors
hours_day  <- seq(1:24)
hours_year <- rep(hours_day,365)
id_seq <- seq(1:8760)
yday <- (id_seq %/% 24) + 1

date <- as.Date(yday, origin = "2024-01-01") - 1

df_hourly <- tibble(output_ac = outputs$ac,
                    output_dc = outputs$dc,
                    poa = outputs$poa,
                    hour = hours_year, 
                    yday = yday, 
                    date = date
                    ) |>
  mutate(month = as.factor(lubridate::month(date)))

df_hourly |> head(20) |> DT::datatable(rownames = FALSE, options = list(pageLength = 5))
Table 2: Example of hourly data from PVWatts API

Boxplot of hourly output

Code
df_hourly |>
  ggplot(aes(hour, output_ac, group = hour)) +
  geom_boxplot() +
  labs(x = "Hour",
       y = "W",
       title = "Hourly AC Output of PV System [W]")
Figure 2: Boxplots of hourly ac output for entire year

Average hourly output for each month

Code
df_hourly_summarized <- df_hourly |>
  group_by(month, hour) |>
  summarise(mean_output = round(mean(output_ac),2),
            median_output = round(median(output_ac),2),
            .groups = "drop")


g <- df_hourly_summarized |>
#  filter(hour > 4 & hour < 21) |>
  ggplot(aes(hour, mean_output, group = month)) +
  geom_line(aes(color = month), linewidth = 1.5) +
  labs(x = "Hour",
       y = "W",
       title = "Avg Hourly Output For Each Month")

ggplotly(g)
Figure 3: Average hourly AC output for each month

Hourly Output for a Single Day

Code
# plot hourly output for one day
df_hourly |>
  filter(yday == 135 ) |>
  ggplot(aes(hour, output_ac)) +
  geom_line(linewidth = 2) +
  labs(title = "Hourly Output for One Day",
       x = "Hour",
       y = "AC Output [W]")
Figure 4: Estimated hourly output for a single day

Summary and Next Steps

In this post I showed an example of how to use the PVWatts API from NREL to estimate the output of a PV system, in R. I hope you found this interesting and useful, and maybe inspired to start your own explorations.

SessionInfo

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

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 httr_1.4.7    ggplot2_3.5.1 dplyr_1.1.4   here_1.0.1   
[6] glue_1.8.0   

loaded via a namespace (and not attached):
 [1] gtable_0.3.6      jsonlite_1.8.9    crayon_1.5.3      compiler_4.4.1   
 [5] renv_1.0.9        tidyselect_1.2.1  jquerylib_0.1.4   tidyr_1.3.1      
 [9] scales_1.3.0      yaml_2.3.10       fastmap_1.2.0     R6_2.5.1         
[13] labeling_0.4.3    generics_0.1.3    curl_6.0.0        knitr_1.49       
[17] htmlwidgets_1.6.4 tibble_3.2.1      munsell_0.5.1     rprojroot_2.0.4  
[21] lubridate_1.9.4   bslib_0.8.0       pillar_1.9.0      rlang_1.1.4      
[25] DT_0.33           utf8_1.2.4        cachem_1.1.0      xfun_0.49        
[29] sass_0.4.9        lazyeval_0.2.2    timechange_0.3.0  viridisLite_0.4.2
[33] cli_3.6.3         withr_3.0.2       magrittr_2.0.3    crosstalk_1.2.1  
[37] digest_0.6.37     grid_4.4.1        rstudioapi_0.17.1 lifecycle_1.0.4  
[41] vctrs_0.6.5       evaluate_1.0.1    data.table_1.16.4 farver_2.1.2     
[45] fansi_1.0.6       colorspace_2.1-1  rmarkdown_2.29    purrr_1.0.2      
[49] tools_4.4.1       pkgconfig_2.0.3   htmltools_0.5.8.1

References

Wickham, Hadley. 2023. “Httr: Tools for Working with URLs and HTTP.” https://CRAN.R-project.org/package=httr.