Murfreesboro, Tennessee, can get pretty windy sometimes. Here’s a graph of the maximum wind gust recorded in Murfreesboro on each day for the past 730 days, minus the two most recent days, for which data usually aren’t available yet. The graph is interactive; clicking or tapping on it will show you the value for a particular date.

Below the graph is a table showing each day’s maximum wind gust along with the day’s minimum and maximum temperature and general weather conditions (clear sky, overcast, etc.) The table is interactive, too. You can sort or filter by any column, do a global complete or partial search, and even rearrange the columns. If you want to keep what you see, you can copy it or export it in a variety of formats.

The data come from Open-Meteo, a meteorological data source that is free for noncommercial purposes. I used an R script to extract and process the data, the Plotly and DT packages for R to make the graph and table, and R Markdown to publish the page on RPubs.com. The code is below. Dr. Greg Martin’s excellent YouTube video on the DT package helped me figure out the table. I used ChatGPT to work out some bugs in the code.

Open-Meteo’s API offers data on many other weather variables, and for other locations and time periods. Sharp-eyed visitors will note that the code downloads data for all the way back to 1996, the year I moved to Murfreesboro. That would be too much data for the free version of the DT package to My main goal, here, was to learn how to use the Open-Meteo API and the DT package, both of which are new to me. I’ll be incorporating both into a data skills course I teach for Middle Tennessee State University’s School of Journalism and Strategic Media.

# Historical weather data 
# Murfreesboro, TN, past 365 * 30 days

library(tidyverse)
library(httr)
library(jsonlite)
library(tibble)
library(tidyr)
library(lubridate)
library(plotly)
library(DT)

base_url <- "https://archive-api.open-meteo.com/v1/archive"
start_date <- Sys.Date() - (365 * 30)
end_date <- Sys.Date()

url <- paste0(
  base_url,
  "?latitude=35.82913501174874",
  "&longitude=-86.50940847281211",
  "&start_date=",
  start_date,
  "&end_date=",
  end_date,
  "&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,windgusts_10m_max,weathercode,uv_index_max,sunrise,sunset",
  "&timezone=America%2FChicago"
)

# Fetch and process
response <- fromJSON(content(GET(url), "text"))
weather_data <- response$daily

df <- as_tibble(weather_data) %>%
  mutate(
    MaxInF = round((temperature_2m_max * 9 / 5) + 32, 1),
    MinInF = round((temperature_2m_min * 9 / 5) + 32, 1),
    Weather = case_when(
      weathercode == 0 ~ "Clear sky",
      weathercode == 1 ~ "Mainly clear",
      weathercode == 2 ~ "Partly cloudy",
      weathercode == 3 ~ "Overcast",
      weathercode == 45 ~ "Fog",
      weathercode == 48 ~ "Depositing rime fog",
      weathercode == 51 ~ "Light drizzle",
      weathercode == 53 ~ "Moderate drizzle",
      weathercode == 55 ~ "Dense drizzle",
      weathercode == 61 ~ "Slight rain",
      weathercode == 63 ~ "Moderate rain",
      weathercode == 65 ~ "Heavy rain",
      weathercode == 71 ~ "Slight snow fall",
      weathercode == 73 ~ "Moderate snow fall",
      weathercode == 75 ~ "Heavy snow fall",
      weathercode == 80 ~ "Rain showers (slight)",
      weathercode == 81 ~ "Rain showers (moderate)",
      weathercode == 82 ~ "Rain showers (violent)",
      weathercode == 95 ~ "Thunderstorm (light)",
      weathercode == 96 ~ "Thunderstorm + hail",
      weathercode == 99 ~ "Thunderstorm + heavy hail",
      TRUE ~ "Unknown"
    )
  )

# Filter for the most recent 365 * 2 days
df_recent <- df %>%
  mutate(date = as.Date(time)) %>%
  filter(date >= Sys.Date() - 730)

# Convert wind gusts from km/h to mph
df_recent <- df_recent %>%
  mutate(WindGustsMPH = round(windgusts_10m_max / 1.609344, 1))

# Plot with Plotly
(WindPlot <- plot_ly(
  data = df_recent,
  x = ~ date,
  y = ~ WindGustsMPH,
  type = 'scatter',
  mode = 'lines',
  line = list(color = 'royalblue'),
  hoverinfo = 'text',
  text = ~ paste0("Date: ", date, "<br>Wind Gust: ", WindGustsMPH, " mph")
) %>%
    layout(
    title = list(
      text = "Daily Maximum Wind Gusts in Murfreesboro, TN (Last 730 Days)",
      y = 0.95  # optional: move title down a bit
    ),
    xaxis = list(title = "Date"),
    yaxis = list(title = "Wind Gusts (mph)"),
    hovermode = "closest",
    margin = list(t = 100)  # Add top margin (100px)
  )

Wind <- df_recent %>%
  select(date, WindGustsMPH, MinInF, MaxInF, Weather)

indivisible(WindTable <- datatable(
  Wind,
  colnames = c("Date", "Max gust (mph)", "Min temp (F)", "Max temp (F)", "Weather"),
  escape = FALSE,
  extensions = c("Buttons", "ColReorder", "FixedHeader", "Scroller", "KeyTable"),
  options = list(
    dom = "Bfrtip",
    buttons = c("copy", "csv", "excel", "pdf", "print"),
    colReorder = TRUE,
    fixedHeader = TRUE,
    scrollY = 400,
    scroller = TRUE,
    keys = TRUE,
    order = list(list(1, "desc")),
    searchCols = list(NULL, NULL, NULL, NULL, NULL)
  ),
  filter = "top",
  rownames = FALSE,
  caption = "Murfreesboro, TN, wind gust data, from Open-Meteo"
))

WindPlot

WindTable