|
| 1 | +#' Find breakpoints in DFI curve |
| 2 | +#' |
| 3 | +#' @param dfi numeric, DFI values between 1 and 0 |
| 4 | +#' @param n_bp numeric, number of breakpoints |
| 5 | +#' @param min_gp_gap numeric, smallest interval between two breakpoints |
| 6 | +#' @param filter_min numeric, filter_max+1 is minimum potential breakpoint estimate |
| 7 | +#' @param filter_max numeric, filter_max-1 is maximum potential breakpoint estimate |
| 8 | +#' @param dfi_check logical, if TRUE DFI values are converted to be continuously descreasing with cummin() |
| 9 | +#' @param print logical, if TRUE calculation of breakpoints are printed |
| 10 | +#' @param plotting logical, if TRUE the DFI curve and piecewise linear segments are plotted |
| 11 | +#' |
| 12 | +#' @return Returns a list. |
| 13 | +#' \item{breakpoints}{estimates for the n breakpoints with names "bp_x"} |
| 14 | +#' \item{bias}{value of the objective function OF = 1/2 RMSE + 1/2 MAE"} |
| 15 | +#' \item{rel_contr}{Relative streamflow contributions between \code{filter_min}, the breakpoints and \code{filter_max}} |
| 16 | + |
| 17 | +#' |
| 18 | +#' @export |
| 19 | +#' @examples |
| 20 | +#' # use dfi_example as an DFI vector with 121 values |
| 21 | +#' find_bp(dfi_example, n_bp = 2, filter_max = 90, dfi_check = FALSE) |
| 22 | +find_bp <- function(dfi, |
| 23 | + n_bp = 3, |
| 24 | + min_gp_gap = 5, |
| 25 | + filter_min = 0, |
| 26 | + filter_max = 120, |
| 27 | + dfi_check = TRUE, |
| 28 | + print = FALSE, |
| 29 | + plotting = FALSE) { |
| 30 | + |
| 31 | + len <- length(dfi) |
| 32 | + |
| 33 | + if(any(is.na(dfi))) { |
| 34 | + stop("DFI vector has NA values.") |
| 35 | + } |
| 36 | + if(len <= filter_max) { |
| 37 | + stop("DFI vector is too short or reduce filter_max, i.e. length(dfi) > filter_max") |
| 38 | + } |
| 39 | + if(filter_max < filter_min + 5){ |
| 40 | + stop("Adjust \"filter_min\" or \"filter_max\"!" ) |
| 41 | + } |
| 42 | + if(min_gp_gap <= 2) { |
| 43 | + stop("\"min_gap\" is too small, should be 3 or larger.") |
| 44 | + } |
| 45 | + if(!n_bp %in% 1:3) { |
| 46 | + stop("Number of breakpoints must be 1,2 or 3") |
| 47 | + } |
| 48 | + if(dfi_check) { |
| 49 | + dfi <- cummin(dfi) |
| 50 | + } |
| 51 | + |
| 52 | + # breakpoint grid |
| 53 | + len <- length(dfi) |
| 54 | + pot_bp <- (filter_min+1):(filter_max-1) |
| 55 | + bp_grid <- expand.grid(rep(list(pot_bp),n_bp)) |
| 56 | + names(bp_grid) <- paste0("bp_",1:n_bp) |
| 57 | + |
| 58 | + # create breakpoint combinations |
| 59 | + if (n_bp == 1) bp_grid <- as.matrix(pot_bp) |
| 60 | + |
| 61 | + if (n_bp == 2) { |
| 62 | + bp_grid <- subset(bp_grid, (bp_1 < bp_2 & bp_1 + min_gp_gap <= bp_2)) |
| 63 | + bp_grid <- as.matrix(bp_grid, rownames.force = FALSE) |
| 64 | + } |
| 65 | + if (n_bp == 3) { |
| 66 | + bp_grid <- subset(bp_grid, (bp_1 < bp_2 & |
| 67 | + bp_1 + min_gp_gap <= bp_2 & |
| 68 | + bp_2 < bp_3 & |
| 69 | + bp_2 + min_gp_gap <= bp_3 )) |
| 70 | + bp_grid <- as.matrix(bp_grid, rownames.force = FALSE) |
| 71 | + } |
| 72 | + |
| 73 | + # fit breakpoint model |
| 74 | + dummy <- 999 |
| 75 | + best_bps <- NULL |
| 76 | + cat("Calculating breakpoints . . . \n") |
| 77 | + for (i in 1:nrow(bp_grid)) { |
| 78 | + |
| 79 | + bps <- bp_grid[i,] |
| 80 | + fit <- approx(x = c(0,bps,len-1), dfi[c(1,bps+1,len)], xout = 0:(len-1))$y |
| 81 | + OF <- rmse(fit,dfi) * 0.50 + mae(fit, dfi) * 0.50 |
| 82 | + if(OF < dummy){ |
| 83 | + if(print) cat(i,bps,OF,"\n") |
| 84 | + dummy <- OF |
| 85 | + best_fit <- fit |
| 86 | + result <- list(breakpoints = bps, bias = dummy ) |
| 87 | + } |
| 88 | + |
| 89 | + if(i %in% floor(nrow(bp_grid) * 1:10/10)) cat(paste0(round(i/nrow(bp_grid)*100,0)),"% ") |
| 90 | + |
| 91 | + } |
| 92 | + cat("\nBreakpoints ready . . . \n\n\n") |
| 93 | + |
| 94 | +result$rel_contr <- -diff(c(dfi[c(1, result$breakpoints+1)], 0)) |
| 95 | + |
| 96 | +if (plotting){ |
| 97 | + plot(0:120, dfi, |
| 98 | + type = "l", |
| 99 | + col = "blue", |
| 100 | + ylim = c(0,1), |
| 101 | + ylab = "DFI", |
| 102 | + xlab = "Filter width N") |
| 103 | + points(0:120, best_fit, type = "l", col = "red") |
| 104 | + abline(v=result$breakpoints) |
| 105 | +} |
| 106 | + |
| 107 | + |
| 108 | +return(result) |
| 109 | +} |
0 commit comments