U.S. National Weather Service

Step by Step Process through the U.S. NWS API for a 7 Day Forcast

Author

Ana Collado

The U.S. National Weather Service is part of a federal agency that monitors climate (longterm weather averages), provides weather forecasts, warnings, and general information to the public. Their data is openly available for use through its API for developers.

Exploration Goal: Obtain 7-Day Forecast for New York

The NWS website states the grid forecast can be obtained with coordinates. The longitude and latitude points for New York are 40.730610, -73.935242 respectively.

·Load Libraries:

library(httr2)
library(tidyverse)
library(repurrrsive)
library(ggplot2)

·Assigning Values:

api_base_url <-  "https://api.weather.gov/"
ny_coords <- "https://api.weather.gov/points/40.7128,-74.0060"

·Requesting from the API:

ask_api<- request(ny_coords) %>% 
  req_perform()

api_response<- 
  resp_body_json(ask_api) %>% 
  glimpse()
List of 5
 $ @context  :List of 2
  ..$ : chr "https://geojson.org/geojson-ld/geojson-context.jsonld"
  ..$ :List of 17
  .. ..$ @version        : chr "1.1"
  .. ..$ wx              : chr "https://api.weather.gov/ontology#"
  .. ..$ s               : chr "https://schema.org/"
  .. ..$ geo             : chr "http://www.opengis.net/ont/geosparql#"
  .. ..$ unit            : chr "http://codes.wmo.int/common/unit/"
  .. ..$ @vocab          : chr "https://api.weather.gov/ontology#"
  .. ..$ geometry        :List of 2
  .. ..$ city            : chr "s:addressLocality"
  .. ..$ state           : chr "s:addressRegion"
  .. ..$ distance        :List of 2
  .. ..$ bearing         :List of 1
  .. ..$ value           :List of 1
  .. ..$ unitCode        :List of 2
  .. ..$ forecastOffice  :List of 1
  .. ..$ forecastGridData:List of 1
  .. ..$ publicZone      :List of 1
  .. ..$ county          :List of 1
 $ id        : chr "https://api.weather.gov/points/40.7128,-74.006"
 $ type      : chr "Feature"
 $ geometry  :List of 2
  ..$ type       : chr "Point"
  ..$ coordinates:List of 2
  .. ..$ : num -74
  .. ..$ : num 40.7
 $ properties:List of 17
  ..$ @id                : chr "https://api.weather.gov/points/40.7128,-74.006"
  ..$ @type              : chr "wx:Point"
  ..$ cwa                : chr "OKX"
  ..$ forecastOffice     : chr "https://api.weather.gov/offices/OKX"
  ..$ gridId             : chr "OKX"
  ..$ gridX              : int 33
  ..$ gridY              : int 35
  ..$ forecast           : chr "https://api.weather.gov/gridpoints/OKX/33,35/forecast"
  ..$ forecastHourly     : chr "https://api.weather.gov/gridpoints/OKX/33,35/forecast/hourly"
  ..$ forecastGridData   : chr "https://api.weather.gov/gridpoints/OKX/33,35"
  ..$ observationStations: chr "https://api.weather.gov/gridpoints/OKX/33,35/stations"
  ..$ relativeLocation   :List of 3
  .. ..$ type      : chr "Feature"
  .. ..$ geometry  :List of 2
  .. ..$ properties:List of 4
  ..$ forecastZone       : chr "https://api.weather.gov/zones/forecast/NYZ072"
  ..$ county             : chr "https://api.weather.gov/zones/county/NYC061"
  ..$ fireWeatherZone    : chr "https://api.weather.gov/zones/fire/NYZ212"
  ..$ timeZone           : chr "America/New_York"
  ..$ radarStation       : chr "KDIX"

The glimpse() function shows all the groups of data for our chosen coordinates. Since our goal is to pull the weeks predicted weather, the information needed is stored in the endpoint named $forecast .

#Assigning endpoint value
forecast <- "https://api.weather.gov/gridpoints/OKX/33,35/forecast"

#Requesting from the API again
ask_again <- 
  request(forecast) %>% 
  req_perform()

