if ("tidyverse" %in% rownames(installed.packages()) == 'FALSE') install.packages('tidyverse')
if ("broom" %in% rownames(installed.packages()) == 'FALSE') install.packages('broom')
1 Background
On July 10th I calibrated the HOBO logs to a Kessler NIST K750 RTD Thermometer and an apogee instruments underwater quantum flux PAR meter.
Each was a 2 point calibration matching time-stamp to reference instrument read-out.
NIST temp & time 27.03 , 07/10/2024 10:40:00 27.26, 07/10/2024 11:00:00
Apogee par & time 1600, 07/10/2024 11:10:00 760, 07/10/2024 11:40:00
1.1 Inputs
1.2 Outputs
2 Setup
2.1 Install packages
2.2 Load libraries
library(tidyverse)
library(broom) # for tidy()
2.3 Load HOBO logs
<- read.csv("Amb1_blue 2024-07-10 13_10_13 HST (Data HST).csv")
amb1_blue <- read.csv("Amb1_bluegreen 2024-07-10 13_10_51 HST (Data HST).csv")
amb1_bluegreen <- read.csv("Amb2_orangepink 2024-07-10 11_47_18 HST (Data HST).csv")
amb2_orangepink <- read.csv("Amb2_pink 2024-07-10 11_48_03 HST (Data HST).csv")
amb2_pink <- read.csv("Hot1_black 2024-07-10 13_09_24 HST (Data HST).csv")
hot1_black <- read.csv("Hot1_green 2024-07-10 13_08_26 HST (Data HST).csv")
hot1_green <- read.csv("Hot2_orange 2024-07-10 11_44_18 HST (Data HST).csv")
hot2_orange <- read.csv("Hot2_yellow 2024-07-10 11_46_08 HST (Data HST).csv") hot2_yellow
3 References
# Example inputs for temperature calibration
<- c(27.03, 27.26) # NIST temperatures
ref_temp <- as.POSIXct(c("07/10/2024 10:40:00", "07/10/2024 11:00:00"),
ref_time_temp format="%m/%d/%Y %H:%M:%S")
# For PAR calibration
<- c(1600, 760)
ref_par <- as.POSIXct(c("07/10/2024 11:10:00", "07/10/2024 11:40:00"),
ref_time_par format="%m/%d/%Y %H:%M:%S")
<- tibble::tibble(
cal_table datetime = as.POSIXct(c("2024-07-10 10:40:00", "2024-07-10 11:00:00",
"2024-07-10 11:10:00", "2024-07-10 11:40:00")),
ref_temp = c(27.03, 27.26, NA, NA), # NIST
ref_par = c(NA, NA, 1600, 760) # Apogee PAR
)
4 Format logs
- amb1_blue
<- amb1_blue %>%
amb1_blue rename(datetime = Date.Time..HST.,
temp = Temperature.....C.,
lux = Light....lux.) %>%
mutate(datetime = as.POSIXct(datetime, format = "%m/%d/%Y %H:%M:%S")) %>%
mutate(pendent = "amb1_blue") %>%
select(datetime, temp, lux, pendent) %>%
filter(datetime %in% c(ref_time_temp, ref_time_par))
- amb1_bluegreen
<- amb1_bluegreen %>%
amb1_bluegreen rename(datetime = Date.Time..HST.,
temp = Temperature.....C.,
lux = Light....lux.) %>%
mutate(datetime = as.POSIXct(datetime, format = "%m/%d/%Y %H:%M:%S")) %>%
mutate(pendent = "amb1_bluegreen") %>%
select(datetime, temp, lux, pendent) %>%
filter(datetime %in% c(ref_time_temp, ref_time_par))
- amb2_orangepink
<- amb2_orangepink %>%
amb2_orangepink rename(datetime = Date.Time..HST.,
temp = Temperature.....C.,
lux = Light....lux.) %>%
mutate(datetime = as.POSIXct(datetime, format = "%m/%d/%Y %H:%M:%S")) %>%
mutate(pendent = "amb2_orangepink") %>%
select(datetime, temp, lux, pendent) %>%
filter(datetime %in% c(ref_time_temp, ref_time_par))
- amb2_pink
<- amb2_pink %>%
amb2_pink rename(datetime = Date.Time..HST.,
temp = Temperature.....C.,
lux = Light....lux.) %>%
mutate(datetime = as.POSIXct(datetime, format = "%m/%d/%Y %H:%M:%S")) %>%
mutate(pendent = "amb2_pink") %>%
select(datetime, temp, lux, pendent) %>%
filter(datetime %in% c(ref_time_temp, ref_time_par))
- hot1_black
<- hot1_black %>%
hot1_black rename(datetime = Date.Time..HST.,
temp = Temperature.....C.,
lux = Light....lux.) %>%
mutate(datetime = as.POSIXct(datetime, format = "%m/%d/%Y %H:%M:%S")) %>%
mutate(pendent = "hot1_black") %>%
select(datetime, temp, lux, pendent) %>%
filter(datetime %in% c(ref_time_temp, ref_time_par))
- hot1_green
<- hot1_green %>%
hot1_green rename(datetime = Date.Time..HST.,
temp = Temperature.....C.,
lux = Light....lux.) %>%
mutate(datetime = as.POSIXct(datetime, format = "%m/%d/%Y %H:%M:%S")) %>%
mutate(pendent = "hot1_green") %>%
select(datetime, temp, lux, pendent) %>%
filter(datetime %in% c(ref_time_temp, ref_time_par))
- hot2_orange
<- hot2_orange %>%
hot2_orange rename(datetime = Date.Time..HST.,
temp = Temperature.....C.,
lux = Light....lux.) %>%
mutate(datetime = as.POSIXct(datetime, format = "%m/%d/%Y %H:%M:%S")) %>%
mutate(pendent = "hot2_orange") %>%
select(datetime, temp, lux, pendent)%>%
filter(datetime %in% c(ref_time_temp, ref_time_par))
- hot2_yellow
<- hot2_yellow %>%
hot2_yellow rename(datetime = Date.Time..HST.,
temp = Temperature.....C.,
lux = Light....lux.) %>%
mutate(datetime = as.POSIXct(datetime, format = "%m/%d/%Y %H:%M:%S")) %>%
mutate(pendent = "hot2_yellow") %>%
select(datetime, temp, lux, pendent) %>%
filter(datetime %in% c(ref_time_temp, ref_time_par))
Bind logs
<- rbind(amb1_blue,
cal_logs
amb1_bluegreen,
amb2_orangepink,
amb2_pink,
hot1_black,
hot1_green,
hot2_orange, hot2_yellow)
5 Create calibrations
5.1 Temp
For each sensor, calculate slope and intercept using the two point calibration
# Define your calibration temperature reference times
<- as.POSIXct(c("2024-07-10 10:40:00", "2024-07-10 11:00:00"))
cal_temp_times
# Sample dataframe 'cal_logs' containing datetime, temp, lux, pendent
# Step 1: Filter calibration temp points only
<- cal_logs %>%
temp_cal_points filter(datetime %in% cal_temp_times) %>%
select(pendent, datetime, temp) %>%
mutate(ref_temp = ifelse(datetime == cal_temp_times[1], 27.03, 27.26))
# Step 2: Group by sensor and fit linear model temp ~ ref_temp
<- temp_cal_points %>%
temp_fit_results group_by(pendent) %>%
summarise(
model = list(lm(ref_temp ~ temp, data = cur_data()))
%>%
) mutate(
coef = map(model, tidy)
%>%
) unnest(coef) %>%
select(pendent, term, estimate) %>%
pivot_wider(names_from = term, values_from = estimate) %>%
rename(intercept = `(Intercept)`, slope = temp)
# fit_results now contains one row per sensor with slope and intercept ready to use
print(temp_fit_results)
# A tibble: 8 × 3
pendent intercept slope
<chr> <dbl> <dbl>
1 amb1_blue 19.8 0.256
2 amb1_bluegreen 19.1 0.284
3 amb2_orangepink 18.2 0.315
4 amb2_pink 19.1 0.284
5 hot1_black 19.5 0.271
6 hot1_green 18.3 0.315
7 hot2_orange 19.5 0.267
8 hot2_yellow 17.6 0.338
Average temp calibration
<- temp_fit_results %>%
avg_temp summarize(
avg_temp_intercept = mean(intercept),
avg_temp_slope = mean(slope))
avg_temp
avg_temp_intercept | avg_temp_slope |
---|---|
18.89328 | 0.2912324 |
5.2 PAR
# Define your calibration PAR reference times
<- as.POSIXct(c("2024-07-10 11:10:00", "2024-07-10 11:40:00"))
cal_par_times
# Sample dataframe 'cal_logs' containing datetime, temp, lux, pendent
# Step 1: Filter calibration par points only
<- cal_logs %>%
par_cal_points filter(datetime %in% cal_par_times) %>%
select(pendent, datetime, lux) %>%
mutate(ref_par = ifelse(datetime == cal_par_times[1], 1600, 760))
# Step 2: Group by sensor and fit linear model lux ~ ref_par
<- par_cal_points %>%
par_fit_results group_by(pendent) %>%
summarise(
model = list(lm(ref_par ~ lux, data = cur_data()))
%>%
) mutate(
coef = map(model, tidy)
%>%
) unnest(coef) %>%
select(pendent, term, estimate) %>%
pivot_wider(names_from = term, values_from = estimate) %>%
rename(intercept = `(Intercept)`, slope = lux)
# fit_results now contains one row per sensor with slope and intercept ready to use
print(par_fit_results)
# A tibble: 8 × 3
pendent intercept slope
<chr> <dbl> <dbl>
1 amb1_blue -1400. 0.0626
2 amb1_bluegreen -1428. 0.0727
3 amb2_orangepink -776. 0.0573
4 amb2_pink -4905. 0.158
5 hot1_black -1427. 0.0697
6 hot1_green -1797. 0.0868
7 hot2_orange -1968. 0.0805
8 hot2_yellow -1998. 0.0957
Average PAR calibration
<- par_fit_results %>%
avg_par summarize(
avg_par_intercept = mean(intercept),
avg_par_slope = mean(slope))
avg_par
avg_par_intercept | avg_par_slope |
---|---|
-1962.403 | 0.0853845 |
6 Apply calibrations
To apply the intercept and slope from your linear regression calibration to raw data and generate calibrated values, you use the formula:
\[ \text{Calibrated value} = (\text{slope} \times \text{raw value}) + \text{intercept} \]