Skip to contents
# minute data
data(kennel_2020)

Time domain

Single value

  • Clark’s 1967 method. Because Clark’s method is based on differences, the spacing between samples can affect the results. The following example uses differences of 1 minute, 1 hour, and 1 day for the Clark method.
  • Least Squares method. The least squares method can be based on differenced or non-differenced data. In the following example the non-differenced data is used in addition to the 1 minute, 1 hour, and 1 day differences.

If the values vary based on the interval used in the difference calculation, it may suggest that the system does not behave like an ideal confined system or observational noise is high relative to true changes.

static_time <- recipe(wl~., kennel_2020) |>
  step_baro_clark(wl,
                  baro,
                  lag_space = 1,
                  inverse = FALSE) |> # 1 minutes (every minute differences)
  step_baro_clark(wl,
                  baro, 
                  lag_space = 60,
                  inverse = FALSE) |> # 60 minutes (hourly differences)
  step_baro_clark(wl, 
                  baro, 
                  lag_space = 1440,
                  inverse = FALSE) |> # 1440 minutes (daily differences)
  step_baro_least_squares(wl, baro, 
                          inverse = FALSE) |> 
  step_baro_least_squares(wl, baro, 
                          lag_space = 1L,
                          inverse = FALSE,
                          differences = TRUE) |> # 1 minutes (every minute differences)
  step_baro_least_squares(wl, baro, 
                          lag_space = 60L,
                          inverse = FALSE,
                          differences = TRUE) |> # 60 minutes (hourly differences)
  step_baro_least_squares(wl, baro, 
                          lag_space = 1440L,
                          inverse = FALSE,
                          differences = TRUE) |> # 1440 minutes (daily differences)
  prep() |>
  bake()

# The barometric efficiency is stored in the step (warning: this may change)
static_time$get_step_data("barometric_efficiency",
                          additional_columns = c("step_name", 
                                                 "lag_space", 
                                                 "differences"),
                          type = "dt")
#>           be               step_name lag_space differences
#>        <num>                  <char>     <num>      <lgcl>
#> 1: 0.1941766         step_baro_clark         1        TRUE
#> 2: 0.4738098         step_baro_clark        60        TRUE
#> 3: 0.8453437         step_baro_clark      1440        TRUE
#> 4: 0.8273019 step_baro_least_squares         1       FALSE
#> 5: 0.2493419 step_baro_least_squares         1        TRUE
#> 6: 0.4529153 step_baro_least_squares        60        TRUE
#> 7: 0.8410913 step_baro_least_squares      1440        TRUE

Response functions

Standard lags


formula <- as.formula(wl~.)

lag_standard <- recipe(formula = formula, data = kennel_2020) |>
  step_lead_lag(baro, lag = 0:1440) |> 
  step_spline_b(datetime, df = 11, intercept = TRUE) |> 
  step_drop_columns(c(baro, datetime, et)) |>
  step_ols(formula = formula) |>
  prep() |>
  bake()
resp_l <- lag_standard$get_response_data()

Distributed lags

lag_dl <- recipe(wl~., kennel_2020) |>
  step_distributed_lag(baro, knots = log_lags(15, 1440), intercept = TRUE) |> 
  step_spline_b(datetime, df = 11, intercept = TRUE) |> 
  step_drop_columns(c(baro, datetime, et)) |>
  step_ols(wl~.) |> 
  prep() |>
  bake()
resp_dl <- lag_dl$get_response_data()
resp_dl <- lag_dl$get_step_data("response_data", 
                                type = "dt")

plot(value~x, resp_l[resp_l$term == "lead_lag" & resp_l$variable == "cumulative", ],
     type = "l", ylim = c(0, 1),
     xlab = "Lag in minutes",
     ylab = "Cumulative Response"
     )
points(value~x, resp_dl[resp_dl$term == "distributed_lag_interpolated" & resp_dl$variable == "cumulative", ],
       type = "l", col = "red")

Barometric response functions with standard leading and lagging method compared to the distributed lag method.  Both methods are generally the same but the distributed lag method is smoother.