response_again <- 
  resp_body_json(ask_again) %>% 
  glimpse()
List of 4
 $ @context  :List of 2
  ..$ : chr "https://geojson.org/geojson-ld/geojson-context.jsonld"
  ..$ :List of 5
  .. ..$ @version: chr "1.1"
  .. ..$ wx      : chr "https://api.weather.gov/ontology#"
  .. ..$ geo     : chr "http://www.opengis.net/ont/geosparql#"
  .. ..$ unit    : chr "http://codes.wmo.int/common/unit/"
  .. ..$ @vocab  : chr "https://api.weather.gov/ontology#"
 $ type      : chr "Feature"
 $ geometry  :List of 2
  ..$ type       : chr "Polygon"
  ..$ coordinates:List of 1
  .. ..$ :List of 5
 $ properties:List of 7
  ..$ units            : chr "us"
  ..$ forecastGenerator: chr "BaselineForecastGenerator"
  ..$ generatedAt      : chr "2024-11-29T03:33:06+00:00"
  ..$ updateTime       : chr "2024-11-29T03:30:49+00:00"
  ..$ validTimes       : chr "2024-11-28T21:00:00+00:00/P7DT16H"
  ..$ elevation        :List of 2
  .. ..$ unitCode: chr "wmoUnit:m"
  .. ..$ value   : num 2.13
  ..$ periods          :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14
  .. ..$ :List of 14

Now, we’ll unnest and rectangle the data.

·Converting to Tibble:

response_df<- tibble(response_again)
str(response_df) %>% 
  print()

I encountered many errors in unnesting because of the structure of the data. When using glimpse() or print() the data seeemd as though I would point to $properties to unnest.

But in using view() the data looked different. str() shows whats inside in a very readable sort of way, but view() is perfect for identifying/naming the items in code. This format was necessary to point to the appropriate items to unnest.

See below:

screenshot of forecast tibble

str(response_df$response_again[[4]]) %>% 
  glimpse()

(inserting screenshot of output to save you scrolling time)

·Selecting the Lists to Unnest


forecast<- tibble(response_df$response_again[[4]])

Once the main list is selected, using the $ button allows for items of interest to be selected and by extension the appropriate characters are used.

“this will save a lot of time and error messages” - my past self

forecast <- tibble(forecast$`response_df$response_again[[4]]`[[7]])

forecast<- forecast %>%
  unnest_wider(`forecast$\`response_df$response_again[[4]]\`[[7]]`) %>% 
  print()
## # A tibble: 14 × 14
##    number name           startTime endTime isDaytime temperature temperatureUnit
##     <int> <chr>          <chr>     <chr>   <lgl>           <int> <chr>          
##  1      1 Tonight        2024-11-… 2024-1… FALSE              37 F              
##  2      2 Friday         2024-11-… 2024-1… TRUE               46 F              
##  3      3 Friday Night   2024-11-… 2024-1… FALSE              31 F              
##  4      4 Saturday       2024-11-… 2024-1… TRUE               40 F              
##  5      5 Saturday Night 2024-11-… 2024-1… FALSE              31 F              
##  6      6 Sunday         2024-12-… 2024-1… TRUE               38 F              
##  7      7 Sunday Night   2024-12-… 2024-1… FALSE              31 F              
##  8      8 Monday         2024-12-… 2024-1… TRUE               41 F              
##  9      9 Monday Night   2024-12-… 2024-1… FALSE              32 F              
## 10     10 Tuesday        2024-12-… 2024-1… TRUE               39 F              
## 11     11 Tuesday Night  2024-12-… 2024-1… FALSE              31 F              
## 12     12 Wednesday      2024-12-… 2024-1… TRUE               39 F              
## 13     13 Wednesday Nig… 2024-12-… 2024-1… FALSE              32 F              
## 14     14 Thursday       2024-12-… 2024-1… TRUE               40 F              
## # ℹ 7 more variables: temperatureTrend <chr>,
## #   probabilityOfPrecipitation <list>, windSpeed <chr>, windDirection <chr>,
## #   icon <chr>, shortForecast <chr>, detailedForecast <chr>

