A look at the effect of Green-Up on high temperature extremes in northen New England
In 2014, in my last year of my undergraduate Meteorology degree at Plymouth State University. I completed a Senior Research Project titled “Effects of Green-up on Spring and Summer Maximum Temperatures in Northern New Hampshire from 1989 to 2012”; I later presented this research at the 39th Northeast Storm Conference in Rutland, Vermont, USA. My original research project was done using Microsoft Excel, but used the same data sets. The goal of this project specifically was to redo the project, with an additional eight years of data, in R.
The Hubbard Brook Experimental Forest (HBEF) is a 7,800-acre valley located within the White Mountain National Forest in New Hampshire, USA. Research started by the Northern Research Station office of the US Forest Service because in 1956, the Hubbard Brook Ecosystem Study (HBES) began in 1953, and HBEF was designated a National Science Foundation Long-Term Ecological Research Forest in 1988. (Hubbard Brook Ecosystem Study, 2022; USDA Forest Service Northern Research Station, 2022)
In the sizable HBES Data Catalog, there are over 60 years of daily temperature records, and yearly documentation of tree growth in the spring. When the leaves of deciduous trees have fully bloomed is called “Green-Up”; in the mountains of northern New England, this is late May through early June. With Green-Up comes a tempering of temperature extremes, as the data below shows; there are more days where the maximum temperature is significantly greater than normal before Green-Up than after. (Hubbard Brook Ecosystem Study, n.d.)
phen1 <- read.csv('https://pasta.lternet.edu/package/data/eml/knb-lter-hbr/51/12/9f623c83fb1da7595c6d2d498bde15df')
#phen1 <- read.csv('C:/Users/ahammond1/OneDrive - University of Massachusetts/DataSci/HBEF_Phenology_longform.csv')
** Spring Phenology Codes: ** [0] No change from winter conditions, unexpanded buds only [1] Bud swelling noticeable [2] Small leaves or flowers visible, initial stages of leaf expansion, leaves about 1 cm long [3] Leaves 1/2 of final length, leaves obscure half of sky as seen through crowns [3.5] Leaves 3/4 expanded, sky mostly obscured through crown, crowns not yet in summer condition [4] Canopy appears in summer condition leaves fully expanded little sky visible through crowns
(USDA Forest Service Northern Research Station, 2021b)
phen1 <- phen1 %>% # YYYY-MM-DD to YYYY
mutate(phen1, YEAR = substr(DATE, 0, 4))
phen1$YEAR <- as.integer(phen1$YEAR) # Define as integer
phen1$DATE <- as.Date(phen1$DATE) # Define & format as a Date
phen1$DOY <- as.integer(phen1$DAY) # Redefining "DAY" to DOY (Day Of Year)
phen1$dayCode = paste(phen1$YEAR, phen1$DOY, sep = ",") # Merge Year and Day into one item, easier for coding.
phen1 <- phen1 %>%
arrange(YEAR, DOY)
SpringCal <- phenSpring %>% #Min, Mean, Max Stage by Year and Day, non-weighted, does not account for Site/Species
group_by(dayCode) %>%
summarise(minStage = min(Phenology_Stage),
averageStage = mean(Phenology_Stage),
maxStage = max(Phenology_Stage))
SpringCal <- SpringCal %>% #Split DayCode into Year and DOY
mutate(YEAR = substr(dayCode, 0, 4),
DOY = substr(dayCode,6,8) )
SpringCal$YEAR <- as.integer(SpringCal$YEAR) #Define as INT
SpringCal$DOY <- as.integer(SpringCal$DOY) #Define as INT
SpringCal <- SpringCal %>%
arrange(YEAR, DOY)
GreenUp <- SpringCal %>%
filter(`averageStage` >= 3.75) %>%
group_by(YEAR)
GreenUp = GreenUp[!duplicated(GreenUp$YEAR), ] # Remove duplicated >> keep first instance of Stage >= 3.75
GreenUp$date <- as.Date(GreenUp$DOY, origin = "2019-01-01") # Non-leap Year
GreenUp$date <- format(GreenUp$date, format = "%b-%d") # Monday-Day format
GreenUp = select(GreenUp, `YEAR`, `date`, `DOY`)
remove(phenSpring, SpringCal)
dailyTemp <- read_csv('https://pasta.lternet.edu/package/data/eml/knb-lter-hbr/59/10/9723086870f14b48409869f6c06d6aa8')
#dailyTemp <- read_csv('C:/Users/ahammond1/OneDrive - University of Massachusetts/DataSci/HBEF_air_temp_daily_1957-2021.csv')
(USDA Forest Service Northern Research Station, 2021a)
normals <- dailyTemp %>%
group_by(month = month(date), day = (day(date))) %>%
summarise(count1 = n(), # How many years averaged together
Tmin = mean(MIN), sdMin = sd(MIN), # sd = standard deviation
Tave = mean(AVE), sdAve = sd(AVE),
Tmax = mean(MAX), sdMax = sd(MAX),
sigma = sdMax,
oneSigma = (Tmax + sigma), # Events >= 1 standard deviation greater than normal
twoSigma = (Tmax + (2*sigma))) # Events >= 2 standard deviations greater than normal)
normals$month <- as.integer(normals$month)
normals$dateX = paste(normals$month, normals$day, sep = "-")
normals$dateX <- as.Date(normals$dateX, format = "%m-%d")
normals$DOY <- format(normals$dateX, format = "%j")
normals$DOY <- as.integer(normals$DOY)
tempSpring <- dailyTemp %>%
mutate(YEAR = substr(`date`, 0, 4))
tempSpring <- tempSpring %>%
mutate(MONTH = substr(`date`, 6, 7))
tempSpring$YEAR <- as.integer(tempSpring$YEAR)
tempSpring$MONTH <- as.integer(tempSpring$MONTH)
tempSpring <- tempSpring %>%
filter(STA == "HQ" &
YEAR >= 1989 & YEAR <=2020 &
MONTH >= 1 & MONTH <= 7)
tempSpring$date <- as.Date(tempSpring$date, format = "%m-%d")
tempSpring$DOY <- format(tempSpring$date, format = "%j")
tempSpring$DOY <- as.integer(tempSpring$DOY)
#remove(dailyTemp)
tempSpringNORMALS <- tempSpring %>%
inner_join(
x = tempSpring, y = normalSPRING,
by = 'DOY')
tempSpNorGreen <- tempSpringNORMALS %>%
inner_join(
x = tempSpringNORMALS, y = GreenUp,
by = 'YEAR')
remove(tempSpringNORMALS)
eventsB <- tempSpNorGreen %>%
group_by(YEAR) %>%
filter(DOY.x < DOY.y & MAX >= Tmax) %>%
summarise(bGreenCt = n())
eventsB1 <- tempSpNorGreen %>%
group_by(YEAR) %>%
filter(DOY.x < DOY.y & MAX >= oneSigma) %>%
summarise(bGreen1 = n())
eventsB <- eventsB %>%
left_join(
x = eventsB,
y = eventsB1,
by = 'YEAR')
eventsB1 <- tempSpNorGreen %>%
group_by(YEAR) %>%
filter(DOY.x < DOY.y & MAX >= twoSigma) %>%
summarise(bGreen2 = n())
eventsB <- eventsB %>%
left_join(
x = eventsB,
y = eventsB1,
by = 'YEAR')
remove(eventsB1)
eventsA <- tempSpNorGreen %>%
group_by(YEAR) %>%
filter(DOY.x >= DOY.y & MAX >= Tmax) %>%
summarise(aGreenCt = n())
eventsA1 <- tempSpNorGreen %>%
group_by(YEAR) %>%
filter(DOY.x >= DOY.y & MAX >= oneSigma) %>%
summarise(aGreen1 = n())
eventsA <- eventsA %>%
left_join(
x = eventsA,
y = eventsA1,
by = 'YEAR')
eventsA1 <- tempSpNorGreen %>%
group_by(YEAR) %>%
filter(DOY.x >= DOY.y & MAX >= twoSigma) %>%
summarise(aGreen2 = n())
eventsA <- eventsA %>%
left_join(
x = eventsA,
y = eventsA1,
by = 'YEAR')
remove(eventsA1)
events <- eventsB %>%
left_join(
x = eventsB,
y = eventsA,
by = 'YEAR')
remove(eventsA, eventsB)
events[is.na(events)] <- 0 # If NA, make 0
events <- events %>% # Rename Columns
rename(
"Before Green-Up" = bGreenCt,
"Before + 1σ" = bGreen1,
"Before + 2σ" = bGreen2,
"After Green-Up" = aGreenCt,
"After + 1σ" = aGreen1,
"After + 2σ" = aGreen2)
EventsF <- events %>%
group_by(YEAR) %>%
pivot_longer(
cols = 2:7,
names_to = "type",
values_to = "count" )
EventsF <- EventsF %>%
mutate(BA = recode(`type`,
'Before Green-Up' = "Before",
'Before + 1σ' = "Before",
'Before + 2σ' = "Before",
'After Green-Up' = "After",
'After + 1σ' = "After",
'After + 2σ' = "After"))
EventsF$BAf <- factor(EventsF$BA, levels = c("Before","After"))
EventsF <- EventsF %>%
mutate(type1 = recode(`type`,
'Before Green-Up' = "Greater Normal",
'Before + 1σ' = "> 1σ",
'Before + 2σ' = "> 2σ",
'After Green-Up' = "Greater Normal",
'After + 1σ' = "> 1σ",
'After + 2σ' = "> 2σ"))
EventsF$typef <- factor(EventsF$type1, levels = c("> 2σ","> 1σ", "Greater Normal"))
knitr::kable(GreenUp, "pipe",
col.names = c("Year", "Date", "Day of Year"),
align = "ccc",
caption = "Green-Up Date for 1989 to 2020")
| Year | Date | Day of Year |
|---|---|---|
| 1989 | May-31 | 150 |
| 1990 | Jun-05 | 155 |
| 1991 | May-29 | 148 |
| 1992 | Jun-10 | 160 |
| 1993 | Jun-08 | 158 |
| 1994 | Jun-07 | 157 |
| 1995 | Jun-06 | 156 |
| 1996 | Jun-05 | 155 |
| 1997 | Jun-17 | 167 |
| 1998 | May-28 | 147 |
| 1999 | Jun-02 | 152 |
| 2000 | Jun-07 | 157 |
| 2001 | May-30 | 149 |
| 2002 | Jun-11 | 161 |
| 2003 | Jun-17 | 167 |
| 2004 | Jun-03 | 153 |
| 2005 | Jun-07 | 157 |
| 2006 | Jun-06 | 156 |
| 2007 | May-30 | 149 |
| 2008 | Jun-04 | 154 |
| 2009 | Jun-09 | 159 |
| 2010 | May-25 | 144 |
| 2011 | Jun-01 | 151 |
| 2012 | May-31 | 150 |
| 2013 | May-29 | 148 |
| 2014 | Jun-03 | 153 |
| 2015 | May-27 | 146 |
| 2016 | Jun-03 | 153 |
| 2017 | May-31 | 150 |
| 2018 | May-30 | 149 |
| 2019 | Jun-11 | 161 |
| 2020 | Jun-03 | 153 |
normals %>%
filter(month >= 1 & month <= 7) %>%
ggplot(aes(x = dateX)) +
geom_line(aes(y = Tmax), color = "red", size = 1) +
geom_line(aes(y = Tave), color = "black", size = 1) +
geom_line(aes(y = Tmin), color = "blue", size = 1) +
labs(title = "Normal Minimum, Mean and Maximum Temperatures for January through July",
subtitle = "At Hubbard Brook Experimental Forest Headquarters",
x = "Day of Year", y = "Temperture in Degrees Celcius",
caption = "Daily Normal Maximum (Red), Mean (Black) and Minimum (Blue) Temperature")
Figure 1: Normal Temperatures in Degrees Celcius
tempSpNorGreen %>%
ggplot(aes(x = dateX)) +
geom_line(aes(y = Tmax), color = "red", size = 1) +
geom_line(aes(y = oneSigma), linetype = "dashed") +
geom_line(aes(y = twoSigma), linetype = "twodash", size = 1) +
labs(title = "Normal Maximum Temperature Plus 1 & 2 Standard Deviations",
subtitle = "At Hubbard Brook Experimental Forest Headquarters",
x = "Day of Year", y = "Temperture in ° Celcius",
caption = "Normal Daily Maximum Temperature (Red) plus 1 (Dashed) and 2 (Bold-Dashed) Standard Deviations")
Figure 2: Normal Maximum Temperature Plus 1 and 2 Standard Deviations
tempSpNorGreen %>%
ggplot(aes(x = dateX)) +
geom_point(aes(y = MAX), size = 0.25) +
geom_line(aes(y = Tmax), color = "red", size = 1) +
geom_line(aes(y = twoSigma), linetype = "twodash", size = 1) +
labs(title = "Daily Maximum Temperatures with Normals Maximum Temperature and 2 Standard Deviations Greater Than Normal",
subtitle = "At Hubbard Brook Experimental Forest Headquarters",
x = "Day of Year", y = "Temperture in ° Celcius",
caption = "Daily Maximum Temperature (Points), Normal (Red) and 2 Standard Deviations Greater than Normal Maximum temperatures")
EventsF %>%
ggplot(aes(YEAR, count, fill = typef)) +
geom_bar(stat = "identity") +
facet_wrap('BAf') +
coord_flip() + scale_x_reverse() +
scale_fill_manual(values = c("#ff0000", "#fffb00", "#59d96a")) +
labs(title = "Number of Days with a Maximum Temperature Greater Than the Normal Maximum Temperature ",
subtitle = "Before and After Green-Up" ,
x = "Year", y = "Number of Days") +
theme(legend.title = element_blank())
Figure 3: Number of Days with a Maximum Temperature Greater than the Normal Maximum Temperature
The data sets used above do show a marked difference in extreme temperature events before versus after Green-Up; there were almost double the number of events greater than 2 standard deviations above normal before than after overall. There is some early fluctuations, but a 32-year dataset is great to know some medium term trends. I am glad I picked a project and dataset where I already knew what to expect as an answer; it gave me the flexibility to work with R without working about how the evaluations would turn out. I definitely utilized the books available online for R Markdown and ggplot2, and I got lucky by working with others that knew R and could help me rationalize some things verbally. If I were to continue with this project, I would want to expand into evaluating minimum temperatures, as well as the Fall season when the leaves fall of the trees. It would also be interesting to look at the spatial aspects of the data. Different parts of the valley were recorded for Phenology Stage, and are indicated in Image 1; for this project I just averaged them together, but it would be interesting to see how Green-Up changes on the north side of the basin versus the south.