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 sizelibrary(httr)library(plotly)
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")`.
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 listquery_str <-""for (i inseq_along(req_defaults)) { a <-paste0("&", names(req_defaults)[i], "=", req_defaults[i]) query_str <-paste0(query_str, a)}# full API request urlreq_url <-paste0(base_url, "?api_key=", api_key, query_str)# make requestresp <- httr::GET(req_url)# check status code (should = 200 if it worked)glue("API response status code: {resp$status_code}")
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.
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
# plot hourly output for one daydf_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]")
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.
---title: "Using the PVWatts API with R"date: 2024-12-16#date-modified: todayimage: image.pngformat: html: code-fold: show code-tools: true toc: true fig-width: 9 fig-height: 7 tbl-cap-location: bottomcode-annotations: hovereditor: visualcategories: [R,energy,API, solar]freeze: truedraft: falsebibliography: references.bib---# IntroductionThis post shows an example of how you can estimate electricity production from a PV array using the [PVWatts API](https://developer.nrel.gov/docs/solar/pvwatts/v8/) 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](https://pvwatts.nrel.gov/) online where you can get the same estimates.```{r}#| label: Load Libraries#| code-summary: Load libraries#| message: falselibrary(glue)library(here) # <1>library(dplyr) # <2>library(ggplot2) # <3>theme_set(theme_grey(base_size =16)) # increase default ggplot font sizelibrary(httr) # <4>library(plotly) # <5>```1. Project organization2. Data wrangling3. Plotting4. API functions5. Interactive plots# Getting data from the APIFirst you'll need to obtain a free API key for the NREL developer network at <https://developer.nrel.gov/signup/>::: callout-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 parametersFirst we need to make a list of requuired input parameters to use:```{r}#| label: Make defaults for required parameters# set defaults for required parametersreq_defaults <-list(system_capacity =4, # Nameplate capacity (kW).module_type =0, # Standardlosses =14, # System losses (percent)array_type =1, # Fixed - Roof Mountedtilt =25, # 30 deg tiltazimuth =180, # facing Southaddress ="Denver,co",timeframe ="hourly")```## API requestThen we can build the full request url from the input parameters and make the API request using the {httr} package [@httr]:```{r}#| label: Make API Reqeust#| code-fold: show#| code-summary: Make API Reqeustapi_key <-Sys.getenv("AFDC_KEY")base_url <-"https://developer.nrel.gov/api/pvwatts/v8.json"# build query string from defaults listquery_str <-""for (i inseq_along(req_defaults)) { a <-paste0("&", names(req_defaults)[i], "=", req_defaults[i]) query_str <-paste0(query_str, a)}# full API request urlreq_url <-paste0(base_url, "?api_key=", api_key, query_str)# make requestresp <- httr::GET(req_url)# check status code (should = 200 if it worked)glue("API response status code: {resp$status_code}")```## Parse the response objectNext we parse the json response into a list:```{r}#| label: Parse-responseresp_parsed <- jsonlite::fromJSON(httr::content(resp,"text"))str(resp_parsed)outputs <- resp_parsed$outputs```## Output FormatDepending 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## AnnualThe estimated total annual (AC) output of this array is `r round(outputs$ac_annual)` kWh.## MonthlyI'll put the monthly data into a separate dataframe so we can easily work with it.```{r}#| label: tbl-monthly-dataframe#| tbl-cap: Monthly data returned by PV watts APIdf_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))```### Monthly Output of PV system```{r}#| label: fig-monthly-output#| fig-cap: Monthly AC output of PV system estimated from PVWatts APIdf_monthly |>ggplot(aes(month, output_ac)) +geom_col(color ="gray") +labs(title ="Monthly (AC) Output",x ="Month",y ="[kWh]")```## Hourly DataNext 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 (@fig-hourly-output-boxplot), average hourly output for each month (@fig-avg-hourly-output-per-month), and estimated hourly output for a single day (@fig-hourly-single-day).::: callout-noteNote that the hourly output is in units of W, not kWh:::```{r}#| label: tbl-hourly-dataframe#| tbl-cap: Example of hourly data from PVWatts API#| code-summary: Make dataframe of hourly data# make hour and yearday vectorshours_day <-seq(1:24)hours_year <-rep(hours_day,365)id_seq <-seq(1:8760)yday <- (id_seq %/%24) +1date <-as.Date(yday, origin ="2024-01-01") -1df_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))```### Boxplot of hourly output```{r}#| label: fig-hourly-output-boxplot#| fig-cap: Boxplots of hourly ac output for entire yeardf_hourly |>ggplot(aes(hour, output_ac, group = hour)) +geom_boxplot() +labs(x ="Hour",y ="W",title ="Hourly AC Output of PV System [W]")```### Average hourly output for each month```{r}#| label: fig-avg-hourly-output-per-month#| fig-cap: Average hourly AC output for each monthdf_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)```### Hourly Output for a Single Day```{r}#| label: fig-hourly-single-day#| fig-cap: Estimated hourly output for a single day# plot hourly output for one daydf_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]")```# SummaryIn 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 are inspired to start your own explorations.# SessionInfo::: {.callout-tip collapse="true"}## Expand for Session Info```{r, echo = FALSE}sessionInfo()```:::