Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
^\.github$
^data-raw$
^README\.Rmd$
^doc$
^Meta$
4 changes: 2 additions & 2 deletions .github/workflows/R-CMD-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- {os: windows-latest, r: 'release'}
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
- {os: ubuntu-latest, r: 'release'}
- {os: ubuntu-20.04, r: '3.6'}
- {os: ubuntu-22.04, r: '3.6'}

env:
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
Expand All @@ -42,7 +42,7 @@ jobs:

- uses: actions/setup-python@v4
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
python-version: '3.14' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified

- uses: r-lib/actions/setup-pandoc@v2
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
.RData
.Ruserdata
^rosettaPTF\.Rproj$
README.html
/doc/
/Meta/
8 changes: 5 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Package: rosettaPTF
Title: R Frontend for Rosetta Pedotransfer Functions
Version: 0.1.5
Version: 0.2.0
Author: Soil and Plant Science Division Staff
Maintainer: Andrew G. Brown <andrew.g.brown@usda.gov>
Description: Access Python rosetta-soil pedotransfer functions in an R environment. Rosetta is a neural network-based model for predicting unsaturated soil hydraulic parameters from basic soil characterization data. The model predicts parameters for the van Genuchten unsaturated soil hydraulic properties model, using sand, silt, and clay, bulk density and water content. The codebase is now maintained by Dr. Todd Skaggs and other U.S. Department of Agriculture employees. This R package is intended to provide for use cases that involve many thousands of calls to the pedotransfer function. Less demanding use cases are encouraged to use the web interface or API endpoint. There are additional wrappers of the API endpoints provided by the soilDB R package `ROSETTA()` method.
Description: Access the rosetta-soil Python pedotransfer functions from R. Rosetta is a neural network-based model for predicting unsaturated soil hydraulic parameters from basic soil characterization data (sand, silt, clay, bulk density, and water content). Predictions are made for the van Genuchten unsaturated hydraulic properties model, with uncertainty quantification via bootstrap ensemble. Designed for efficient batch processing of large datasets through vectorized computation and optional parallel processing.
Config/reticulate:
list(
packages = list(
Expand All @@ -15,7 +15,7 @@ License: GPL (>= 2)
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
RoxygenNote: 7.3.3
Depends: R (>= 3.5)
URL: https://github.com/ncss-tech/rosettaPTF, https://ncss-tech.github.io/rosettaPTF/
BugReports: https://github.com/ncss-tech/rosettaPTF/issues
Expand All @@ -24,4 +24,6 @@ Imports:
reticulate,
terra
Suggests:
litedown,
testthat
VignetteBuilder: litedown
5 changes: 5 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Generated by roxygen2: do not edit by hand

S3method(ann_predict,Rosetta)
S3method(ann_predict,default)
S3method(predict,Rosetta)
S3method(predict,UnsaturatedK)
S3method(py_to_r,rosetta.rosetta.SoilData)
S3method(run_rosetta,RasterBrick)
S3method(run_rosetta,RasterStack)
Expand All @@ -11,10 +13,12 @@ S3method(run_rosetta,default)
S3method(run_rosetta,matrix)
export(Rosetta)
export(SoilDataFromArray)
export(UnsaturatedK)
export(ann_predict)
export(find_python)
export(get_rosetta_module)
export(install_rosetta)
export(rosesoil)
export(rosetta_module_available)
export(run_rosetta)
importFrom(parallel,makeCluster)
Expand All @@ -32,6 +36,7 @@ importFrom(reticulate,r_to_py)
importFrom(reticulate,use_condaenv)
importFrom(reticulate,use_python)
importFrom(stats,na.omit)
importFrom(stats,predict)
importFrom(terra,`nlyr<-`)
importFrom(terra,rast)
importFrom(terra,readStart)
Expand Down
11 changes: 11 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# rosettaPTF 0.2.0

* Compatible with `rosetta-soil` Python package v0.3
* Updated `run_rosetta()` and `predict.Rosetta()` to handle the 7-parameter output (adding `K0` and `L`) introduced in `rosetta-soil` v0.3.
* Added `estimate_type` argument to `run_rosetta()` to support linear, logarithmic (default), and geometric parameter estimations. Improved documentation and added examples for the geometric scale.
* Added `UnsaturatedK()` R constructor and `predict.UnsaturatedK()` method for predicting `K0` and `L` from retention parameters (requires `rosetta-soil` >= 0.3).
* Added `rosesoil()` R wrapper for the new upstream `rosesoil()` function (requires `rosetta-soil` >= 0.3).
* Deprecated `SoilDataFromArray()` in favor of direct list input (supported in `rosetta-soil` >= 0.3.
* Deprecated `ann_predict()` as the underlying Python method has been removed in v0.3. It now redirects to `predict()`.
* Added a new vignette: **"Performance Optimization and Raster Processing"** covering best practices for high-throughput workflows.

# rosettaPTF 0.1.5

* Fix check logic for whether input SpatRaster is in memory
Expand Down
1 change: 0 additions & 1 deletion R/AAAA.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ numpy_module <- NULL
}

!is.null(rosetta_module) && !is.null(numpy_module)

}

#' @importFrom reticulate configure_environment
Expand Down
81 changes: 77 additions & 4 deletions R/Class-Rosetta.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,46 @@
#' @rdname Rosetta-class
#' @export
Rosetta <- function(rosetta_version = 3, model_code = 3) {
object <- rosetta_module$Rosetta(rosetta_version, model_code)
object <- rosetta_module$Rosetta(as.integer(rosetta_version), as.integer(model_code))
structure(list(object = object), class = "Rosetta")
}

#' Predict Rosetta Parameter Values and Standard Deviations from a _Rosetta_ instance
#' @param object _Rosetta_ object containing class instance (e.g. from `Rosetta()`)
#' @param soildata A list containing vectors; with number of parameters matching the model type of `object`
#' @param ... not used
#' @return A list containing `mean` and `stdev` matrices (one row per sample).
#'
#' For `rosetta-soil` >= 0.3, the columns are: `theta_r`, `theta_s`, `alpha`, `npar`, `ksat`.
#' Note that these parameters are in the scale produced by the underlying model (often log10 for alpha, npar, and ksat).
#' @importFrom reticulate r_to_py import
#' @method predict Rosetta
#' @export
#' @examples
#' # predict(Rosetta(), list(c(30, 30, 40, 1.5), c(55, 25, 20, 1.1)))
predict.Rosetta <- function(object, soildata, ...) {
object$object$predict(numpy_module$array(reticulate::r_to_py(soildata),
dtype = "float"))
if (rosetta_pkg_version() >= package_version("0.3.0")) {
res <- object$object$predict(numpy_module$array(reticulate::r_to_py(soildata),
dtype = "float"))
retc_boot <- res[[1]]
ksat_boot <- res[[2]]

retc_mean <- numpy_module$mean(retc_boot, axis = 0L)
retc_std <- numpy_module$std(retc_boot, axis = 0L)
ksat_mean <- numpy_module$mean(ksat_boot, axis = 0L)
ksat_std <- numpy_module$std(ksat_boot, axis = 0L)

mean_val <- numpy_module$concatenate(list(retc_mean, ksat_mean), axis = 1L)
std_val <- numpy_module$concatenate(list(retc_std, ksat_std), axis = 1L)

return(list(mean = mean_val, stdev = std_val))

} else {
res <- object$object$predict(numpy_module$array(reticulate::r_to_py(soildata),
dtype = "float"))
names(res) <- c("mean", "stdev")
return(res)
}
}

#' Extended _Rosetta_ Predictions, Parameter Distributions and Summary Statistics after Zhang & Schaap (2017)
Expand All @@ -47,16 +71,65 @@ ann_predict <- function(object, soildata, sum_data = TRUE)
#' @rdname ann_predict
#' @export
ann_predict.default <- function(object, soildata, sum_data = TRUE) {
message("ann_predict() is defined for objects with class Rosetta; see `Rosetta()` to create a new instance")
if (rosetta_pkg_version() >= package_version("0.3.0")) {
.Deprecated("predict", msg = "ann_predict() is deprecated in rosetta-soil >= 0.3.0. Use predict() instead.")
} else {
message("ann_predict() is defined for objects with class Rosetta; see `Rosetta()` to create a new instance")
}
ann_predict.Rosetta(object = object, soildata = soildata, sum_data = sum_data)
}

#' @rdname ann_predict
#' @method ann_predict Rosetta
#' @export
#' @importFrom stats predict
#' @examples
#' # ann_predict(Rosetta(), list(c(30, 30, 40, 1.5), c(55, 25, 20, 1.1)))
ann_predict.Rosetta <- function(object, soildata, sum_data = TRUE) {
if (rosetta_pkg_version() >= package_version("0.3.0")) {
.Deprecated("predict", msg = "ann_predict() is deprecated in rosetta-soil >= 0.3.0. Use predict() instead.")
return(predict(object, soildata))
}
object$object$ann_predict(numpy_module$array(reticulate::r_to_py(soildata),
dtype = "float"),
sum_data = sum_data)
}

#' Make an UnsaturatedK object instance
#'
#' @description `UnsaturatedK`: Create an instance of the `UnsaturatedK` class from `rosetta-soil` >= 0.3. This class is used to predict `K0` and `L` from retention parameters.
#'
#' @return an instance of the `UnsaturatedK` class.
#' @export
UnsaturatedK <- function() {
if (rosetta_pkg_version() < package_version("0.3.0")) {
stop("UnsaturatedK requires rosetta-soil >= 0.3.0", call. = FALSE)
}

object <- rosetta_module$UnsaturatedK()
structure(list(object = object), class = "UnsaturatedK")
}

#' Predict K0 and L from retention parameters
#'
#' @param object _UnsaturatedK_ object
#' @param retc_params A list or matrix of retention parameters (theta_r, theta_s, alpha, npar)
#' @param ... not used
#' @return a `data.frame` with `log10_K0_mean`, `lpar_mean`, `log10_K0_sd`, `lpar_sd`
#' @method predict UnsaturatedK
#' @export
predict.UnsaturatedK <- function(object, retc_params, ...) {
res <- object$object$predict(numpy_module$array(reticulate::r_to_py(retc_params),
dtype = "float"))

k0l_mean <- numpy_module$mean(res, axis = 0L)
k0l_std <- numpy_module$std(res, axis = 0L)

df <- data.frame(
log10_K0_mean = k0l_mean[, 1],
lpar_mean = k0l_mean[, 2],
log10_K0_sd = k0l_std[, 1],
lpar_sd = k0l_std[, 2]
)
return(df)
}
58 changes: 58 additions & 0 deletions R/rosesoil.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#' Run rosesoil() from rosetta-soil >= 0.3.0
#'
#' @param soildata A list of numeric vectors or a data.frame (3-6 columns: sand, silt, clay, optionally bulk density, th33, and th1500)
#' @param rosetta_version integer, 1-3. Default: 3
#' @param estimate_type _character_. One of `"arith"` (default), `"log"`, or `"geo"`. Only used if `rosetta-soil` >= 0.3.1. `"log"` returns parameters on a logarithmic (log10) scale for `alpha`, `npar`, `ksat`, and `k0`. `"geo"` returns the geometric mean of bootstrap estimates (exponent of the mean of log-transformed values). This is often preferred for parameters that vary by orders of magnitude, such as `alpha` and `ksat`.
#' @param vars optional column name mapping (same as run_rosetta)
#' @return a data.frame with all RosettaResult fields
#' @export
rosesoil <- function(soildata, rosetta_version = 3, estimate_type = "arith", vars = NULL) {
if (rosetta_pkg_version() < package_version("0.3.0")) {
stop("rosesoil() requires rosetta-soil >= 0.3.0. Please run install_rosetta(upgrade = TRUE).")
}

if (inherits(soildata, "data.frame")) {
if (!is.null(vars)) {
if (!all(vars %in% colnames(soildata))) {
stop("all custom parameter names in `vars` must be present in `soildata`",
call. = FALSE)
} else {
soildata <- soildata[, vars[seq_along(colnames(soildata))]]
}
}

nid <- nrow(soildata)
soildatatemplate <- data.frame(
sand = numeric(nid),
silt = numeric(nid),
clay = numeric(nid),
bulkdensity = numeric(nid),
th33 = numeric(nid),
th1500 = numeric(nid)
)
soildatatemplate[] <- NA_real_
soildatatemplate[, 1:ncol(soildata)] <- soildata
soildata_list <- unlist(apply(soildatatemplate, 1,
function(x)
list(as.numeric(
stats::na.omit(as.numeric(x))
))),
recursive = FALSE)
} else {
soildata_list <- soildata
}

res_obj <- rosetta_module$rosesoil(as.integer(rosetta_version),
soildata_list,
estimate_type = estimate_type)

res_dicts <- res_obj$asdicts()

# handle NULL values in dicts (convert to NA)
res_df <- do.call(rbind, lapply(res_dicts, function(d) {
d[sapply(d, is.null)] <- NA_real_
as.data.frame(d)
}))

return(res_df)
}
20 changes: 19 additions & 1 deletion R/rosetta_utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

#' Convert list of numeric vectors to _SoilData_ Python object
#'
#' @description `SoilDataFromArray`: convert a list of numeric vectors containing soil properties to a `rosetta.rosetta.SoilData` class
#' @description `SoilDataFromArray`: convert a list of numeric vectors containing soil properties to a `rosetta.rosetta.SoilData` class. In `rosetta-soil` >= 0.3, direct list input is preferred.
#'
#' @param x a list of numeric vectors
#' @return an object reference to a Rosetta _SoilData_ Python object constructed from `x`
#' @export
SoilDataFromArray <- function(x) {
if (rosetta_pkg_version() >= package_version("0.3.0")) {
.Deprecated(msg = "Direct list input is now supported by rosetta-soil >= 0.3.0. SoilDataFromArray is deprecated.")
return(x)
}
rosetta_module$SoilData$from_array(x)
}

Expand All @@ -20,6 +24,20 @@ py_to_r.rosetta.rosetta.SoilData <- function(x) {
x
}

#' Get rosetta-soil Python package version
#' @return `package_version` object
#' @keywords internal
rosetta_pkg_version <- function() {
if (rosetta_module_available()) {
v <- try(rosetta_module$`__version__`, silent = TRUE)
if (inherits(v, "try-error") || is.null(v)) {
return(package_version("0.1.0"))
}
return(package_version(v))
}
package_version("0.0.0")
}

#' Check if Rosetta module is available for import from local Python environment
#' @return _logical_
#' @export
Expand Down
Loading
Loading