filter out night sap flow for total
SWC to ∆T (midday)
ET (flux data) to ∆T (midday)
Compare 30min sap vs ∆T 30min
for dendro:plot diurnal and asses MDS after that
add time series of sap vs ∆T WITH rain fall pulses on the same panel to see if there are responses
for daily patterns, add points to the lines to make it clearer
Work In Progress: add good timeseries.
DONE go to Dendro script and double check the units (microns) and put that on
DONE add precip
DONE add raw, growth values to trace how TWD is calculated
LAB_JS_30MIN <- expression(J[s] ~ (g ~ m^-2 ~ s^-1))
LAB_JS_DAILY <- expression(J[s] ~ (g ~ m^-2 ~ day^-1))
LAB_JS_30MIN_TXT <- "Js (g m^-2 s^-1)"
LAB_JS_DAILY_TXT <- "Js (g m^-2 day^-1)"
MIDDAY_START <- 10
MIDDAY_END <- 14
MPJ_LAT <- 34.43
MPJ_LON <- -106.13
MPJ_SOLAR_TZ <- "America/Phoenix"
SPECIES_COLS <- c("piñon" = "#40B0A6", "juniper" = "#E1BE6A")
# CHANGED: centralize repeated parsing logic here so later chunks use the same
# tree/species naming rules instead of re-implementing them.
species_from_tree <- function(tree_id) {
case_when(
str_starts(tree_id, "p") ~ "piñon",
str_starts(tree_id, "j") ~ "juniper",
TRUE ~ NA_character_
)
}
tree_from_sensor <- function(sensor_id) {
str_remove(sensor_id, "[ab]_sap$")
}
# CHANGED: pull the twd_norm.Rmd predawn/midday normalization logic into one
# reusable helper so daily MDS plus both predawn- and TWD4-based normalization
# stay in sync.
calc_normalized_dendro_metrics <- function(
data,
lat = MPJ_LAT,
lon = MPJ_LON,
solar_tz = MPJ_SOLAR_TZ
) {
required_cols <- c("tree", "species", "ts", "value", "twd")
if (!all(required_cols %in% names(data))) {
stop(
"calc_normalized_dendro_metrics() requires columns: ",
paste(required_cols, collapse = ", ")
)
}
date_range <- range(as_date(data$ts), na.rm = TRUE)
ts_tz <- attr(data$ts, "tzone")
na_tz <- if (length(ts_tz) == 0 || is.na(ts_tz) || ts_tz == "") {
"UTC"
} else {
ts_tz
}
sun_times <- suncalc::getSunlightTimes(
date = seq.Date(date_range[1], date_range[2], by = "day"),
lat = lat,
lon = lon,
keep = c("sunrise", "solarNoon", "sunset"),
tz = solar_tz
) |>
mutate(date = as_date(date)) |>
select(date, sunrise, solarNoon, sunset)
dendro_with_sun <- data |>
mutate(date = as_date(ts)) |>
left_join(sun_times, by = "date") |>
mutate(
is_predawn = ts < sunrise,
is_midday = ts >= solarNoon & ts <= sunset
)
twd_daily <- dendro_with_sun |>
group_by(tree, species, date) |>
summarise(
twd_predawn = if (any(is_predawn & !is.na(twd))) min(twd[is_predawn], na.rm = TRUE) else NA_real_,
twd_predawn_ts = if (any(is_predawn & !is.na(twd))) {
ts[is_predawn & !is.na(twd)][which.min(twd[is_predawn & !is.na(twd)])][1]
} else {
as.POSIXct(NA, tz = na_tz)
},
twd_4 = if (all(is.na(twd))) NA_real_ else max(twd, na.rm = TRUE),
.groups = "drop"
)
mds_daily <- dendro_with_sun |>
group_by(tree, species, date) |>
summarise(
predawn_max = if (any(is_predawn & !is.na(value))) max(value[is_predawn], na.rm = TRUE) else NA_real_,
predawn_max_ts = if (any(is_predawn & !is.na(value))) {
ts[is_predawn & !is.na(value)][which.max(value[is_predawn & !is.na(value)])][1]
} else {
as.POSIXct(NA, tz = na_tz)
},
midday_min = if (any(is_midday & !is.na(value))) min(value[is_midday], na.rm = TRUE) else NA_real_,
midday_min_ts = if (any(is_midday & !is.na(value))) {
ts[is_midday & !is.na(value)][which.min(value[is_midday & !is.na(value)])][1]
} else {
as.POSIXct(NA, tz = na_tz)
},
mds = predawn_max - midday_min,
.groups = "drop"
)
twd_daily |>
left_join(mds_daily, by = c("tree", "species", "date")) |>
group_by(tree) |>
mutate(
mds_max = if (all(is.na(mds))) {
NA_real_
} else {
quantile(mds, probs = 0.99, na.rm = TRUE, names = FALSE, type = 7)
},
twd_norm_pd = twd_predawn / mds_max,
twd_norm_4 = twd_4 / mds_max,
mds_norm = mds / mds_max
) |>
ungroup()
}
# CHANGED: use shared plot builders inside the Rmd so the species panels and
# correlation plots stay in sync without copy-paste edits.
build_daily_species_stack <- function(species_name, title_text, rain_plot) {
species_tc <- daily_tc_midday |>
filter(species == species_name)
species_sap <- daily_sap |>
filter(species == species_name) |>
select(date, sensor, total_sap)
species_twd4 <- daily_twd4 |>
filter(species == species_name) |>
select(date, tree, twd_4)
p_ts_dtc <- plot_ly(species_tc) |>
add_lines(
x = ~date,
y = ~dtc_mean_midday,
color = ~tree
) |>
layout(
yaxis = list(title = "midday avg ∆T (°C)"),
showlegend = TRUE
)
p_ts_sap <- plot_ly(species_sap) |>
add_lines(
x = ~date,
y = ~total_sap,
color = ~sensor
) |>
layout(
yaxis = list(title = LAB_JS_DAILY_TXT),
showlegend = FALSE
)
p_ts_twd <- plot_ly(species_twd4) |>
add_lines(
x = ~date,
y = ~twd_4,
color = ~tree
) |>
layout(
yaxis = list(title = "TWD4 (µm)"),
showlegend = FALSE
)
subplot(
p_ts_dtc,
p_ts_sap,
p_ts_twd,
rain_plot,
nrows = 4,
shareX = TRUE,
titleY = TRUE
) |>
layout(
title = title_text,
xaxis = list(title = "Date")
)
}
build_correlation_plot_list <- function(
data,
group_var,
x_var,
y_var,
facet_formula,
x_lab,
y_lab,
title_prefix,
add_smooth = FALSE
) {
group_values <- unique(data[[group_var]])
plot_list <- setNames(vector("list", length(group_values)), group_values)
for (group_value in group_values) {
df_group <- data |>
filter(.data[[group_var]] == group_value) |>
filter(!is.na(.data[[x_var]]), !is.na(.data[[y_var]]))
p <- ggplot(df_group, aes(x = .data[[x_var]], y = .data[[y_var]])) +
geom_point(alpha = 0.7)
if (isTRUE(add_smooth)) {
p <- p + geom_smooth(method = "lm", se = TRUE)
}
plot_list[[as.character(group_value)]] <- p +
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson",
size = 3
) +
facet_grid(facet_formula, scales = "free") +
labs(
title = paste(title_prefix, group_value),
x = x_lab,
y = y_lab
) +
theme_bw() +
theme(strip.text = element_text(size = 9))
}
plot_list
}
dir.create(here("output/research_days"), recursive = TRUE, showWarnings = FALSE)
Looking at:
∆T
stem radial growth (dendro) and max growth line to show how TWD is derived
Total daily sap flow
Sap trees:
P2
J4 - ems & don’t have yet
| Syntax | Description |
|---|---|
| Header | Title |
| Paragraph | Text |
# CHANGED: reuse a single P2 dendro object for the derivation and later
# time-series panels so the example stays internally consistent.
p_dendro_raw <- ggplot(dendro_dat, aes(x = ts, y = value, color = tree)) +
geom_line() +
labs(
title = "Raw dendrometer values",
y = "Stem radius displacement (µm)",
x = "Timestamp"
)
# to speed things up since plottly can be glitchy
# p_dendro_raw
# ggplotly(p_dendro_raw)
p2_dendro <- dendro_dat |>
filter(tree == "p2") |>
select(ts, value) |>
arrange(ts) |>
mutate(
value = value + abs(first(value)),
max = cummax(value),
twd = max - value
)
p_twd <- ggplot(p2_dendro, aes(x = ts)) +
geom_line(aes(y = value)) +
geom_line(aes(y = max), color = "red") +
geom_line(aes(y = twd), color = "blue") +
labs(
title = "Radial growth and TWD components",
y = "Stem radius (µm)",
x = "Timestamp"
)
p2_dt <- tc_dat |>
transmute(ts, p2 = p2_dt)
p_dt <- ggplot(p2_dt, aes(ts, p2)) +
geom_line()
# p2 <- p2_dt |>
# left_join(
# p2_dat_test |> select(ts, twd),
# by = "ts"
# ) |>
# fill(twd, .direction = "downup")
# raw ∆T values
# p_a <- plot_ly(p2_dt, x = ~ts, y = ~p2, type = 'scatter', mode = 'lines',name = "p2 raw")
# CHANGED: reshape sap data once here so later chunks can reuse the same long
# and daily tables instead of rebuilding them with slightly different rules.
sap_long <- sap_dat |>
pivot_longer(
cols = -ts,
names_to = "sensor",
values_to = "sap_flow"
) |>
mutate(
date = as_date(ts),
tree = tree_from_sensor(sensor),
species = species_from_tree(tree)
) |>
filter(!is.na(species))
sap_long |>
ggplot(aes(x = ts, y = sap_flow, colour = sensor))+
geom_line() +
facet_wrap(~species, ncol = 1)
## Warning: Removed 74994 rows containing missing values or values outside the scale range
## (`geom_line()`).
ggplotly(sap_long |>
ggplot(aes(x = ts, y = sap_flow, colour = sensor))+
geom_line())
# Daily max & mean sap flow
daily_sap <- sap_long |>
group_by(sensor, tree, species, date) |>
summarise(
total_sap = sum(sap_flow, na.rm = TRUE),
.groups = "drop"
)
total_sap_p <- ggplot(daily_sap, aes(x = date, y = total_sap, color = sensor)) +
geom_line() +
facet_wrap(~species, ncol = 1) +
labs(
title = "Total Daily Sap",
y = LAB_JS_DAILY,
x = "Date"
)
# ggplotly(total_sap_p)
# to smooth data...
p2_dt <- p2_dt |>
arrange(ts) |>
mutate(
p2_smooth = rollmean(p2, k = 5, fill = NA, align = "center")
)
# CHANGED: keep both 30-min and daily precip panels so sub-daily and daily
# figures share a matching time scale.
daily_precip <- flux |>
mutate(date = as_date(ts)) |>
group_by(date) |>
summarise(
precip = sum(precip, na.rm = TRUE),
.groups = "drop"
)
p_rain <- plot_ly(flux) |>
add_lines(
x = ~ts,
y = ~precip,
name = "precip",
line = list(color = "blue")
) |>
layout(
yaxis = list(title = "precip (mm)")
)
p_rain_daily <- plot_ly(daily_precip) |>
add_lines(
x = ~date,
y = ~precip,
name = "daily precip",
line = list(color = "blue")
) |>
layout(
yaxis = list(title = "precip (mm)")
)
# smoothed 2.5hr ∆T data
p_dt <- plot_ly(p2_dt) |>
add_markers(
x = ~ts,
y = ~p2_smooth,
name = "p2_smoothed ∆T",
marker = list(size = 4, color = "black")
) |>
layout(
yaxis = list(title = "∆T (°C)")
)
p_rad_growth <- plot_ly(
p2_dendro,
x = ~ts, y = ~value,
type = "scatter", mode = "lines",
name = "Stem radius"
) |>
add_lines(
x = ~ts,
y = ~max,
name = "Max radius",
line = list(color = "red", width = 3)
) |>
layout(
yaxis = list(title = "Stem Radius (µm)")
)
p_twd <- plot_ly(p2_dendro, x = ~ts, y = ~twd, type = "scatter", mode = "lines", name = "TWD", line = list(color = "blue")) |>
layout(
yaxis = list(title = "TWD (µm)")
)
p_sap_tot <- plot_ly(daily_sap |> filter(tree == "p2"), x = ~date, y = ~total_sap, type = "scatter", mode = "lines", name = "Daily total Js", line = list(color = "blue")) |>
layout(
yaxis = list(title = LAB_JS_DAILY_TXT, size = 0.1)
)
p_flow <- plot_ly(sap_long |> filter(tree == "p2"), x = ~ts, y = ~sap_flow, type = "scatter", mode = "lines", name = "Sap flux Js") |>
layout(
yaxis = list(title = LAB_JS_30MIN_TXT)
)
fig <- subplot( p_dt, p_rad_growth, p_twd, p_sap_tot, p_flow, nrows = 5, shareX = TRUE, titleY = TRUE) |>
layout(
xaxis = list(title = "Time")
)
## Warning: Ignoring 1907 observations
# fig
MDS — Maximum Daily Shrinkage (Blanco & Kalcsits, 2024)
We determined MDS of absolute SDV by calculating the differences between the daily pre-dawn maximum and the daily afternoon minimum in stem radius
MDS = dailymax(radius) − daily min(radius)
Hypothesis: Higher MDS -> more transpiration -> lower ∆T
TWD4 — Daily Tree Water Deficit (Dietrich et al.,2018)
TWD4 = daily maximum TWD (the TWD value at the minimum stem radius) This version relates best to midday water potential (Ψ).
Hypothesis: Higher MDS -> more transpiration -> lower ∆T
TWD4 increases with water stress and should be positively correlated with ∆T (Dietrich et al.,2018).
Because TWD related more strongly to midday Ψ than to MDS, midday ∆T is likely a better comparison
Further, daily TWD was a better indicator for daily midday Ψ, particularly under dry conditions, than maximum daily shrinkage… (Dietrich et al., 2018)
make sure the max is limited to the morning and the min is after, just so the max isn’t taken from the the end of the day (midnight)
IDEA: maybe use how TWD differs from the previous day to make it daily instead of cumulative
# CHANGED: reuse the same P2 derivation object created above instead of
# recalculating it here with slightly different inputs.
twd4 <- p2_dendro |>
mutate(date = as_date(ts)) |>
group_by(date) |>
filter(twd == max(twd, na.rm = TRUE)) |>
slice(1) |> # in case of ties
ungroup() |>
select(ts, date, twd4 = twd)
p_rad_growth <- plot_ly(
p2_dendro,
x = ~ts, y = ~value,
type = "scatter", mode = "lines",
name = "Stem radius",
line = list(color = "green", width = 3)
) |>
add_trace(
x = ~ts,
y = ~max,
name = "Max radius envelope",
mode = "lines",
fill = "tonexty",
fillcolor = "lightblue",
line = list(color = "red", width = 3)
) |>
layout(
yaxis = list(title = "Stem Radius (µm)")
)
p_twd <- plot_ly(p2_dendro) |>
add_lines(x = ~ts, y = ~twd, name = "TWD", line = list(color = "lightblue")) |>
add_markers(
data = twd4,
x = ~ts,
y = ~twd4,
name = "TWD4 (daily max)",
marker = list(color = "black", size = 8, symbol = "circle")
) |>
layout(
yaxis = list(title = "TWD (µm)")
)
p_twd4 <- plot_ly(
twd4,
x = ~date,
y = ~twd4,
type = "scatter",
mode = "lines",
line = list(color = "black", width = 3),
name = "TWD4"
) |>
layout(
yaxis = list(title = "TWD4 (µm)"),
xaxis = list(title = "Date")
)
fig <- subplot(p_rad_growth, p_twd,p_twd4, p_rain, nrows = 4, shareX = TRUE, titleY = TRUE) |>
layout(
xaxis = list(title = "Time")
)
fig
# CHANGED: switch the daily MDS derivation to the same predawn/midday windows
# used in twd_norm.Rmd so `mds`, `twd_norm_pd`, `twd_norm_4`, and `mds_norm`
# share one definition.
daily_mds <- calc_normalized_dendro_metrics(dendro_dat)
# Filtered datasets
df_p2 <- dendro_dat |>
filter(tree == "p2") |>
arrange(ts)
dm_p2 <- daily_mds |> filter(tree == "p2")
# Build plot
p_p2 <- plot_ly(df_p2) |>
add_lines(
x = ~ts,
y = ~value,
name = "Stem Radius",
line = list(color = "green", width = 3)
) |>
# predawn max (red)
add_markers(
data = dm_p2,
x = ~predawn_max_ts,
y = ~predawn_max,
marker = list(color = "red", size = 7),
name = "Predawn max"
) |>
# midday minimum (blue)
add_markers(
data = dm_p2,
x = ~midday_min_ts,
y = ~midday_min,
marker = list(color = "blue", size = 7),
name = "Midday min"
) |>
layout(
title = "Diurnal Radius + MDS Points",
yaxis = list(title = "Stem Radius (µm)")
)
# CHANGED: anchor the daily MDS trace on the midday minimum timestamp so it
# lines up with the normalized predawn-to-midday window logic above.
p_mds <- plot_ly(data = dm_p2) |>
add_lines(
x = ~midday_min_ts,
y = ~mds,
line = list(color = "purple", width = 3),
name = "MDS") |>
layout(
yaxis = list(title = "MDS (µm)")
)
fig <- subplot(p_p2,p_mds, p_rain, nrows = 3, shareX = TRUE, titleY = TRUE) |>
layout(
xaxis = list(title = "Time")
)
## Warning: Ignoring 1 observations
fig
fig <- subplot(p_rad_growth, p_twd, p_twd4, p_mds, p_rain, nrows = 5, shareX = TRUE, titleY = TRUE) |>
layout(
xaxis = list(title = "Time")
)
fig
# CHANGED: keep the normalized dendro outputs next to TWD4 here so later joins
# can use `twd_norm_pd`, `twd_norm_4`, and `mds_norm` without rebuilding the
# daily metrics.
dendro_daily <- dendro_dat |>
mutate(
date = as_date(ts)
)
# ---- DAILY TWD4 ----
# TWD4 = TWD at the DAILY MINIMUM radius (== daily maximum TWD)
# Because:
# twd = max_envelope - radius
# smallest radius -> largest TWD
daily_twd4 <- dendro_daily |>
group_by(tree, species, date) |>
summarise(
twd_4 = if (all(is.na(twd))) NA_real_ else max(twd, na.rm = TRUE), # TWD4 described in https://doi.org/10.1093/treephys/tpy023
.groups = "drop"
)
# merge
daily_mds_twd <- daily_mds |>
select(
tree,
species,
date,
daily_max_value = predawn_max,
daily_min_value = midday_min,
mds,
twd_4,
twd_norm_pd,
twd_norm_4,
mds_norm
)
base_p <- ggplot(daily_mds_twd, aes(x = date))
twd_p <- base_p + geom_line(aes(y = twd_4, colour = tree)) +
facet_wrap(~species, ncol = 1) +
labs(
title = "Max Daily TWD (TWD4)",
y = "TWD (µm)",
x = "Time"
)
ggplotly(twd_p)
mds_p <- base_p + geom_line(aes(y = mds, colour = tree)) +
facet_wrap(~species, ncol = 1) +
labs(
title = "Max Daily Shrinkage",
y = "MDS (µm)",
x = "Time"
)
ggplotly(mds_p)
Total Daily Sapflux (Snyder et al., 2024)
Note: I tested daily max and mean sap flow, but after discussing with Marcy and Will, I’m using total daily sap flow going forward.
# CHANGED: reuse the sap tables built in the prep chunk so this section only
# handles plotting and interpretation.
ggplotly(sap_long |>
ggplot(aes(x = ts, y = sap_flow, color = sensor)) +
geom_line())
# ggplot code for total
p_total_sap <- ggplot(daily_sap, aes(x = date, y = total_sap, color = sensor)) +
geom_line() +
facet_wrap(~species, ncol = 1) +
ggtitle(" sap") +
labs(
title = "Total Daily Sap",
y = LAB_JS_DAILY,
x = "Date"
)
ggplotly(p_total_sap)
plots <- lapply(split(sap_long, sap_long$species), function(df_sp) {
plot_ly(df_sp,
x = ~ts,
y = ~sap_flow,
color = ~sensor,
type = "scatter",
mode = "lines") |>
layout(
yaxis = list(title = paste(unique(df_sp$species), LAB_JS_30MIN_TXT)),
xaxis = list(title = "Date")
)
})
# Combine into a column of subplots
p_total_sap <- subplot(plots,
nrows = length(plots),
shareX = TRUE,
titleY = TRUE)
fig <- subplot(
p_total_sap,
p_rain_daily,
nrows = 2,
shareX = TRUE,
titleY = TRUE,
heights = c(0.7, 0.3) # larger height for sap, smaller for rain
)
fig
# smoothed 2.5hr ∆T data
p_a <- plot_ly(p2_dt) |>
add_markers(
x = ~ts,
y = ~p2_smooth,
name = "p2_smoothed",
marker = list(size = 4, color = "black")
) |>
layout(
yaxis = list(title = "∆T (°C)")
)
p_b <- plot_ly(sap_long |> filter(tree == "p2")) |>
add_lines(
x = ~ts,
y = ~sap_flow,
name = "Js"
) |>
layout(
yaxis = list(title = LAB_JS_30MIN_TXT)
)
fig <- subplot(p_a, p_b, p_rain, nrows = 3, shareX = TRUE, titleY = TRUE) |>
layout(
xaxis = list(title = "Time")
)
## Warning: Ignoring 1907 observations
fig
Midday window (10:00–14:00): Midday was chosen by visually inspecting the thermal time series and identifying when canopy temperatures consistently peaked and when shadow effects were minimal. This window best captures the period when evaporation cooling is most limited and water stress signals in ∆T are strongest.
Max midday ∆T: Represents the highest canopy temperature anomaly (Tc–Ta) during the midday period. This metric may reflect peak water stress or reduced transpiration when trees are most thermally exposed.
Mean midday ∆T: Calculated as the average Tc–Ta between 10:00 and 14:00. This smooths out short-term fluctuations and better represents sustained midday canopy water status.
# CHANGED: exclude non-tree panel ROIs up front so later joins stay focused on
# tree-level thermal signals.
tc <- tc_dat |>
mutate(
date = as_date(ts),
hour = hour(ts)
)
roi_cols <- tc |>
select(ends_with("_dt"), -contains("panel")) |>
colnames()
tc_long <- tc |>
pivot_longer(
cols = all_of(roi_cols),
names_to = "roi_raw",
values_to = "tc_ta"
)
tc_long <- tc_long |>
mutate(
tree = str_remove(roi_raw, "_dt$"),
species = species_from_tree(tree)
) |>
filter(!is.na(species))
tc_midday <- tc_long |>
filter(hour >= MIDDAY_START, hour <= MIDDAY_END)
daily_tc_midday <- tc_midday |>
group_by(date, tree, species) |>
summarise(
dtc_max = max(tc_ta, na.rm = TRUE),
dtc_mean_midday = mean(tc_ta, na.rm = TRUE),
.groups = "drop"
)
## Warning: There were 872 warnings in `summarise()`.
## The first warning was:
## ℹ In argument: `dtc_max = max(tc_ta, na.rm = TRUE)`.
## ℹ In group 1: `date = 2025-04-08`, `tree = "j20"`, `species = "juniper"`.
## Caused by warning in `max()`:
## ! no non-missing arguments to max; returning -Inf
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 871 remaining warnings.
max_dtc_p <- ggplot(daily_tc_midday, aes(x = date, y = dtc_max, color = tree)) +
geom_point(size = 0.7) +
facet_wrap(~species, ncol = 1)
# ggplotly(max_dtc_p)
mean_dtc_p <- ggplot(daily_tc_midday, aes(x = date, y = dtc_mean_midday, color = tree)) +
geom_point() +
facet_wrap(~species, ncol = 1) +
labs(
title = "Average Midday ∆T",
y = "∆T (°C)",
x = "Date"
)
ggplotly(mean_dtc_p)
# CHANGED: call the shared species plot builder here so the piñon/juniper
# sections stay identical except for the species filter.
build_daily_species_stack(
species_name = "piñon",
title_text = "Piñon: ∆T, Sap Flow, and TWD4",
rain_plot = p_rain_daily
)
## Warning in RColorBrewer::brewer.pal(max(N, 3L), "Set2"): n too large, allowed maximum for palette Set2 is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(max(N, 3L), "Set2"): n too large, allowed maximum for palette Set2 is 8
## Returning the palette you asked for with that many colors
# CHANGED: reuse the same plot builder as the piñon section so formatting only
# has to be updated in one place.
build_daily_species_stack(
species_name = "juniper",
title_text = "Juniper: ∆T, Sap Flow, and TWD4",
rain_plot = p_rain_daily
)
## Warning in RColorBrewer::brewer.pal(max(N, 3L), "Set2"): n too large, allowed maximum for palette Set2 is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(max(N, 3L), "Set2"): n too large, allowed maximum for palette Set2 is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(max(N, 3L), "Set2"): n too large, allowed maximum for palette Set2 is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(max(N, 3L), "Set2"): n too large, allowed maximum for palette Set2 is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(max(N, 3L), "Set2"): n too large, allowed maximum for palette Set2 is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(max(N, 3L), "Set2"): n too large, allowed maximum for palette Set2 is 8
## Returning the palette you asked for with that many colors
daily_tc_dendro has midday ∆T values
with daily MDS and TWD.daily_tc_sap has midday ∆T values with
daily max and mean sap flow.# CHANGED: keep missing normalized dendro values from dropping otherwise valid
# MDS/TWD4 days; the downstream plots filter only the columns they need.
daily_tc_dendro <- daily_tc_midday |>
left_join(
daily_mds_twd |> select(-c(daily_max_value, daily_min_value)),
by = c("tree", "species", "date")
)
daily_tc_sap <- daily_tc_midday |>
left_join(
daily_sap,
by = c("tree", "species", "date")
)|>
na.omit()
# NEED TO WORK ON THIS: doesnt work
# daily_tc_dendro_sap <- daily_tc_midday |>
# left_join(
# daily_mds_twd |> select(-c(daily_max_value,daily_min_value)), by = c("tree", "species", "date")
# ) |>
# left_join(
# daily_sap, by = c("tree", "species", "date")
# )
Midday canopy temperature metrics (∆T max and mean) were paired with daily dendrometer metrics (MDS and TWD) to assess how thermal stress relates to stem - level water status.
For every tree: -∆T variables were plotted against MDS and TWD with p and R values
-separate by month
# CHANGED: use the shared plotting helper here so tree- and species-level
# dendro comparisons stay parallel and only use the cleaned midday ∆T metric.
pair_data <- daily_tc_dendro |>
transmute(
tree,
species,
date,
dtc_var = "dtc_mean_midday",
dtc = dtc_mean_midday,
mds,
twd_4,
mds_norm,
twd_norm_pd,
twd_norm_4
) |>
pivot_longer(
cols = c(mds, twd_4, mds_norm, twd_norm_pd, twd_norm_4),
names_to = "dendro_var",
values_to = "dendro"
) |>
na.omit()
tree_plot_list <- build_correlation_plot_list(
data = pair_data,
group_var = "tree",
x_var = "dtc",
y_var = "dendro",
facet_formula = dtc_var ~ dendro_var,
x_lab = "°C",
y_lab = "stem radius (µm)",
title_prefix = "Tree:"
)
tree_plot_list
## Warning: The dot-dot notation (`..r.label..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(r.label)` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
species_plot_list <- build_correlation_plot_list(
data = pair_data,
group_var = "species",
x_var = "dtc",
y_var = "dendro",
facet_formula = dtc_var ~ dendro_var,
x_lab = "°C",
y_lab = "(µm)",
title_prefix = "Species:"
)
species_plot_list
daily_tc_dendro <- daily_tc_dendro |>
filter(dtc_mean_midday>0)
p_daily_tc_twd4 <- daily_tc_dendro |>
filter(!is.na(dtc_mean_midday), !is.na(twd_4)) |>
ggplot(aes(x = dtc_mean_midday, y = twd_4, color = species)) +
geom_point() +
geom_smooth(method = "lm", se = TRUE) + # Add regression line
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson", size = 3
) +
labs(
title = "Relationship between Midday Canopy Temperature (ΔT) and Max Daily TWD.",
x = "Midday Average ∆T (°C)",
y = "Max Daily TWD (µm)",
# caption = "Max Daily TWD (µm) shows a significant positive correlation with midday canooy temperature in the selected piñon and juniper. Shaded regions indicate 95% confidence intervals. Midday ΔT values were averaged from 10:00 to 16:00."
) +
scale_color_manual(values = SPECIES_COLS) +
theme(plot.caption = element_textbox_simple())
# CHANGED: split the normalized TWD view into predawn- and TWD4-based versions
# so both normalization choices are visible downstream.
p_daily_tc_norm_pd <- daily_tc_dendro |>
filter(!is.na(dtc_mean_midday), !is.na(twd_norm_pd)) |>
ggplot(aes(x = dtc_mean_midday, y = twd_norm_pd, color = species)) +
geom_point() +
geom_smooth(method = "lm", se = TRUE) + # Add regression line
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson", size = 3
) +
labs(
x = "Midday Average ∆T (°C)",
y = "Normalized TWD_pd (-)") +
scale_color_manual(values = SPECIES_COLS) +
theme(plot.caption = element_textbox_simple())
p_daily_tc_norm_4 <- daily_tc_dendro |>
filter(!is.na(dtc_mean_midday), !is.na(twd_norm_4)) |>
ggplot(aes(x = dtc_mean_midday, y = twd_norm_4, color = species)) +
geom_point() +
geom_smooth(method = "lm", se = TRUE) + # Add regression line
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson", size = 3
) +
labs(
x = "Midday Average ∆T (°C)",
y = "Normalized TWD_4 (-)") +
scale_color_manual(values = SPECIES_COLS) +
theme(plot.caption = element_textbox_simple())
p_daily_tc_mds <- daily_tc_dendro |>
filter(!is.na(dtc_mean_midday), !is.na(mds)) |>
ggplot(aes(x = dtc_mean_midday, y = mds, color = species))+
geom_point() +
geom_smooth(method = "lm", se = TRUE) + # Add regression line
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson", size = 3
) +
scale_color_manual(values = SPECIES_COLS)+
labs(
x = "Midday Average ∆T (°C)",
y = "MDS (µm)"
)
p_daily_tc_mds_norm <- daily_tc_dendro |>
filter(!is.na(dtc_mean_midday), !is.na(mds)) |>
ggplot(aes(x = dtc_mean_midday, y = mds_norm, color = species))+
geom_point() +
geom_smooth(method = "lm", se = TRUE) + # Add regression line
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson", size = 3
) +
scale_color_manual(values = SPECIES_COLS)+
labs(
x = "Midday Average ∆T (°C)",
y = "Normalized MDS_pd (-)"
)
ggsave(
filename = here("output/research_days/p_daily_tc_twd4.png"),
plot = p_daily_tc_twd4,
width = 7,
height = 5,
units = "in"
)
## `geom_smooth()` using formula = 'y ~ x'
p_daily_tc_twd4
## `geom_smooth()` using formula = 'y ~ x'
p_daily_tc_norm_pd
## `geom_smooth()` using formula = 'y ~ x'
p_daily_tc_norm_4
## `geom_smooth()` using formula = 'y ~ x'
p_daily_tc_mds
## `geom_smooth()` using formula = 'y ~ x'
p_daily_tc_mds_norm
## `geom_smooth()` using formula = 'y ~ x'
∆T max and mean vs daily sap flow metrics (daily max and mean sap flow) to examine how canopy thermal stress relates to tree - level water use. Data were reshaped into long format to generate all combinations of ∆T × sap flow variables.
For each **sap flow sensor** :-∆T values were plotted against sap flow with with p and R values
Note: tried separating by month to get more info on outliers. showed clusters, but got rid of correlations
# CHANGED: mirror the dendro workflow above so sap correlations use the same
# plotting pattern and cleaner daily inputs.
pair_data <- daily_tc_sap |>
transmute(
sensor,
tree,
species,
date,
dtc_var = "dtc_mean_midday",
dtc = dtc_mean_midday,
total_sap
) |>
mutate(
sap = total_sap, # directly assign total_sap
sap_var = "total_sap", # single label for facet
month = month(date)
) |>
filter(sap >0)
plot_list <- build_correlation_plot_list(
data = pair_data,
group_var = "sensor",
x_var = "dtc",
y_var = "sap",
facet_formula = dtc_var ~ sap_var,
x_lab = "Thermal signal (°C)",
y_lab = LAB_JS_DAILY_TXT,
title_prefix = "Sensor:",
add_smooth = TRUE
)
plot_list
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
TREE_COLS <- c("p2" = "#40B0A6", "j4" = "#E1BE6A")
# CHANGED: keep the saved summary figure explicit so it also renders in the
# knitted notebook.
p_daily_tc_sap <- daily_tc_sap |>
filter(total_sap>0) |>
ggplot(aes(x = dtc_mean_midday, y = total_sap, color = tree)) +
geom_point()+
geom_smooth(method = "lm", se = TRUE)+
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson", size = 3
) +
scale_color_manual(values = TREE_COLS) +
labs(
#title = "Relationship between Midday Canopy Temperature (ΔT) and Daily Sap Flux.",
x = "Midday Average ∆T (°C)",
y = LAB_JS_DAILY,
color = "Tree ID"
# caption = " Daily total sap flux (Js) shows a significant positive correlation with midday thermal gradients in the selected Pinus edulis (P2) but not in selected Juniper monosperma (J4). Shaded regions indicate 95% confidence intervals. Sap Flux values were summed for the entire day. Midday ΔT values were averaged from 10:00 to 16:00."
) +
theme(plot.caption = element_textbox_simple())
p_daily_tc_sap
## `geom_smooth()` using formula = 'y ~ x'
p_daily_tc_sap_greaterThan0dtc_lm <- daily_tc_sap |>
filter(total_sap>0, dtc_mean_midday>0 ) |>
ggplot(aes(x = dtc_mean_midday, y = total_sap, color = tree)) +
geom_point()+
geom_smooth(method = "lm", se = TRUE)+
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson", size = 3
) +
scale_color_manual(values = TREE_COLS) +
labs(
#title = "Relationship between Midday Canopy Temperature (ΔT) and Daily Sap Flux.",
x = "Midday Average ∆T (°C)",
y = LAB_JS_DAILY,
color = "Tree ID") +
theme(plot.caption = element_textbox_simple())
p_daily_tc_sap_greaterThan0dtc_lm
## `geom_smooth()` using formula = 'y ~ x'
p_daily_tc_sap_greaterThan0dtc_gam <- daily_tc_sap |>
filter(total_sap>0, dtc_mean_midday>0 ) |>
ggplot(aes(x = dtc_mean_midday, y = total_sap, color = tree)) +
geom_point()+
geom_smooth(method = "gam", se = TRUE)+
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson", size = 3
) +
scale_color_manual(values = TREE_COLS) +
labs(
#title = "Relationship between Midday Canopy Temperature (ΔT) and Daily Sap Flux.",
x = "Midday Average ∆T (°C)",
y = LAB_JS_DAILY,
color = "Tree ID") +
theme(plot.caption = element_textbox_simple())
p_daily_tc_sap
## `geom_smooth()` using formula = 'y ~ x'
# # p_daily_tc_sap_greater has dtc less than zero linear model
ggsave(
filename = here("output/research_days/p_daily_tc_sap.png"),
plot = p_daily_tc_sap,
width = 7,
height = 5,
units = "in"
)
## `geom_smooth()` using formula = 'y ~ x'
#
# # p_daily_tc_sap_greaterThan0dtc linear model
# ggsave(
# filename = here("output/research_days/p_daily_tc_sap_greaterThan0dtc_lm.png"),
# plot = p_daily_tc_sap_greaterThan0dtc_lm,
# width = 10,
# height = 7,
# units = "in",
# dpi = 300
# )
#
# # p_daily_tc_sap_greaterThan0dtc GAM
# ggsave(
# filename = here("output/research_days/p_daily_tc_sap_greaterThan0dtc_gam.png"),
# plot = p_daily_tc_sap_greaterThan0dtc_gam,
# width = 10,
# height = 7,
# units = "in",
# dpi = 300
# )
p_sap_twdNorm4 <- (p_daily_tc_sap_greaterThan0dtc_lm / p_daily_tc_norm_4) +
plot_layout(axes = "collect") +
plot_annotation(tag_levels = 'a') &
labs(title = NULL)
# ggsave(
# filename = here("output/research_days/p_sap_twdNorm4.png"),
# plot = p_sap_twdNorm4,
# width = 10,
# height = 7,
# units = "in",
# dpi = 300
# )
# CHANGED: reuse the cleaned long sap table so this exploratory chunk still
# runs after the sap import was standardized higher up in the notebook.
test <- sap_long |>
mutate(
sap = sap_flow,
hour = as_hms(ts)
)
summary(test$sap)
p2_sap <- test |>
filter(tree == "p2")
p2_dt_diurnal <- p2_dt |>
mutate(hour = as_hms(ts))
p2_dt_diurnal |>
ggplot(aes(x = hour, y = p2)) +
geom_point(alpha = 0.3) +
geom_smooth(method = "loess", se = FALSE)
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 1372 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 1372 rows containing missing values or values outside the scale range
## (`geom_point()`).
p2_sap |>
ggplot(aes(x = hour, y = sap)) +
geom_point(alpha = 0.3) +
geom_smooth(method = "loess", se = FALSE)
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 12625 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 12625 rows containing missing values or values outside the scale range
## (`geom_point()`).
test |>
ggplot(aes(x = hour, y = sap, color = tree)) +
geom_point(alpha = 0.3) +
geom_smooth(method = "loess", se = FALSE)
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 87398 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 87398 rows containing missing values or values outside the scale range
## (`geom_point()`).
The goal here is to see whether averaging sap and ∆T at the species level changes the relationship compared with looking at individual trees or sensors.
# CHANGED: aggregate sap and midday ∆T by species using means rather than sums
# so the comparison is not driven by one species having more sensors/trees.
species_daily_sap <- daily_sap |>
group_by(species, date) |>
summarise(
sap_species_mean = mean(total_sap, na.rm = TRUE),
sd_sap = sd(total_sap, na.rm = TRUE),
n_sensors = n_distinct(sensor), # Using n_distinct for the number of replicates
se_sap = sd_sap / sqrt(n_sensors),
.groups = "drop"
)
species_daily_dtc <- daily_tc_midday |>
filter(month(date)>3) |>
group_by(species, date) |>
summarise(
dtc_species_midday_mean = mean(dtc_mean_midday, na.rm = TRUE),
sd_dtc = sd(dtc_mean_midday, na.rm = TRUE),
n_trees = n_distinct(tree),
se_dtc = sd_dtc / sqrt(n_trees),
.groups = "drop"
)
p_species_sap_compare <- species_daily_sap |>
ggplot(aes(x = date, y = sap_species_mean, color = species, fill = species)) + # Added fill
geom_ribbon(aes(ymin = sap_species_mean - se_sap,
ymax = sap_species_mean + se_sap),
alpha = 0.2, color = NA) + # Shaded Error Area
geom_line() +
geom_point(size = 0.9) +
scale_color_manual(values = SPECIES_COLS) +
scale_fill_manual(values = SPECIES_COLS) + # Make ribbon match line color
labs(title = "Species mean daily sap flow", x = "Date", y = LAB_JS_DAILY) +
theme(legend.position = "right")
p_species_dtc_midday <- species_daily_dtc |>
ggplot(aes(x = date, y = dtc_species_midday_mean, color = species, fill = species)) +
geom_ribbon(aes(ymin = dtc_species_midday_mean - se_dtc,
ymax = dtc_species_midday_mean + se_dtc),
alpha = 0.2, color = NA) +
geom_line() +
geom_point(size = 0.9) +
scale_color_manual(values = SPECIES_COLS) +
scale_fill_manual(values = SPECIES_COLS) +
labs(title = "Species mean midday ∆T", x = "Date", y = "Midday average ∆T (°C)") +
theme(legend.position = "right")
# Keep your patchwork layout as is
(p_species_sap_compare / p_species_dtc_midday) +
plot_layout(guides = 'collect', axes = "collect")
## Warning: Removed 19 rows containing missing values or values outside the scale range
## (`geom_ribbon()`).
# CHANGED: summarize both sap and dTc diurnally by species so the species-level
# comparison uses the same time-of-day frame and shows uncertainty on both.
species_sap_diurnal <- sap_long |>
mutate(time_of_day = as_hms(format(ts, "%H:%M:%S"))) |>
group_by(species, time_of_day) |>
summarise(
mean_sap = mean(sap_flow, na.rm = TRUE),
sd_sap = sd(sap_flow, na.rm = TRUE),
n = n(),
se_sap = sd_sap / sqrt(n),
.groups = "drop"
)
species_dtc_diurnal <- tc_long |>
mutate(time_of_day = as_hms(format(ts, "%H:%M:%S"))) |>
group_by(species, time_of_day) |>
summarise(
mean_dtc = mean(tc_ta, na.rm = TRUE),
sd_dtc = sd(tc_ta, na.rm = TRUE),
n = n(),
se_dtc = sd_dtc / sqrt(n),
.groups = "drop"
)
p_species_sap_diurnal <- species_sap_diurnal |>
ggplot(aes(x = time_of_day, y = mean_sap, color = species, fill = species)) +
geom_ribbon(
aes(ymin = mean_sap - se_sap, ymax = mean_sap + se_sap),
alpha = 0.2,
color = NA
) +
geom_line(linewidth = 1) +
scale_color_manual(values = SPECIES_COLS) +
scale_fill_manual(values = SPECIES_COLS) +
scale_x_time(
breaks = hms::as_hms(sprintf("%02d:00:00", seq(0, 23, by = 2))),
labels = scales::label_time("%H:%M")
) +
labs(
title = "Species mean diurnal sap flow",
x = "Time of day",
y = LAB_JS_30MIN
)
p_species_dtc_diurnal <- species_dtc_diurnal |>
ggplot(aes(x = time_of_day, y = mean_dtc, color = species, fill = species)) +
geom_ribbon(
aes(ymin = mean_dtc - se_dtc, ymax = mean_dtc + se_dtc),
alpha = 0.2,
color = NA
) +
geom_line(linewidth = 1) +
scale_color_manual(values = SPECIES_COLS) +
scale_fill_manual(values = SPECIES_COLS) +
scale_x_time(
breaks = hms::as_hms(sprintf("%02d:00:00", seq(0, 23, by = 2))),
labels = scales::label_time("%H:%M")
) +
labs(
title = "Species mean diurnal ∆T",
x = "Time of day",
y = "∆T (°C)"
)
# Make p_species_sap_diurnal share time of day range of p_species_dtc_diurnal
shared_limits <- c(min(species_dtc_diurnal$time_of_day), max(as_hms("19:00:00")))
# Combine and force the limits
(p_species_sap_diurnal / p_species_dtc_diurnal) +
plot_layout(guides = 'collect', axes = "collect") &
coord_cartesian(xlim = shared_limits) & theme(legend.position = 'bottom')
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_ribbon()`).
# CHANGED: pair species-aggregated daily sap with species-aggregated mean
# midday ∆T to test whether piñon and juniper show different species-level
# sap-temperature relationships.
species_daily_sap_dtc <- species_daily_sap |>
left_join(species_daily_dtc, by = c("species", "date")) |>
filter(sap_species_mean > 0)
p_species_sap_dtc_pair <- species_daily_sap_dtc |>
filter(dtc_species_midday_mean>=0) |> # i need to look into if i should do this.
ggplot(aes(
x = dtc_species_midday_mean,
y = sap_species_mean,
color = species
)) +
geom_point(alpha = 0.8) +
geom_smooth(method = "gam", se = TRUE) +
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson",
size = 3
) +
scale_color_manual(values = SPECIES_COLS) +
labs(
title = "Species-aggregated sap flow vs species mean midday ∆T",
x = "Species mean midday ∆T (°C)",
y = LAB_JS_DAILY
) +
theme(legend.position = "none")
p_species_sap_dtc_pair
## `geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'
# ggsave(
# filename = here("output/research_days/p_species_sap_dtc_gam.png"),
# plot = p_species_sap_dtc_pair,
# width = 7,
# height = 5,
# units = "in",
# dpi = 300
# )
p_species_sap_dtc_pair <- species_daily_sap_dtc |>
filter(dtc_species_midday_mean>=0) |> # i need to look into if i should do this.
ggplot(aes(
y = dtc_species_midday_mean,
x = sap_species_mean,
color = species
)) +
geom_point(alpha = 0.8) +
geom_smooth(method = "gam", se = TRUE) +
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson",
size = 3
) +
scale_color_manual(values = SPECIES_COLS) +
labs(
title = "Species-aggregated sap flow vs species mean midday ∆T",
y = "Species mean midday ∆T (°C)",
x = LAB_JS_DAILY
) +
theme(legend.position = "none")
p_species_sap_dtc_pair
## `geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'
# CHANGED: repeat the species-level sap–∆T comparison using sap summed only
# from 10:00 to 14:00 so the sap window matches the midday ∆T metric.
species_midday_sap <- sap_long |>
filter(hour(ts) >= MIDDAY_START, hour(ts) <= MIDDAY_END) |>
group_by(sensor, tree, species, date) |>
summarise(
midday_total_sap = sum(sap_flow, na.rm = TRUE),
.groups = "drop"
) |>
group_by(species, date) |>
summarise(
midday_sap_species_mean = mean(midday_total_sap, na.rm = TRUE),
sd_midday_sap = sd(midday_total_sap, na.rm = TRUE),
n_sensors = n_distinct(sensor),
se_midday_sap = sd_midday_sap / sqrt(n_sensors),
.groups = "drop"
)
species_midday_sap_dtc <- species_midday_sap |>
left_join(species_daily_dtc, by = c("species", "date")) |>
filter(midday_sap_species_mean > 0)
p_species_midday_sap_dtc_pair <- species_midday_sap_dtc |>
filter(dtc_species_midday_mean >= 0) |>
ggplot(aes(
x = dtc_species_midday_mean,
y = midday_sap_species_mean,
color = species
)) +
geom_point(alpha = 0.8) +
geom_smooth(method = "gam", se = TRUE) +
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson",
size = 3
) +
scale_color_manual(values = SPECIES_COLS) +
labs(
title = "Species-aggregated midday sap flow vs species mean midday ∆T",
x = "Species mean midday ∆T (°C)",
y = "Midday total sap (10:00-14:00)"
) +
theme(legend.position = "none")
p_species_midday_sap_dtc_pair
## `geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'
p_species_midday_sap_dtc_pair_flipped <- species_midday_sap_dtc |>
filter(dtc_species_midday_mean >= 0) |>
ggplot(aes(
y = dtc_species_midday_mean,
x = midday_sap_species_mean,
color = species
)) +
geom_point(alpha = 0.8) +
geom_smooth(method = "gam", se = TRUE) +
stat_cor(
aes(label = paste(..r.label.., ..p.label.., sep = "~")),
method = "pearson",
size = 3
) +
scale_color_manual(values = SPECIES_COLS) +
labs(
title = "Species-aggregated midday sap flow vs species mean midday ∆T",
y = "Species mean midday ∆T (°C)",
x = "Midday total sap (10:00-14:00)"
) +
theme(legend.position = "none")
p_species_midday_sap_dtc_pair_flipped
## `geom_smooth()` using formula = 'y ~ s(x, bs = "cs")'