Frequency domain

Single value

These methods are based on the 2 cpd frequency which may not be indicative of the static barometric efficiency, in cases with a frequency dependent response (wellbore storage or non-confined conditions).

harmonic <- recipe(wl~., kennel_2020) |>
  step_baro_harmonic(datetime, wl, baro, et) |> 
  prep() |>
  bake()

harmonic$get_step_data("barometric_efficiency")
#> [[1]]
#> [[1]]$ratio
#> [1] 0.5725228
#> 
#> [[1]]$acworth
#> [1] 0.5940213
#> 
#> [[1]]$rau
#> [1] 0.6108032
#> 
#> [[1]]$tf
#> [1] 0.5844761

Transfer function

Three methods:

  • pgram based
    • assumes smooth periodogram
  • Welch’s
    • loss of low frequency information
    • optimal values dependent on the subset length
  • Experimental
    • assumes smooth periodogram
    • smoothing across multiple frequencies
    • many higher frequecies discarded
    • should be faster
    • least squares fit of multiple frequencies

# this formula determines the variables to use for the transfer functions
formula_tf <- as.formula(wl ~ baro + et)

pg = recipe(wl~., data = kennel_2020) |>
  step_fft_transfer_welch(formula = formula_tf, 
                          length_subset = 40000,
                          window = hydrorecipes:::window_rectangle(40000),
                          time_step = 60) |>
  step_fft_transfer_pgram(formula = formula_tf, 
                          spans = c(3, 3), 
                          time_step = 60) |>
  step_fft_transfer_experimental(formula = formula_tf, 
                                 n_groups = 150,
                                 spans = c(3, 3), 
                                 time_step = 60) |>
  prep("df") |>
  bake()

tf <- pg$get_transfer_data(type = "dt")

# plot the barometric transfer functions for the different methods
plot(Mod(value)~frequency, 
     data = tf[grepl("fft_transfer_experimental", id) & variable == "wl_baro_et_1"], 
     type = "l", 
     log = "x", 
     xlim = c(0.01, 200),
     ylim = c(0, 1),
     col = "#00900080")
#> Warning in xy.coords(x, y, xlabel, ylabel, log): 1 x value <= 0 omitted from
#> logarithmic plot
points(Mod(value)~frequency, 
       data = tf[grepl("fft_transfer_welch", id) & variable == "wl_baro_et_1"], 
       type = "l",
       col = "#90000050")
points(Mod(value)~frequency, 
       tf[grepl("fft_transfer_pgram", id) & variable == "wl_baro_et_1"],
       type = "l", 
       col = "#00009050")

Transfer functions.




# plot the amplitude
plot(Mod(value)~frequency, 
     data = tf[grepl("fft_transfer_experimental", id) & variable == "wl_baro_et_1"], 
     type = "l", 
     log = "x", 
     xlim = c(0.01, 200),
     ylim = c(0, 1),
     col = "#00900080")
#> Warning in xy.coords(x, y, xlabel, ylabel, log): 1 x value <= 0 omitted from
#> logarithmic plot

Transfer functions.


# plot the phase
plot(Arg(value)~frequency, 
     data = tf[grepl("fft_transfer_experimental", id) & variable == "wl_baro_et_1"], 
     type = "l", 
     log = "x", 
     xlim = c(0.01, 200),
     col = "#00900080")
#> Warning in xy.coords(x, y, xlabel, ylabel, log): 1 x value <= 0 omitted from
#> logarithmic plot

Transfer functions.


