Background
Fourteen stem psychrometers were installed on seven individual Juniperus osteosperma at an Ameriflux eddy covariance site in southeastern Utah (US-CdM) between 2021-05-21 and 2021-11-06. Here, we intend to combine and plot the stem water potential with soil water content and VPD in order to identify flagged values.
Stem psychrometer data inputs
Individual .csv files were combined by S. Kannenberg and labeled with the associated tree and logger ID. These raw data were read in and the timestamps were rounded to the nearest half hour.
psy <- read.csv("../data_raw/Merged-psychrometer-data.csv", encoding="UTF-8") %>%
rename(chamber_temp = 5,
dT = 6,
wet_bulb = 7,
psy = 8,
correction_dT = 12,
correction = 13,
batt_volt = 14,
int_batt_temp = 15,
ext_power = 16,
ext_power_volt = 17,
ext_power_current = 18,
comment = 19) %>%
mutate(Date = as.POSIXct(Date, format = "%m/%d/%Y", tz = "America/Denver"),
datetime = as.POSIXct(paste0(Date, Time), tz = "America/Denver"),
dt = round.POSIXct(datetime, units = "30 mins"))
Occasionally, the psychrometer dataloggers record two measurements for the same half-hour. We identify duplicates and take the average.
psy %>%
group_by(Tree, Logger) %>%
summarize(n = n(),
n.dt = length(unique(dt)),
ndiff = n - n.dt)
## # A tibble: 14 x 5
## # Groups: Tree [7]
## Tree Logger n n.dt ndiff
## <int> <int> <int> <int> <int>
## 1 1 1 8110 8108 2
## 2 1 2 6931 6931 0
## 3 2 1 8115 8114 1
## 4 2 2 8113 8113 0
## 5 3 1 8108 8105 3
## 6 3 2 8107 8106 1
## 7 4 1 5864 5861 3
## 8 4 2 8050 8049 1
## 9 5 1 3101 3101 0
## 10 5 2 8077 8077 0
## 11 6 1 3979 3978 1
## 12 6 2 5865 5865 0
## 13 7 1 8072 8072 0
## 14 7 2 8030 8028 2
Roughly half of the loggers had at least one duplication, but none had more than three. Next, we take the mean values of the duplicates.
psy2 <- psy %>%
group_by(Tree, Logger, dt) %>%
summarize(chamber_temp = mean(chamber_temp),
dT = mean(dT),
psy = mean(psy),
Intercept = mean(Intercept),
Slope = mean(Slope),
EDBO = mean(EDBO),
correction_dT = mean(correction_dT),
correction = mean(correction))
Finally, let’s check the range of psy measurements for each tree and logger.
psy2 %>%
group_by(Tree, Logger) %>%
summarize(n = sum(!is.na(psy)),
n_na = sum(is.na(psy)),
n_12.5 = length(which(psy < -12.5)),
n_0 = length(which(psy >= 0)),
min_psy = min(psy, na.rm = TRUE),
max_psy = max(psy, na.rm = TRUE))
## # A tibble: 14 x 8
## # Groups: Tree [7]
## Tree Logger n n_na n_12.5 n_0 min_psy max_psy
## <int> <int> <int> <int> <int> <int> <dbl> <dbl>
## 1 1 1 8108 0 0 1751 -10.8 0
## 2 1 2 6924 7 0 1335 -6.8 0.49
## 3 2 1 8114 0 1 565 -25.2 2.72
## 4 2 2 8113 0 0 1729 -4.44 0
## 5 3 1 8105 0 0 132 -12.2 0
## 6 3 2 8106 0 0 1 -8.19 0
## 7 4 1 5767 94 0 6 -9 0
## 8 4 2 8049 0 1 2474 -71.6 0
## 9 5 1 3087 14 0 83 -7.36 0
## 10 5 2 8060 17 1 146 -102. 0
## 11 6 1 3494 484 0 57 -5.39 0
## 12 6 2 5728 137 1 251 -49.9 0
## 13 7 1 8056 16 0 1729 -8.07 0
## 14 7 2 7875 153 0 762 -8.83 0
The number of missing values per logger ranges from 0 to 484. Also, we can remove points that are less than -12.5 and greater than or equal to 0.
psy3 <- psy2 %>%
filter(psy > -12.5 & psy <0)
Combine datasets for plotting
Finally, we convert the psy column to a wide format to match with the met data.
psy_wide <- psy3 %>%
select(Tree, Logger, dt, psy) %>%
tidyr::pivot_wider(names_from = c(Tree, Logger),
values_from = psy) %>%
left_join(met)
Maintenance days
Monthly site visits were made, wherein the psychrometer sensors were removed, cleaned, and reattached to a fresh branch on the same tree. We expect some odd values during these periods and want to highlight them on the visualization.
maint <- read_csv("../data_raw/Maintenance-times.csv") %>%
mutate(Start = as.POSIXct(Start, tz = "America/Denver"),
End= as.POSIXct(End, tz = "America/Denver"))
Dates to skip
Psychrometers can fail to record a realistic \(\Psi\) for a number of reasons, including primarily the loss of contact and good seal between living xylem and the chamber. We have identified periods where either the contact deteriorates over time until the chamber is re-installed (‘Loss of contact’), contact is simply poor and results in spiky readings for a period before resolving naturally (‘Poor contact, very spiky’), or particular spikes are noted, often across loggers and trees and potentially associated with weather phenomenon (‘Unknown spike’). We read in these dates, which can be manually updated during the process of data cleaning.
skip <- read_csv("../data_raw/Psy-skip-dates.csv") %>%
mutate(Start = as.POSIXct(st, format = "%m/%d/%Y %H:%M:%S", tz = "America/Denver"),
End= as.POSIXct(en, format = "%m/%d/%Y %H:%M:%S", tz = "America/Denver"))
Creating interactive plots
For each tree, we would like to interactively plot the time series from both loggers and match with a second panel of VPD and soil VWC, possibly at two depths. The dygraph input needs to be an xts object.
Edit timeseries
Cut the maintenance days (all loggers) and skip days (tree- and logger-specific) from the half-hourly timeseries and output as .csv file.
# Function to find overlap in maintenance times
maint.overlap <- function(vals) {
rowSums(mapply(function(a,b) between(vals, a, b),
maint$Start, maint$End)) > 0
}
psy4 <- psy3 %>%
filter(!test.overlap(dt))
# Create sequence of half-hours
skip_hourly <- data.frame()
for(i in 1:nrow(skip)) {
temp <- data.frame(dt = seq(skip$Start[i], skip$End[i], by = 30*60)) %>%
mutate(Tree = skip$Tree[i],
Logger = skip$Logger[i])
skip_hourly <- rbind.data.frame(skip_hourly, temp)
}
psy5 <- anti_join(psy4, skip_hourly)
write_csv(psy5, file = "../data_cleaned/psy_hourly.csv")