Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add thicknessOf() #306

Merged
merged 8 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ URL: https://github.com/ncss-tech/aqp, https://ncss-tech.github.io/AQP/
BugReports: https://github.com/ncss-tech/aqp/issues
Language: en-US
Encoding: UTF-8
RoxygenNote: 7.3.0
RoxygenNote: 7.3.1
Roxygen: list(markdown = TRUE)
VignetteBuilder: knitr
30 changes: 17 additions & 13 deletions R/thicknessOf.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
#'
#' This function calculates the cumulative (`method="cumulative"`, default) or maximum difference between (`method="minmax"`) horizons within a profile that match a defined pattern (`pattern`) or, more generally, any set of horizon-level logical expressions encoded in a function (`FUN`).
#'
#' @param x A SoilProfileCollection
#' @param pattern character. A pattern to match in `hzdesgn`; used with the default `FUN` definition for regular expression pattern matching on horizons.
#' @param hzdesgn character. A column containing horizon designations or other horizon-level character label used to identify matches; used with the default `FUN` definition.
#' @param method character. Either `"cumulative"` (default) or `"minmax"`. See details.
#' @param na.rm logical. Omit `NA` values in summaries of thickness and in matching? Default: `FALSE`
#' @param FUN function. A function that returns a _logical_ vector equal in length to the number of horizons in `x`. See details.
#' @param x A _SoilProfileCollection_
#' @param pattern _character_. A pattern to match in `hzdesgn`; used with the default `FUN` definition for regular expression pattern matching on horizons.
#' @param hzdesgn _character_. A column containing horizon designations or other horizon-level character label used to identify matches; used with the default `FUN` definition.
#' @param method _character_. Either `"cumulative"` (default) or `"minmax"`. See details.
#' @param prefix _character_. Column prefix for calculated `"thickness"` result, and `"min"`/`"max"` column results for `method="minmax"`. Default: `""`.
#' @param FUN _function_. A function that returns a _logical_ vector equal in length to the number of horizons in `x`. See details.
#' @param na.rm _logical_. Omit `NA` values in summaries of thickness and in matching? Default: `FALSE`
#' @param ... Additional arguments passed to the matching function `FUN`.
#'
#' @return A _data.frame_-like object (corresponding to the `aqp_df_class()` of `x`) with one row per profile in `x`. First column is always the profile ID which is followed by `"thickness"`. In `method="minmax"` the upper and lower boundaries used to calculate `"thickness"` are also returned as `"tmin"` and `"tmax"` columns, respectively.
Expand All @@ -29,24 +30,26 @@
#' thicknessOf(jacobs2000, "Bt")
#'
#' # maximum bottom depth minus minimum top depth of horizon designations matching "Bt"
#' thicknessOf(jacobs2000, "Bt", method = "minmax")
#' thicknessOf(jacobs2000, "Bt", prefix = "Bt_", method = "minmax")
#'
#' # cumulative thickness of horizon designations matching "A|B"
#' thicknessOf(jacobs2000, "A|B")
#' thicknessOf(jacobs2000, "A|B", prefix = "AorB_")
#'
#' # maximum bottom depth minus minimum top depth of horizon designations matching "A|B"
#' thicknessOf(jacobs2000, "A|B", method = "minmax")
#' thicknessOf(jacobs2000, "A|B", method = "minmax", prefix = "AorB_")
#' # note that the latter includes the thickness of E horizons between the A and the B
#'
#' # when using a custom function (be sure to accept ... and consider the effect of NA values)
#'
#' # calculate cumulative thickness of horizons containing >18% clay
#' thicknessOf(jacobs2000, FUN = function(x, ...) !is.na(x[["clay"]]) & x[["clay"]] > 18)
#' thicknessOf(jacobs2000, prefix = "claygt18_",
#' FUN = function(x, ...) !is.na(x[["clay"]]) & x[["clay"]] > 18)
#'
thicknessOf <- function(x,
pattern = NULL,
hzdesgn = guessHzDesgnName(x, required = TRUE),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember if we wanted to default to guessing (across all functions) when horizon names aren't specified or set in metadata. Currently there is a mixture of approaches across aqp.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you have more recently decided to not guess (which has broken or changed output in several places), but I don't think there is a rule we agreed on. If you are making one then we can start a change here.

The functions for guessing horizon designation, texture class names, and generic attributes are used in several places (pretty much all "my" functions I suppose) I could replace all uses of guessxx() with the metadata getter, and required=TRUE argument, but for now this is consistent with many of my other function usages. Let's address that (deprecating the 3 guess functions) in a separate issue or PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have started to address this in #309, and will make a corresponding fix in this PR. Thanks for prodding me on this, there was lots of room to improve consistency and I am happy to tackle it across all functions.

method = "cumulative",
prefix = "",
FUN = function(x, pattern, hzdesgn, ...) grepl(pattern, x[[hzdesgn]]),
na.rm = FALSE,
...) {
Expand All @@ -66,12 +69,13 @@ thicknessOf <- function(x,
h$.internalTHK <- x[[hzd[2]]] - x[[hzd[1]]]
res <- h[, list(thickness = sum(.internalTHK[.internalHZM], na.rm = na.rm)), by = lid]
} else if (method == "minmax") {
res <- h[, list(tmin = suppressWarnings(min(.SD[[1]][.internalHZM], na.rm = na.rm)),
tmax = suppressWarnings(max(.SD[[2]][.internalHZM], na.rm = na.rm))),
res <- h[, list(`min` = suppressWarnings(min(.SD[[1]][.internalHZM], na.rm = na.rm)),
`max` = suppressWarnings(max(.SD[[2]][.internalHZM], na.rm = na.rm))),
by = lid, .SDcols = c(hzd, ".internalHZM")]
res$thickness <- res$tmax - res$tmin
res$thickness <- res$`max` - res$`min`
res$thickness[!is.finite(res$thickness)] <- 0L
} else stop("unknown thickness method: ", shQuote(method), call. = FALSE)
colnames(res)[2:ncol(res)] <- paste0(prefix, colnames(res)[2:ncol(res)])
.as.data.frame.aqp(res, aqp_df_class(x))
}

24 changes: 14 additions & 10 deletions man/thicknessOf.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions tests/testthat/test-thicknessOf.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ test_that("thicknessOf works", {
## method="cumulative"

# cumulative thickness of horizon designations matching "A|B"
x1 <- thicknessOf(jacobs2000, "A|B")
x1 <- thicknessOf(jacobs2000, "A|B", prefix = "AorB_")
expect_equal(nrow(x1), length(jacobs2000))
expect_equal(x1$thickness, c(131, 117, 136, 20, 54, 110, 43))
expect_equal(x1$AorB_thickness, c(131, 117, 136, 20, 54, 110, 43))

## method="minmax"

# maximum bottom depth minus minimum top depth of horizon designations matching "A|B"
x2 <- thicknessOf(jacobs2000, "A|B", method = "minmax")
x2 <- thicknessOf(jacobs2000, "A|B", method = "minmax", prefix = "AorB_")
expect_equal(ncol(x2), 4)
expect_equal(x2$thickness, c(156, 145, 175, 20, 135, 168, 140))
expect_true(all(x2$thickness >= x1$thickness))
expect_equal(x2$AorB_min, rep(0, nrow(x2)))
expect_equal(x2$AorB_thickness, c(156, 145, 175, 20, 135, 168, 140))
expect_true(all(x2$AorB_thickness >= x1$AorB_thickness))

## custom logical function

Expand Down