# tf <- pg$get_transfer_data(type = "dt")
# tmp <-  tf[grepl("fft_transfer_experimental", id) & variable == "wl_baro_et_1"]
# tmp[, frequency_log := log10(frequency)]
# tmp <- tmp[-c(1, .N)]
# fit_r <- lm(Re(value)~splines2::bSpline(frequency_log, df = 10), tmp)
# fit_i <- lm(Im(value)~splines2::bSpline(frequency_log, df = 10), tmp)
# plot(Re(value)~frequency_log, tmp)
# points(fit_r$fitted.values~frequency_log, tmp, type = "l", col = "red")
# plot(fit_r$fitted.values~frequency, tmp, type = "l", col = "red")
# 
# plot(Im(value)~frequency_log, tmp)
# points(fit_i$fitted.values~frequency_log, tmp, type = "l", col = "red")
# 
# plot(Im(value)~frequency, tmp)
# points(fit_i$fitted.values~frequency, tmp, type = "l", col = "red")
# 
# brf_from_frf <- function(x) {
# 
#   n <- floor(length(x) / 2) + 1
# 
#   imp <- fft(x, inverse = TRUE) / length(x)
#   imp <- Mod(imp) * sign(Re(imp))
# 
#   cumsum(rev(imp)[1:n] + (imp)[1:n])
# 
# }
# 
# 
# x <- tmp$frequency
# y <- tmp$value
# frequency_to_time_domain <- function(x, y) {
# 
#   dc1 <- y[1] # DC values
#   dc2 <- y[length(y)] # DC values
#   x_n <- x[-c(1, length(x))] # DC indices
#   y_n <- y[-c(1, length(y))] # DC values
#   
#   # knots <- hydrorecipes:::log_lags(20, max(x_n))
#   # knots <- knots[-c(1, length(knots))]
#   # len <- min(knots):max(knots)
#   len <- seq(min(x_n), max(x_n), length.out = 10000)
# # this is used to smooth the complex response
#   sp_in  <- splines2::bSpline(x_n, df = 10)
#   sp_out <- splines2::bSpline(len, df = 10)
# 
# 
#     # out <- matrix(NA_real_,
#     #               nrow = length(len),
#     #               ncol = 1)
#     # out[1] <- len
# 
# # loop through each transfer function - smooth the response - estimate brf
#     # for (i in 1:ncol(y_n)) {
# 
# # smooth the real and imaginary components separately
#   re <- Re(y_n)
#   im <- Im(y_n)
#   
#   fit_r <- RcppEigen::fastLm(X = sp_in, y = re)$coefficients
#   fit_i <- RcppEigen::fastLm(X = sp_in, y = im)$coefficients
#   
#   re <- as.vector(sp_out %*% fit_r)
#   im <- as.vector(sp_out %*% fit_i)
# 
# # estimate the brf from the smoothed uniform spaced frf
#         out <- brf_from_frf(complex(real = re, imaginary = im))
#     # }
# 
#         plot(out, type = "l", log = "x")
# 
#     out
# }
# tmp <- tf[grepl("fft_transfer_experimental", id) & variable == "wl_baro_et_1"]
# frequency_to_time_domain(x = tmp$frequency, 
#                          y = tmp$value)

References

sessionInfo()
#> R version 4.4.2 (2024-10-31)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.1 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] hydrorecipes_0.0.6 Bessel_0.6-1      
#> 
#> loaded via a namespace (and not attached):
#>  [1] cli_3.6.3         knitr_1.49        rlang_1.1.5       xfun_0.50        
#>  [5] textshaping_1.0.0 data.table_1.16.4 jsonlite_1.8.9    htmltools_0.5.8.1
#>  [9] earthtide_0.1.7   gslnls_1.4.1      ragg_1.3.3        sass_0.4.9       
#> [13] gmp_0.7-5         rmarkdown_2.29    grid_4.4.2        evaluate_1.0.3   
#> [17] jquerylib_0.1.4   fastmap_1.2.0     yaml_2.3.10       lifecycle_1.0.4  
#> [21] compiler_4.4.2    fs_1.6.5          htmlwidgets_1.6.4 Rcpp_1.0.14      
#> [25] lattice_0.22-6    systemfonts_1.2.1 digest_0.6.37     collapse_2.0.19  
#> [29] R6_2.5.1          parallel_4.4.2    Matrix_1.7-1      bslib_0.8.0      
#> [33] tools_4.4.2       Rmpfr_1.0-0       RcppThread_2.2.0  pkgdown_2.1.1    
#> [37] cachem_1.1.0      desc_1.4.3