Rainfall maps code
Fri, Sep 14, 2018
4-minute read
This code builds the county-level map in this post.
A note of caution: this code takes a long time to run on a powerful iMAC. Depending on your machine it may take even longer.
Any geography may be substituted for counties.
rm(list = ls())
library(tidyverse)
library(sf)
library(tmap)
############################################################
# Climatological normals
############################################################
# Get 1981-2010 normals
# About: ftp://ftp.ncdc.noaa.gov/pub/data/normals/1981-2010/readme.txt
normals <- read_table("ftp://ftp.ncdc.noaa.gov/pub/data/normals/1981-2010/products/precipitation/ytd-prcp-normal.txt",
col_names = c("STNID","MONTH","d1","d2","d3","d4","d5","d6","d7",
"d8","d9","d10","d11","d12","d13","d14","d15",
"d16","d17","d18","d19","d20","d21","d22","d23",
"d24","d25","d26","d27","d28","d29","d30","d31"))
# Remove flags from the ends of the vars
remove.flag <- function(column){
str_sub(column, start = 1, end = (nchar(column)-1))
}
normals[,c(3:33)] <- apply(normals[,c(3:33)], 2, remove.flag)
normals[,c(3:33)] <- apply(normals[,c(3:33)], 2, as.numeric)
# Collapse into key-value pairs
normals <- gather(normals, key="DAY", value = "precip", -STNID, -MONTH)
# Reformat days
normals$DAY <- str_pad(str_replace(normals$DAY, "d", ""),
width = 2, side = "left", pad = "0")
# Replace negative values with NA
normals$precip[normals$precip<0] <- NA
# Make date variable
normals$date <- as.Date(paste(str_sub(Sys.Date(),1,4), normals$MONTH, normals$DAY, sep = "-"))
# Convert precip variable to inches
normals$precip <- normals$precip/100 # It's 100ths of inch
############################################################
# Current rainfall
############################################################
# Get current year rainfall
rain <- read_csv(paste0("https://www1.ncdc.noaa.gov/pub/data/ghcn/daily/by_year/",
str_sub(Sys.Date(),1,4),
".csv.gz"),
col_names = c("STNID","date","type","amount","flag1","flag2","flag3","flag4"))
# Remove flags
rain <- rain[,c(1:4)]
# Just keep precipitation
rain <- rain[rain$type=="PRCP",]
# Create date-formatted variable
rain$date <- as.Date(paste(str_sub(rain$date,1,4),
str_sub(rain$date,5,6),
str_sub(rain$date,7,8), sep = "-"))
# Summarize to cumulative precipation for the most recent date available
rain <- rain %>%
group_by(STNID) %>%
arrange(date) %>%
summarise(cum.precip = sum(amount),
date = last(date)) %>%
filter(date >= (Sys.Date()-30)) # Just keep stations updated in last 30 days
# Convert cumulative precipation to inches
rain$cum.precip <- (rain$cum.precip/10)*0.03937008 # it's 10ths of mm
# Merge norms and rainfall, keeping just the relevant columns
d <- inner_join(normals[,c("STNID","date","precip")], rain)
############################################################
# Mapping
############################################################
# Read station locations
stations <- read_table("ftp://ftp.ncdc.noaa.gov/pub/data/normals/1981-2010/station-inventories/prcp-inventory.txt",
col_names = c("STNID","latitude","longitude","elevation","state","name","gsnflag",
"hcnflag","wmoid","method"))
# Convert to spatial (simple features) object
stations <- st_as_sf(stations, coords = c("longitude","latitude"), crs = 4326)
# Get counties shape, converting to simple features object
counties <- st_as_sf(tigris::counties())
counties <- counties[as.numeric(as.character(counties$STATEFP))<57 &
counties$STATEFP!="15" & counties$STATEFP!="02",]
stations <- st_transform(stations, crs = st_crs(counties))
# Get states shape, converting to simple features object
state <- st_as_sf(tigris::states())
state <- state[as.numeric(as.character(state$STATEFP))<57 &
state$STATEFP!="15" & state$STATEFP!="02",]
# Transform the crs of stations to match that of counties
stations <- st_transform(stations, crs = st_crs(counties))
# Match stations to counties
stations <- st_intersection(stations, counties)
# Merge geocoded stations with rainfall data and summarize
cnty.sum <- inner_join(stations[,c("STNID","GEOID")], d) %>%
st_set_geometry(NULL) %>% # converts from sf object to data_frame
group_by(GEOID) %>% # group by station
summarise(norm.rain = mean(precip, na.rm = T),
rain.2018 = mean(cum.precip, na.rm = T),
firsdate = min(date), # oldest most-recent date used from county
lastdate = max(date)) # newest most-recent date used from county
# Match summarized county data to county shapefile
to.map <- left_join(counties, cnty.sum)
# Calculate this year's rainfall as a percent of normal
to.map$pOfNorm <- to.map$rain.2018/to.map$norm.rain*100
# Adjust so palette treats correctly
#to.map$p.diff[!is.na(to.map$p.diff)<100] <- to.map$p.diff[!is.na(to.map$p.diff)<100]*(-1)
to.map$pOfNorm2 <- to.map$pOfNorm-100
# Get first and last dates included in current coverage
# This is necessary because stations update info at different times
last.date <- max(to.map$lastdate[!is.na(to.map$lastdate)])
first.date <- min(to.map$firsdate[!is.na(to.map$firsdate)])
p.norm.map <- tm_shape(to.map) +
tm_polygons(border.alpha = 0) +
tm_shape(to.map) +
tm_fill(col = "pOfNorm2", palette = "RdBu",
breaks = c(-Inf,-40,-30,-20,-10,0,10,20,30,40,Inf),
labels = c("less than 60%","60 to 70%","70 to 80%",
"80 to 90%", "90 to 100%",
"100 to 110%", "110 to 120%","120 to 130%",
"130 to 140%", "Greater than 140%"),
title = "% of normal") +
tm_shape(state) +
tm_polygons(border.alpha = 1, alpha = 0) +
tm_layout(frame = F, title.position = c(.63,.93), fontfamily = "serif",
title = paste(str_sub(Sys.Date(),1,4),
"rainfall as a % of normal rainfall")) +
tm_credits(text = paste0("End dates range from ",
first.date, " to ",
last.date,"."),
position = c(.12,.15)) +
tm_credits(text = "These statistics are generated by comparing 2018",
position = c(.75,.25)) +
tm_credits(text = "year-to-date rainfall to 1981-2000 ytd normals",
position = c(.75,.23)) +
tm_credits(text = "as calculated by NOAA. 6,375 stations include",
position = c(.75,.21)) +
tm_credits(text = "both 2018 data and NOAA normals. Counties are",
position = c(.75, .19)) +
tm_credits(text = "colored with the average value for all stations",
position = c(.76,.17)) +
tm_credits(text = "in that county. Counties with no useable",
position = c(.77,.15)) +
tm_credits(text = "weather stations are left blank.",
position = c(.78,.13))
save_tmap(tm=p.norm.map, "~/path/PercentOfNorm_county.png",
width = 10)