·Isolating Columns of Interest

forecast<- forecast %>% 
  select(number, name, startTime, temperature, temperatureUnit, shortForecast) %>% 
  print()
# A tibble: 14 × 6
   number name            startTime    temperature temperatureUnit shortForecast
    <int> <chr>           <chr>              <int> <chr>           <chr>        
 1      1 Tonight         2024-11-28T…          37 F               Mostly Clear 
 2      2 Friday          2024-11-29T…          46 F               Sunny then C…
 3      3 Friday Night    2024-11-29T…          31 F               Mostly Clear 
 4      4 Saturday        2024-11-30T…          40 F               Sunny        
 5      5 Saturday Night  2024-11-30T…          31 F               Mostly Clear 
 6      6 Sunday          2024-12-01T…          38 F               Mostly Sunny 
 7      7 Sunday Night    2024-12-01T…          31 F               Mostly Clear 
 8      8 Monday          2024-12-02T…          41 F               Mostly Sunny 
 9      9 Monday Night    2024-12-02T…          32 F               Partly Cloudy
10     10 Tuesday         2024-12-03T…          39 F               Mostly Sunny 
11     11 Tuesday Night   2024-12-03T…          31 F               Partly Cloudy
12     12 Wednesday       2024-12-04T…          39 F               Mostly Sunny 
13     13 Wednesday Night 2024-12-04T…          32 F               Chance Rain …
14     14 Thursday        2024-12-05T…          40 F               Chance Rain …

·Finalizing the Data Frame Clean

$startTime is in a character datatype although the datapoints are technically a date and time span. While its completely possible to convert to reflect this, I will instead use the char type to select for “dates” and create a column that I can group into days of the week.

I indeed had to come back to this code to fix it! The API will respond very time this code is run with my key, the data will change to reflect the time and the new forecast. It is absolutely necessary to use the weekdays() to auto populate day of the week depending on the ever changing $startTime

forecast$startTime <- as.Date(forecast$startTime)

forecast <- forecast %>%
  mutate(day= weekdays(startTime)) %>% 
  arrange(startTime) %>% 
  group_by(startTime)

·Creating a Forecast Visualization using ggplot

forecast %>%
  ggplot(aes(day, temperature, fill= temperature))+
  geom_line(linewidth = 50)+
  facet_wrap(~day, scales = "free_x",
             ncol = 7, axis.labels = "all_y", drop = TRUE)+
  theme_minimal()+
  theme(
    panel.background = element_rect(fill = "#FFFFFF"),
    panel.grid = element_line(colour ="#FFFFFF"))+
  labs(title = "7 DAY FORECAST", caption = "")

·Addressing Issues

The plot is close to what I intend, but there are several issues here that need to be addressed:

  1. The API request outputs current forecast, the day of the week is not specific enough.

  2. The filled blocks should be color graded to better depict the high and low of the day

  3. The day of the week appears on both the top and bottom

  4. Y axis label doesn’t specify temperature unit

  5. Weather forecast window has an in between where the beginning of the window has only once observation (night) and the end of the window only has one observation (day). using geom_line is not efficient because it requires two points within the grouping.
    EX:

#1 and #3 can be fixed by changing the data type of $startTime after its adjusted, the column can be used as the x in the ggplot base. Then the plot will show the day of the week and the date instead of being redundant. The column name will appear in the x axis now but we can remove that.

#4 can be solved by adding a ylab() line to the ggplot code. #2 however becomes a little more complicated. geom_line() does not support “fill”. Its possible to use geom_point() in shape = 15 (square) and pair it with geom_line(), then adjust the size and line widths to match in a way that seems like one large box. But even so the gradient wouldn’t work.

A possible fix pivoting wider so that the observations in $name that predict “night” temperature are in their own column. This way it would be easier to plot a range. But, for now, the visualization gets the point across.

#5 The API returns a single weather data point when the forecast window moves. geom_tile() was an easy fix, but the visualization is not as intended.

·ggPlot redo

·Useful References