diff --git a/.drone.yml b/.drone.yml index a5ee6e8e..c2453b6e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,54 +1,156 @@ +--- kind: pipeline type: docker -name: mrgsolve +name: cran-latest + +platform: + os: linux + arch: amd64 steps: -- name: Pull mpn container from ECR +- name: pull image: omerxx/drone-ecr-auth + commands: + - $(aws ecr get-login --no-include-email --region us-east-1) + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:cran-latest + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:cran-latest + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:cran-latest volumes: - name: docker.sock path: /var/run/docker.sock + +- name: "Check package: R 4.1" + pull: never + image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:cran-latest commands: - - $(aws ecr get-login --no-include-email --region us-east-1) - - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/mpn:2020-03-24 + - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"', dependencies = TRUE)' + - make drone -- name: R40-new - image: "906087756158.dkr.ecr.us-east-1.amazonaws.com/mpn-complete:2020-06-08" +- name: "Check package: R 4.0" pull: never - environment: - R_LIBS_USER: "/opt/rpkgs/4.0" - _MRGSOLVE_SKIP_MODLIB_BUILD_: false + image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.0:cran-latest commands: + - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"', dependencies = TRUE)' - make drone -- name: R36 - image: "906087756158.dkr.ecr.us-east-1.amazonaws.com/mpn:2020-03-24" +- name: "Check package: R 3.6" pull: never - environment: - R_LIBS_USER: "/opt/rpkgs/3.6/2020-03-24" - _MRGSOLVE_SKIP_MODLIB_BUILD_: false + image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-3.6:cran-latest commands: + - R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"', dependencies = TRUE)' - make drone - -- name: release - when: - event: - - tag - status: - - success - image: "906087756158.dkr.ecr.us-east-1.amazonaws.com/mpn:2020-03-24" + +volumes: +- name: docker.sock + host: + path: /var/run/docker.sock + +trigger: + event: + exclude: + - promote + +--- +kind: pipeline +type: docker +name: coverage + +platform: + os: linux + arch: amd64 + +steps: +- name: pull + image: omerxx/drone-ecr-auth + commands: + - $(aws ecr get-login --no-include-email --region us-east-1) + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:latest + volumes: + - name: docker.sock + path: /var/run/docker.sock + +volumes: +- name: docker.sock + host: + path: /var/run/docker.sock +- name: cache + temp: {} + +trigger: + event: + exclude: + - promote + +depends_on: +- cran-latest + +--- +kind: pipeline +type: docker +name: release + +platform: + os: linux + arch: amd64 + +steps: +- name: pull + image: omerxx/drone-ecr-auth + commands: + - $(aws ecr get-login --no-include-email --region us-east-1) + - docker pull 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:latest + volumes: + - name: docker.sock + path: /var/run/docker.sock + +- name: Build package pull: never - environment: - R_LIBS_USER: "/opt/rpkgs/3.6/2020-03-24" + image: 906087756158.dkr.ecr.us-east-1.amazonaws.com/r-dev-ci-mpn-4.1:latest commands: - - git config --global user.email "drone@metrumrg.com" - - git config --global user.name "Drony" + - git config --global user.email drone@metrumrg.com + - git config --global user.name Drony - git fetch --tags - - R -e "pkgpub::create_tagged_repo()" - - aws s3 sync /tmp/${DRONE_TAG} s3://mpn.metworx.dev/releases/${DRONE_REPO_NAME}/${DRONE_TAG} - - aws s3 sync /tmp/${DRONE_TAG} s3://mpn.metworx.dev/releases/${DRONE_REPO_NAME}/latest_tag + - R -s -e 'pkgpub::create_tagged_repo(.dir = '"'"'/ephemeral'"'"')' + environment: + NOT_CRAN: true + volumes: + - name: cache + path: /ephemeral + +- name: "Publish package: ${DRONE_TAG}" + pull: if-not-exists + image: plugins/s3 + settings: + bucket: mpn.metworx.dev + source: /ephemeral/${DRONE_TAG}/**/* + strip_prefix: /ephemeral/${DRONE_TAG}/ + target: /releases/${DRONE_REPO_NAME}/${DRONE_TAG} + volumes: + - name: cache + path: /ephemeral + +- name: "Publish package: latest_tag" + pull: if-not-exists + image: plugins/s3 + settings: + bucket: mpn.metworx.dev + source: /ephemeral/${DRONE_TAG}/**/* + strip_prefix: /ephemeral/${DRONE_TAG}/ + target: /releases/${DRONE_REPO_NAME}/latest_tag + volumes: + - name: cache + path: /ephemeral volumes: - name: docker.sock host: path: /var/run/docker.sock +- name: cache + temp: {} + +trigger: + event: + - tag + +depends_on: +- cran-latest diff --git a/DESCRIPTION b/DESCRIPTION index 88e532d0..9c58ace5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: mrgsolve Title: Simulate from ODE-Based Models -Version: 1.0.0 +Version: 1.0.1 Authors@R: c(person(given = "Kyle T", family = "Baron", role = c("aut", "cre"), @@ -38,12 +38,12 @@ Depends: R (>= 3.1.2), methods Imports: - Rcpp (>= 0.12.12), - dplyr (>= 0.8.1), - magrittr (>= 1.5), - tibble (>= 2.1.1), - rlang (>= 0.3.4), - tidyselect (>= 0.2.5), + Rcpp (>= 1.0.7), + dplyr (>= 1.0.8), + magrittr (>= 2.0.1), + tibble (>= 3.1.6), + rlang (>= 1.0.1), + tidyselect (>= 1.1.1), lifecycle, glue Suggests: @@ -56,9 +56,9 @@ Suggests: data.table, pmxTools LinkingTo: - Rcpp (>= 0.12.12), - RcppArmadillo (>= 0.7.900.2.0), - BH (>= 1.62.0-1) + Rcpp (>= 1.0.7), + RcppArmadillo (>= 0.10.7.3.0), + BH (>= 1.75.0-0) RdMacros: lifecycle Encoding: UTF-8 @@ -83,6 +83,7 @@ Collate: 'annot.R' 'chain.R' 'class_build.R' + 'class_evd.R' 'events.R' 'class_rx.R' 'compile.R' diff --git a/Makefile b/Makefile index 66aaac64..584e5d28 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ check: check-only: make doc - R CMD check ${TARBALL} --no-manual --no-test + R CMD check ${TARBALL} --no-manual --no-tests --no-install cran: export _MRGSOLVE_SKIP_MODLIB_BUILD_=no cran: @@ -128,9 +128,10 @@ modlib: export _MRGSOLVE_SKIP_MODLIB_BUILD_=no modlib: Rscript -e 'testthat::test_file("inst/maintenance/unit/test-modlib.R")' -# possibly no longer in use +# this is in use drone: make house + #R -s -e 'devtools::install_deps(upgrade = '"'"'always'"'"', dependencies=TRUE)' R CMD build --md5 $(PKGDIR) R CMD check --as-cran ${TARBALL} export _MRGSOLVE_SKIP_MODLIB_BUILD_=false diff --git a/NAMESPACE b/NAMESPACE index 6c3883eb..dcc39a29 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -67,6 +67,7 @@ export("%then%") export(Req) export(allparam) export(as.ev) +export(as.evd) export(as.tbl.mrgsims) export(as_bmat) export(as_cmat) @@ -103,6 +104,7 @@ export(ev_rep) export(ev_repeat) export(ev_rx) export(ev_seq) +export(evd) export(expand.ev) export(expand.idata) export(expand_observations) @@ -161,6 +163,7 @@ export(stime) export(tgrid) export(touch_funs) export(tscale) +export(uctran) export(valid_data_set) export(valid_data_set.matrix) export(valid_idata_set) @@ -192,6 +195,7 @@ exportMethods(data_set) exportMethods(dim) exportMethods(ev) exportMethods(ev_rx) +exportMethods(evd) exportMethods(head) exportMethods(idata_set) exportMethods(init) diff --git a/NEWS.md b/NEWS.md index eb42a3f1..a59ae716 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,19 @@ +# mrgsolve 1.0.1 + +- Add `LOG()`, `EXP()`, `SQRT()` macros when `nm-vars` plugin is invoked + #931, #936. + +- Use `evd()` to create an event object which renders nmtran names + in upper case (e.g. `TIME` rather than `time`) #935, #919. + +- Fixed bug where `rate` was not getting set for modeled events #934. + +- Fixed bug where `self.stop_id()` and `self.stop_id_cf()` had reversed + behavior as documented #927, #928. + +- Refactored EVID=3 behavior to leave `NEWIND` as-is #934. + + # mrgsolve 1.0.0 - New model syntax: `THETA(n)` is interpreted as `THETAn` in the model code; diff --git a/R/chain.R b/R/chain.R index b60a3f36..feb9d029 100644 --- a/R/chain.R +++ b/R/chain.R @@ -195,7 +195,7 @@ obsaug <- function(x,value=TRUE,...) { ##' idata_set(idata) %>% ##' design(list(des1, des2),"amt") %>% ##' data_set(data) %>% -##' mrgsim %>% +##' mrgsim() %>% ##' plot(RESP~time|GRP) ##' ##' @export diff --git a/R/class_ev.R b/R/class_ev.R index c36c0a25..00e3a0a5 100644 --- a/R/class_ev.R +++ b/R/class_ev.R @@ -1,4 +1,4 @@ -# Copyright (C) 2013 - 2019 Metrum Research Group +# Copyright (C) 2013 - 2022 Metrum Research Group # # This file is part of mrgsolve. # @@ -15,14 +15,79 @@ # You should have received a copy of the GNU General Public License # along with mrgsolve. If not, see . + +# A series of functions for dealing with ev/data.frame ------- + +#' Just get the data.frame part +#' +#' This is an internal function. +#' +#' If an event object, return `data` slot; otherwise, call as.data.frame. This +#' is supposed to be optimized for handling event objects. +#' +#' @param x An R object. +#' +#' @noRd +to_data_frame <- function(x) { + # Just return the data frame + if(is.ev(x)) { + x@data + } else { + as.data.frame(x) + } +} + +#' Convert an event object to data set +#' +#' Call this function when `x` is already known to be an event object. +#' +#' @param x An event object. +#' @param id The subject ID. +#' +#' @noRd +ev_to_ds <- function(x, id = 1) { + # Specifically for simulating a (known) ev object + ans <- x@data + if(nrow(ans)==0) return(ans) + if(match("ID", names(ans), 0)==0) ans$ID <- id + recase_ev(ans, x@case) +} + +#' Create a data set from and event object or data frame +#' +#' This is more general applicability. +#' +#' @noRd +As_data_set <- function(x, id = 1) { + # Possibly handle data.frame or + if(is.ev(x)) return(ev_to_ds(x, id = id)) + ans <- as.data.frame(x) + if(nrow(ans)==0) return(ans) + if(match("ID", names(ans), 0) ==0) ans$ID <- id + ans +} + +ev_proto <- list(data = data.frame(), case = 0L) +ev_slots <- c(data = "data.frame", case = "integer") +ev_initialize <- function(.Object, case = 0L, ...) { + .Object <- callNextMethod() + if(!case %in% c(0, 1)) { + stop("Event object case must be either 0 or 1.") + } + .Object@case <- case + .Object +} + ##' S4 events class ##' @slot data a data frame of events +##' @slot case indicates how to handle column naming upon coerce to data.frame ##' @export ##' @keywords internal -setClass("ev", slots=c(data="data.frame")) +setClass("ev", prototype = ev_proto, slots = ev_slots) +setMethod("initialize", "ev", ev_initialize) is.ev <- function(x) { - inherits(x,"ev") + inherits(x, "ev") } ##' dplyr verbs for event objects @@ -62,14 +127,14 @@ mutate.ev <- function(.data, ...) { ##' @rdname ev_dplyr ##' @export select.ev <- function(.data, ...) { - .data@data <- as.data.frame(dplyr::select(.data@data,...)) + .data@data <- as.data.frame(dplyr::select(.data@data, ...)) .data } ##' @rdname ev_dplyr ##' @export filter.ev <- function(.data, ...) { - .data@data <- as.data.frame(dplyr::filter(.data@data,...)) + .data@data <- as.data.frame(dplyr::filter(.data@data, ...)) .data } @@ -125,19 +190,19 @@ as.matrix.ev <- function(x,...) { ##' @export as.data.frame.ev <- function(x, row.names = NULL, optional = FALSE, add_ID = NULL, ...) { - ans <- x@data - if(is.numeric(add_ID) & !has_ID(ans) & nrow(ans) > 0) { - ans[["ID"]] <- add_ID[1] - } - return(ans) + ev_to_ds(x, id = add_ID) } -##' @rdname ev_methods -##' @export -##' @keywords internal +#' @rdname ev_methods +#' @export +#' @keywords internal setMethod("show", "ev", function(object) { - cat("Events:\n") - print(as.data.frame(object)) + header <- "Events:\n" + if(object@case==1) { + header <- "Events Data:\n" + } + cat(header) + print(object@data) return(invisible(NULL)) }) @@ -160,19 +225,6 @@ setMethod("[[", "ev", function(x, i, exact=TRUE) { x@data[[i]] }) -As_data_set <- function(x) { - if(!is.data.frame(x)) { - if(is.ev(x)) { - x <- x@data - } else { - x <- as.data.frame(x) - } - } - if(nrow(x)==0) return(x) - if(!has_ID(x)) x[["ID"]] <- 1 - return(x) -} - finalize_ev_data <- function(data) { if("tinf" %in% names(data)) { tinf <- data[["tinf"]] diff --git a/R/class_evd.R b/R/class_evd.R new file mode 100644 index 00000000..f9c0165a --- /dev/null +++ b/R/class_evd.R @@ -0,0 +1,77 @@ +# Note: `evd` isn't really a separate class; it could be implemented that +# way, but for not it's just an `ev()` object with a specific `case` +# attribute. + + +#' Create an event object with data-like names +#' +#' This function calls [ev()] to create an event object and then sets the +#' case attribute so that it renders nmtran data names in upper case. An +#' object created with [evd()] can be used in the same way as an object +#' created with [ev()]. +#' +#' Note that `evd` isn't a separate class; it is just an `ev` object with +#' a specific `case` attribute. See examples which illustrate the difference. +#' +#' @param x An mrgmod object. +#' @param ... Arguments passed to [ev()]. +#' +#' @examples +#' a <- evd(amt = 100) +#' b <- ev(amt = 300) +#' a +#' as.data.frame(a) +#' as_data_set(a, b) +#' as_data_set(b, a) +#' as.data.frame(seq(a, b)) +#' +#' @seealso [ev()], [lctran()], [uctran()] +#' +#' @md +#' @export +setGeneric("evd", function(x, ...) standardGeneric("evd")) + +#' @rdname evd +#' @export +setMethod("evd", "mrgmod", function(x, ...) { + x <- ev(x, ...) + x@args[["events"]] <- set_ev_case(x@args[["events"]], 1L) + x +}) + +#' @rdname evd +#' @export +setMethod("evd", "missing", function(...) { + set_ev_case(ev(...), 1L) +}) + +#' @rdname evd +#' @export +setMethod("evd", "ev", function(x, ...) { + set_ev_case(x, 1L) +}) + + +#' @param x An event object. +#' @rdname evd +#' @export +as.evd <- function(x) { + if(!is.ev(x)) stop("evnt must be an ev object.") + x@case <- 1L + x +} + +set_ev_case <- function(x, case) { + if(!is.ev(x)) return(x) + x@case <- case + x +} + +# This actually changes names of the result +# For now, 0 = as-is; 1 = all uppercase +recase_ev <- function(data, case = 0) { + if(case==0) return(data) + convert <- names(data) %in% GLOBALS$TRAN_LOWER + names(data)[convert] <- toupper(names(data)[convert]) + data +} diff --git a/R/data_set.R b/R/data_set.R index 202cad8c..46e7dc3c 100644 --- a/R/data_set.R +++ b/R/data_set.R @@ -103,13 +103,12 @@ setGeneric("data_set", function(x,data,...) { standardGeneric("data_set") }) - ##' @rdname data_set ##' @export setMethod("data_set",c("mrgmod", "data.frame"), function(x,data,.subset=TRUE,.select=TRUE,object=NULL,need=NULL,...) { if(is.character(need)) { - suppressMessages(inventory(x,data,need)) + suppressMessages(inventory(x, data, need)) } if(!missing(.subset)) { data <- dplyr::filter(data,`!!`(enquo(.subset))) @@ -118,10 +117,10 @@ setMethod("data_set",c("mrgmod", "data.frame"), function(x,data,.subset=TRUE,.se data <- dplyr::select(data,`!!!`(.select)) } if(nrow(data) ==0) { - stop("Zero rows in data after filtering.", call.=FALSE) + stop("Zero rows in data after filtering.", call. = FALSE) } if(is.character(object)) { - data <- data_hooks(data,object,x@envir,param(x),...) + data <- data_hooks(data, object, x@envir, param(x), ...) } x@args[["data"]] <- data return(x) @@ -129,125 +128,181 @@ setMethod("data_set",c("mrgmod", "data.frame"), function(x,data,.subset=TRUE,.se ##' @rdname data_set ##' @export -setMethod("data_set",c("mrgmod", "ANY"), function(x,data,...) { - return(data_set(x,as.data.frame(data),...)) +setMethod("data_set",c("mrgmod", "ANY"), function(x, data, ...) { + return(data_set(x, as.data.frame(data), ...)) }) ##' @rdname data_set ##' @export -setMethod("data_set", c("mrgmod", "ev"), function(x,data,...) { - return(data_set(x,As_data_set(data),...)) +setMethod("data_set", c("mrgmod", "ev"), function(x, data, ...) { + return(data_set(x, As_data_set(data), ...)) }) ##' @rdname data_set ##' @export -setMethod("data_set", c("mrgmod", "missing"), function(x, object,...) { +setMethod("data_set", c("mrgmod", "missing"), function(x, object, ...) { object <- data_hooks(object=object,envir=x@envir,param=param(x),...) - return(data_set(x,as.data.frame(object),...)) + return(data_set(x, as.data.frame(object) ,...)) }) -##' Convert select upper case column names to lower case -##' -##' Previous data set requirements included lower case names for data items -##' like `AMT` and `EVID`. Lower case is no longer required. -##' -##' @param data an nmtran-like data frame -##' -##' @return A data.frame with renamed columns -##' -##' @details -##' Columns that will be renamed with lower case versions: `AMT`, -##' `II`, `SS`, `CMT`, `ADDL`, `RATE`, `EVID`, -##' `TIME`. If a lower case version of these names exist in the data -##' set, the column will not be renamed. -##' -##' @examples -##' data <- data.frame(TIME = 0, AMT = 5, II = 24, addl = 2) -##' lctran(data) -##' -##' @return -##' The input data set, with select columns made lower case. -##' -##' @md -##' @export -lctran <- function(data) { +#' Change the case of nmtran-like data items +#' +#' Previous data set requirements included lower case names for data items +#' like `AMT` and `EVID`. Lower case is no longer required. However, it is still +#' a requirement that nmtran like data column names are either all lower case +#' or all upper case. +#' +#' Columns that will be renamed with lower or upper case versions: +#' +#' - `AMT / amt` +#' - `II / ii` +#' - `SS / ss` +#' - `CMT / cmt` +#' - `ADDL / addl` +#' - `RATE / rate` +#' - `EVID / evid` +#' - `TIME / time` +#' +#' If both lower and upper case versions of the name are present in the data +#' frame, no changes will be made. +#' +#' @param data A data set with nmtran-like format. +#' @param warn If `TRUE`, a warning will be issued when there are both upper +#' and lower case versions of any nmtran-like column in the data frame. +#' +#' @return +#' A data frame with possibly renamed columns. +#' +#' @examples +#' data <- data.frame(TIME = 0, AMT = 5, II = 24, addl = 2, WT = 80) +#' lctran(data) +#' +#' data <- data.frame(TIME = 0, AMT = 5, II = 24, addl = 2, wt = 80) +#' uctran(data) +#' +#' # warning +#' data <- data.frame(TIME = 1, time = 2, CMT = 5) +#' lctran(data) +#' +#' @return +#' The input data set, with select columns made lower case. +#' +#' @md +#' @export +lctran <- function(data, warn = TRUE) { + if(!is.data.frame(data)) { + stop("`data` must be a data.frame.") + } n <- names(data) - infrom <- is.element(n,tran_upper) - haslower <- is.element(tolower(n),n) + infrom <- n %in% GLOBALS$TRAN_UPPER + haslower <- tolower(n) %in% n change <- infrom & !haslower - if(sum(change) > 0) names(data)[change] <- tolower(n[change]) + if(any(change)) names(data)[change] <- tolower(n[change]) + if(isTRUE(warn) && any(dup <- infrom & haslower)) { + warning( + "There are both upper and lower case versions ", + "of some nmtran names in the data set" + ) + } data } +#' @rdname lctran +#' @export +uctran <- function(data, warn = TRUE) { + if(!is.data.frame(data)) { + stop("`data` must be a data.frame.") + } + n <- names(data) + infrom <- n %in% GLOBALS$TRAN_LOWER + hasupper <- toupper(n) %in% n + change <- infrom & !hasupper + if(any(change)) names(data)[change] <- toupper(n[change]) + if(isTRUE(warn) && any(dup <- infrom & hasupper)) { + warning( + "There are both upper and lower case versions ", + "of some nmtran names in the data set." + ) + } + data +} -data_hooks <- function(data,object,envir,param=list(),...) { +data_hooks <- function(data, object, envir, param = list(), ...) { param <- as.list(param) - envir <- combine_list(as.list(param),as.list(envir)) + envir <- combine_list(as.list(param), as.list(envir)) objects <- cvec_cs(object) args <- list(...) if(missing(data)) { - data <- eval(tparse(objects[1]),envir=envir) + data <- eval(tparse(objects[1]), envir = envir) if(is.function(data)) { - data <- do.call(data,args,envir=as.environment(envir)) + data <- do.call(data, args, envir = as.environment(envir)) } objects <- objects[-1] } for(f in objects) { args$data <- data - data <- do.call(f,args,envir=as.environment(envir)) + data <- do.call(f, args, envir = as.environment(envir)) } return(data) } - -##' Create a simulation data set from ev objects -##' -##' The goal is to take a series of event objects and combine them -##' into a single data set that can be passed to [data_set()]. -##' -##' @param x ev objects -##' @param ... more ev objects -##' -##' @details -##' Each event object is added to the data frame as an `ID` -##' or set of `ID`s that are distinct from the `ID`s -##' in the other event objects. Note that including `ID` -##' argument to the [ev()] call where `length(ID)` -##' is greater than one will render that set of -##' events for all of `ID`s that are requested. -##' -##' To get a data frame with one row (event) per `ID`, look at [expand.ev()]. -##' -##' @return -##' A data frame suitable for passing into [data_set()]. -##' -##' @examples -##' -##' as_data_set(ev(amt = c(100,200), cmt=1, ID = seq(3)), -##' ev(amt = 300, time = 24, ID = seq(2)), -##' ev(amt = 1000, ii = 8, addl = 10, ID = seq(3))) -##' -##' # Instead of this, use expand.ev -##' as_data_set(ev(amt = 100), ev(amt = 200), ev(amt = 300)) -##' -##' @md -##' @rdname as_data_set -##' @export +#' Create a simulation data set from ev objects +#' +#' The goal is to take a series of event objects and combine them +#' into a single data set that can be passed to [data_set()]. +#' +#' @param x ev objects +#' @param ... more ev objects +#' +#' @details +#' Each event object is added to the data frame as an `ID` or set of `ID`s +#' that are distinct from the `ID`s in the other event objects. Note that +#' including `ID` argument to the [ev()] call where `length(ID)` is greater +#' than one will render that set of events for all of `ID`s that are requested. +#' +#' When determining the case for output names, the `case` attribute for +#' the first `ev` object passed will be used to set the case for the output +#' data.frame. +#' +#' To get a data frame with one row (event) per `ID`, look at [expand.ev()]. +#' +#' @return +#' A data frame suitable for passing into [data_set()]. +#' +#' @examples +#' a <- ev(amt = c(100,200), cmt=1, ID = seq(3)) +#' b <- ev(amt = 300, time = 24, ID = seq(2)) +#' c <- ev(amt = 1000, ii = 8, addl = 10, ID = seq(3)) +#' +#' as_data_set(a, b, c) +#' +#' d <- evd(amt = 500) +#' +#' as_data_set(d, a) +#' +#' # Instead of this, use expand.ev +#' as_data_set(ev(amt = 100), ev(amt = 200), ev(amt = 300)) +#' +#' @seealso [expand.ev()], [ev()] +#' +#' @md +#' @rdname as_data_set +#' @export setGeneric("as_data_set", function(x,...) standardGeneric("as_data_set")) -##' @rdname as_data_set -setMethod("as_data_set","ev", function(x,...) { +#' @rdname as_data_set +setMethod("as_data_set", "ev", function(x, ...) { other_ev <- list(...) if(length(other_ev)==0) { return(check_ev(x)) } - do.call(collect_ev,c(list(x),other_ev)) + do.call(collect_ev, c(list(x), other_ev)) }) -##' @rdname as_data_set -setMethod("as_data_set","data.frame", function(x,...) { - as_data_set(as.ev(x),...) +#' @rdname as_data_set +setMethod("as_data_set","data.frame", function(x, ...) { + as_data_set(as.ev(x) ,...) }) ##' Replicate a list of events into a data set @@ -283,7 +338,7 @@ setMethod("as_data_set","data.frame", function(x,...) { ##' ##' ##' @export -ev_assign <- function(l,idata,evgroup,join=FALSE) { +ev_assign <- function(l, idata, evgroup, join = FALSE) { idata <- as.data.frame(idata) @@ -314,12 +369,12 @@ ev_assign <- function(l,idata,evgroup,join=FALSE) { } l <- lapply(l,function(x) { - x[,colnames(l[[1]]),drop=FALSE] + x[,colnames(l[[1]]), drop=FALSE] }) evgroup <- idata[,evgroup] uevgroup <- sort(unique(evgroup)) - evgroup <- match(evgroup,uevgroup) + evgroup <- match(evgroup, uevgroup) if(length(l) != length(uevgroup)) { stop("For this idata set, please provide exactly ", @@ -328,20 +383,18 @@ ev_assign <- function(l,idata,evgroup,join=FALSE) { } x <- do.call(rbind,l[evgroup]) - dimnames(x) <- list(NULL,colnames(x)) + dimnames(x) <- list(NULL, colnames(x)) x <- as.data.frame(x) n <- (sapply(l,nrow))[evgroup] - ID <- rep(idata[["ID"]],times=n) + ID <- rep(idata[["ID"]], times = n) x[["ID"]] <- ID if(join) { - nu <- sapply(idata,is.numeric) - x <- dplyr::left_join(x,idata[,nu,drop=FALSE],by="ID") + nu <- sapply(idata, is.numeric) + x <- left_join(x,idata[,nu,drop=FALSE],by="ID") } - return(x) - } ##' @param ... used to pass arguments from \code{assign_ev} diff --git a/R/events.R b/R/events.R index 14f1996b..2387eefb 100644 --- a/R/events.R +++ b/R/events.R @@ -1,4 +1,4 @@ -# Copyright (C) 2013 - 2021 Metrum Research Group +# Copyright (C) 2013 - 2022 Metrum Research Group # # This file is part of mrgsolve. # @@ -16,94 +16,94 @@ # along with mrgsolve. If not, see . -##' Event objects for simulating PK and other interventions -##' -##' An event object specifies dosing or other interventions that get implemented -##' during simulation. Event objects do similar things as \code{\link{data_set}}, -##' but simpler and quicker. -##' -##' @param x a model object -##' @param time event time -##' @param amt dose amount -##' @param evid event ID -##' @param cmt compartment -##' @param ID subject ID -##' @param replicate logical; if \code{TRUE}, events will be replicated for -##' each individual in \code{ID} -##' @param until the expected maximum \bold{observation} time for this regimen -##' @param tinf infusion time; if greater than zero, then the \code{rate} item -##' will be derived as \code{amt/tinf} -##' @param realize_addl if \code{FALSE} (default), no change to \code{addl} -##' doses. If \code{TRUE}, \code{addl} doses are made explicit with -##' \code{\link{realize_addl}} -##' @param object passed to show -##' @param ... other items to be incorporated into the event object; see -##' details -##' -##' @details -##' \itemize{ -##' \item Required items in events objects include -##' \code{time}, \code{amt}, \code{evid} and \code{cmt}. -##' \item If not supplied, \code{evid} is assumed to be 1. -##' \item If not supplied, \code{cmt} is assumed to be 1. -##' \item If not supplied, \code{time} is assumed to be 0. -##' \item If \code{amt} is not supplied, an error will be generated. -##' \item If \code{total} is supplied, then \code{addl} will be set -##' to \code{total} - 1. -##' \item Other items can include \code{ii}, \code{ss}, and \code{addl} -##' (see \code{\link{data_set}} for details on all of these items). -##' \item \code{ID} may be specified as a vector. -##' \item If replicate is \code{TRUE} (default), then the events -##' regimen is replicated for each \code{ID}; otherwise, the number of -##' event rows must match the number of \code{ID}s entered -##' } -##' @return events object -##' -##' @seealso \code{\link{ev_rep}}, \code{\link{ev_days}}, -##' \code{\link{ev_repeat}}, \code{\link{ev_assign}}, -##' \code{\link{ev_seq}}, \code{\link{mutate.ev}}, -##' \code{\link{as.ev}}, \code{\link{ev_methods}} -##' -##' @examples -##' mod <- mrgsolve::house() -##' -##' mod <- mod %>% ev(amt = 1000, time = 0, cmt = 1) -##' -##' loading <- ev(time = 0, cmt = 1, amt = 1000) -##' -##' maint <- ev(time = 12, cmt = 1, amt = 500, ii = 12, addl = 10) -##' -##' c(loading, maint) -##' -##' reduced_load <- dplyr::mutate(loading, amt = 750) -##' -##' @export -setGeneric("ev", function(x,...) { +#' Event objects for simulating PK and other interventions +#' +#' An event object specifies dosing or other interventions that get implemented +#' during simulation. Event objects do similar things as \code{\link{data_set}}, +#' but simpler and quicker. +#' +#' @param x a model object +#' @param time event time +#' @param amt dose amount +#' @param evid event ID +#' @param cmt compartment +#' @param ID subject ID +#' @param replicate logical; if \code{TRUE}, events will be replicated for +#' each individual in \code{ID} +#' @param until the expected maximum \bold{observation} time for this regimen +#' @param tinf infusion time; if greater than zero, then the \code{rate} item +#' will be derived as \code{amt/tinf} +#' @param realize_addl if \code{FALSE} (default), no change to \code{addl} +#' doses. If \code{TRUE}, \code{addl} doses are made explicit with +#' \code{\link{realize_addl}} +#' @param object passed to show +#' @param ... other items to be incorporated into the event object; see +#' details +#' +#' @details +#' \itemize{ +#' \item Required items in events objects include +#' \code{time}, \code{amt}, \code{evid} and \code{cmt}. +#' \item If not supplied, \code{evid} is assumed to be 1. +#' \item If not supplied, \code{cmt} is assumed to be 1. +#' \item If not supplied, \code{time} is assumed to be 0. +#' \item If \code{amt} is not supplied, an error will be generated. +#' \item If \code{total} is supplied, then \code{addl} will be set +#' to \code{total} - 1. +#' \item Other items can include \code{ii}, \code{ss}, and \code{addl} +#' (see \code{\link{data_set}} for details on all of these items). +#' \item \code{ID} may be specified as a vector. +#' \item If replicate is \code{TRUE} (default), then the events +#' regimen is replicated for each \code{ID}; otherwise, the number of +#' event rows must match the number of \code{ID}s entered +#' } +#' @return events object +#' +#' @seealso \code{\link{evd}}, \code{\link{ev_rep}}, \code{\link{ev_days}}, +#' \code{\link{ev_repeat}}, \code{\link{ev_assign}}, +#' \code{\link{ev_seq}}, \code{\link{mutate.ev}}, +#' \code{\link{as.ev}}, \code{\link{ev_methods}} +#' +#' @examples +#' mod <- mrgsolve::house() +#' +#' mod <- mod %>% ev(amt = 1000, time = 0, cmt = 1) +#' +#' loading <- ev(time = 0, cmt = 1, amt = 1000) +#' +#' maint <- ev(time = 12, cmt = 1, amt = 500, ii = 12, addl = 10) +#' +#' c(loading, maint) +#' +#' reduced_load <- dplyr::mutate(loading, amt = 750) +#' +#' @export +setGeneric("ev", function(x, ...) { standardGeneric("ev") }) -##' @rdname ev -##' @export -setMethod("ev", "mrgmod", function(x,object=NULL,...) { +#' @rdname ev +#' @export +setMethod("ev", "mrgmod", function(x, object = NULL, ...) { if(is.null(object)) { x@args[["events"]] <- ev(...) return(x) } if(is.character(object)) { - object <- eval(parse(text = object),envir = x@envir) + object <- eval(parse(text = object), envir = x@envir) } x@args[["events"]] <- object x }) -##' @rdname ev -##' @export +#' @rdname ev +#' @export setMethod("ev", "missing", function(time=0, amt=0, evid=1, cmt=1, ID=numeric(0), replicate=TRUE, until=NULL, tinf=NULL, realize_addl=FALSE, ...) { if(length(match.call())==1) { - return(new("ev", data=data.frame()[0,])) + return(new("ev", data = data.frame()[0,])) } if(any(evid==0)) { @@ -114,7 +114,7 @@ setMethod("ev", "missing", function(time=0, amt=0, evid=1, cmt=1, ID=numeric(0), wstop("argument \"amt\" is missing") } - l <- list(time=time, cmt=cmt, amt=amt, evid=evid) + l <- list(time = time, cmt = cmt, amt = amt, evid = evid) if(is.numeric(tinf) && length(tinf) > 0) l[["tinf"]] <- tinf if(is.numeric(until) && length(until) > 0) l[["until"]] <- until @@ -129,7 +129,7 @@ setMethod("ev", "missing", function(time=0, amt=0, evid=1, cmt=1, ID=numeric(0), names(l) <- c(na1,na2) } - data <- as.data.frame(as_tibble(l)) + data <- as.data.frame(as_tibble(l), stringsAsFactors = FALSE) if(all(c("rate", "tinf") %in% names(data))) { wstop("input can include either rate or tinf, not both") @@ -160,10 +160,6 @@ setMethod("ev", "missing", function(time=0, amt=0, evid=1, cmt=1, ID=numeric(0), data <- arrange__(data,.dots=c("ID", "time")) rownames(data) <- NULL } else { - # data <- data.frame(.Call(`_mrgsolve_EXPAND_EVENTS`, - # match("ID", colnames(data),0), - # data.matrix(data), - # ID, PACKAGE="mrgsolve" )) data <- expand_event_object(data,ID) } @@ -177,50 +173,51 @@ setMethod("ev", "missing", function(time=0, amt=0, evid=1, cmt=1, ID=numeric(0), } if(realize_addl) data <- realize_addl(data) - return(new("ev", data=data)) + return(new("ev", data = data)) }) -##' @rdname ev -##' @export -setMethod("ev", "ev", function(x, realize_addl=FALSE,...) { +#' @rdname ev +#' @export +setMethod("ev", "ev", function(x, realize_addl = FALSE, ...) { + x <- set_ev_case(x, 0L) if(realize_addl) { return(realize_addl(x)) - } else { - return(x) - } + } + x }) - -##' Coerce an object to class ev -##' -##' @param x an object to coerce -##' @param keep_id if \code{TRUE}, \code{ID} column is retained if it exists -##' @param clean if \code{TRUE}, only dosing or ID information is retained in -##' the result -##' @param ... not used -##' -##' @examples -##' data <- data.frame(amt = 100) -##' -##' as.ev(data) -##' -##' @export -setGeneric("as.ev", function(x,...) { +#' Coerce an object to class ev +#' +#' @param x An object to coerce. +#' @param keep_id If `TRUE`, `ID` column is retained if it exists. +#' @param clean If `TRUE`, only dosing or ID information is retained in +#' the result. +#' @param ... Not used. +#' +#' @examples +#' data <- data.frame(amt = 100) +#' +#' as.ev(data) +#' +#' @return +#' An object with class ev. +#' +#' @md +#' @export +setGeneric("as.ev", function(x, ...) { standardGeneric("as.ev") }) -##' @rdname as.ev -##' @export -setMethod("as.ev", "data.frame", function(x,keep_id=TRUE,clean = FALSE,...) { +df_to_ev <- function(x, keep_id = TRUE, clean = FALSE, ...) { - if(nrow(x)==0) { - return(new("ev",data=data.frame())) + if(nrow(x) == 0) { + return(new("ev", data = data.frame())) } - x <- as.data.frame(x) + x <- as.data.frame(x, stringsAsFactors = FALSE) convert <- c("TIME", GLOBALS[["CARRY_TRAN_UC"]]) - upper <- intersect(convert,names(x)) + upper <- intersect(convert, names(x)) if(length(upper) > 0) { where <- match(upper, names(x)) @@ -241,7 +238,7 @@ setMethod("as.ev", "data.frame", function(x,keep_id=TRUE,clean = FALSE,...) { x[["evid"]] <- na2zero(x[["evid"]]) x <- x[x[["evid"]] != 0,] if(nrow(x)==0) { - wstop("no dosing events found; could not coerce to ev object") + wstop("no dosing events found; could not coerce to ev object.") } } @@ -250,137 +247,155 @@ setMethod("as.ev", "data.frame", function(x,keep_id=TRUE,clean = FALSE,...) { if(clean) { keep <- c("ID", GLOBALS[["CARRY_TRAN_LC"]]) keep <- intersect(keep, names(x)) - x <- x[,keep] + x <- x[,keep, drop = FALSE] } x <- finalize_ev(x) - new("ev", data=x) -}) + new("ev", data = x) +} -##' @rdname as.ev -##' @export -setMethod("as.ev", "ev", function(x,...) { +#' @rdname as.ev +#' @export +setMethod("as.ev", "data.frame", df_to_ev) + +#' @rdname as.ev +#' @export +setMethod("as.ev", "ev", function(x, ...) { x }) check_ev <- function(x) { - x <- as.data.frame(x) - if(!has_name("ID", x)) x[["ID"]] <- 1 + if(!inherits(x, c("ev", "data.frame"))) { + stop("All items must have class ev or data.frame.") + } + x <- to_data_frame(x) + if(!"ID" %in% names(x)) x[["ID"]] <- 1 return(x) } collect_ev <- function(...) { x <- list(...) - tran <- c("ID","time", "cmt", "evid", - "amt", "ii", "addl", "rate", "ss") - x <- lapply(x,check_ev) - y <- lapply(x, "[[","ID") - mx <- sapply(y,function(xx) length(unique(xx))) - mx <- cumsum(c(0,mx[-length(mx)])) - y <- mapply(y,mx, FUN=function(yi,mxi) return(yi+mxi), SIMPLIFY=FALSE) + tran <- c("ID","time", "cmt", "evid", "amt", "ii", "addl", "rate", "ss") + is_evnt <- vapply(x, is.ev, TRUE) + if(any(is_evnt)) { + w <- which(is_evnt)[1] + case <- x[[w]]@case + } else { + case <- 0 + } + x <- lapply(x, check_ev) + ids <- lapply(x, "[[", "ID") + nid <- sapply(ids, function(tid) length(unique(tid))) + idn <- cumsum(c(0, nid[-length(nid)])) + new_ids <- Map(f = `+`, ids, idn) x <- bind_rows(x) - x[["ID"]] <- unlist(y,use.names=FALSE) - tran <- intersect(tran,names(x)) + x[["ID"]] <- unlist(new_ids, use.names = FALSE) + tran <- intersect(tran, names(x)) what <- names(x) %in% tran - - x <- mutate_at(x,which(what),list(~na2zero(.))) - + for(col in which(what)) { + x[[col]] <- na2zero(x[[col]]) + } na.check <- which(!what) - if(length(na.check) > 0) { - if(any(is.na(unlist(x[,na.check])))) { + if(anyNA(x[, na.check])) { warning("missing values in some columns",call.=FALSE) } } - x <- dplyr::select(x,c(match(tran,names(x)),seq_along(names(x)))) - - if(!any(c("time", "TIME") %in% names(x))) { + take <- unique(c(match(tran,names(x)),seq_along(names(x)))) + x <- x[, take, drop = FALSE] + if(is.na(timename(x))) { wstop("no time or TIME column in the data set") } - - if(!any(c("cmt", "CMT") %in% names(x))) { + if(is.na(cmtname(x))) { wstop("no cmt or CMT column in the data set") } - - if(!has_ID(x)) { + if(!"ID" %in% names(x)) { wstop("no ID column in the data set") } + if(case > 0) { + x <- recase_ev(x, case) + } return(x) } -##' Operations for ev objects -##' -##' @param e1 object on left hand side of operator (lhs) -##' @param e2 object on right hand side of operator (rhs) -##' @name ev_ops -##' -##' @aliases +,ev,ev-method -##' @docType methods -##' -##' @details -##' All operations involving \code{\link[=mrgmod-class]{mrgmod}} -##' objects have been deprecated. -##' -##' @rdname ev_ops -##' @keywords internal +#' Operations for ev objects +#' +#' @param e1 object on left hand side of operator (lhs) +#' @param e2 object on right hand side of operator (rhs) +#' @name ev_ops +#' +#' @aliases +,ev,ev-method +#' @docType methods +#' +#' @details +#' All operations involving \code{\link[=mrgmod-class]{mrgmod}} +#' objects have been deprecated. +#' +#' @rdname ev_ops +#' @keywords internal setMethod("+", signature(e1="ev", e2="ev"), function(e1,e2) { + #stop("e1 + e2 operation is now deprecated") return(add.ev(e1,e2)) }) -##' @rdname ev_ops -##' @export -##' @keywords internal +#' @rdname ev_ops +#' @export +#' @keywords internal setGeneric("%then%", function(e1,e2) standardGeneric("%then%")) -##' @rdname ev_ops -##' @export -##' @keywords internal -setMethod("%then%",c("ev", "ev"), function(e1,e2) { - left <- as.data.frame(e1) - if(!has_name("ii",left) | !has_name("addl",left)) { - stop("both ii and addl are required in lhs",call.=FALSE) - } - y <- max(with(left, time + ii*addl + ii)) - e2@data$time <- y - e1 + e2 +#' @rdname ev_ops +#' @export +#' @keywords internal +setMethod("%then%",c("ev", "ev"), function(e1, e2) { + ev_seq(e1, e2) + # left <- e1@data + # if(!has_name("ii",left) | !has_name("addl",left)) { + # stop("both ii and addl are required in lhs",call.=FALSE) + # } + # y <- max(with(left, time + ii*addl + ii)) + # e2@data$time <- y + # add.ev(e1,e2) }) -##' @rdname ev_ops -##' @export -##' @keywords internal +#' @rdname ev_ops +#' @export +#' @keywords internal setMethod("+", c("ev", "numeric"), function(e1, e2) { - e1@data$time <- e1@data$time + e2 - e1 + stop("e1 + numeric operation is deprecated") + # e1@data$time <- e1@data$time + e2 + # e1 }) -##' @param x an ev object -##' @param recursive not used -##' @param ... other ev objects to collect -##' -##' @rdname ev_ops -##' @export -setMethod("c", "ev", function(x,...,recursive=TRUE) { +#' @param x an ev object +#' @param recursive not used +#' @param ... other ev objects to collect +#' +#' @rdname ev_ops +#' @export +setMethod("c", "ev", function(x, ..., recursive = TRUE) { y <- list(...) if(length(y)==0) return(x) + case <- x@case for(i in seq_along(y)) { - x <- add.ev(x,y[[i]]) + x <- add.ev(x, y[[i]]) } + x@case <- case return(x) }) -add.ev <- function(e1,e2) { +add.ev <- function(e1, e2) { short <- setdiff(names(e1@data), names(e2@data)) long <- setdiff(names(e2@data), names(e1@data)) if(any(short=="ID") | any(long=="ID")) { - stop("ID found in one ev object but not the other") + stop("ID found in one ev object but not the other.") } - if(length(short)>0) { - add <- as.list(rep(0,length(short))) + if(length(short) > 0) { + add <- as.list(rep(0, length(short))) names(add) <- short e2@data <- cbind(e2@data, add) } @@ -390,79 +405,89 @@ add.ev <- function(e1,e2) { names(add) <- long e1@data <- cbind(e1@data, add) } + e1@data <- as.data.frame(bind_rows(e1@data, e2@data)) - if(has_name("ID", e1@data)) { - e1@data<- e1@data[order(e1@data$ID, e1@data$time),] + if("ID" %in% names(e1@data)) { + e1@data <- e1@data[order(e1@data$ID, e1@data$time), ] } else { - e1@data<- e1@data[order(e1@data$time),] + e1@data <- e1@data[order(e1@data$time), ] } return(e1) } - - -##' Replicate an event object -##' -##' An event sequence can be replicated a certain number of -##' times in a certain number of IDs. -##' -##' @param x event object -##' @param ID numeric vector if IDs -##' @param n passed to \code{\link{ev_repeat}} -##' @param wait passed to \code{\link{ev_repeat}} -##' @param as.ev if \code{TRUE} an event object is returned -##' @param id deprecated; use \code{ID} instead -##' -##' @seealso \code{\link{ev_repeat}} -##' -##' @examples -##' -##' e1 <- c(ev(amt=100), ev(amt=200, ii=24, addl=2, time=72)) -##' -##' ev_rep(e1, 1:5) -##' -##' @return -##' A single data.frame or event object as -##' determined by the value of \code{as.ev}. -##' -##' @export +#' Replicate an event object +#' +#' An event sequence can be replicated a certain number of +#' times in a certain number of IDs. +#' +#' @param x event object +#' @param ID numeric vector if IDs +#' @param n passed to \code{\link{ev_repeat}} +#' @param wait passed to \code{\link{ev_repeat}} +#' @param as.ev if \code{TRUE} an event object is returned +#' @param id deprecated; use \code{ID} instead +#' +#' @seealso \code{\link{ev_repeat}} +#' +#' @examples +#' +#' e1 <- c(ev(amt=100), ev(amt=200, ii=24, addl=2, time=72)) +#' +#' ev_rep(e1, 1:5) +#' +#' @return +#' A single data.frame or event object as +#' determined by the value of \code{as.ev}. +#' +#' @export ev_rep <- function(x, ID = 1, n = NULL, wait = 0, as.ev = FALSE, id = NULL) { if(!missing(id)) { warning("id argument is deprecated; use ID instead") ID <- id } - x <- expand_event_object(as.data.frame(x),ID) + if(!inherits(x, c("data.frame", "ev"))) { + stop("x must be a data.frame or ev object.") + } + case <- ifelse(is.ev(x), x@case, 0) + data <- expand_event_object(to_data_frame(x), ID) if(!is.null(n)) { if(n > 1) { - x <- ev_repeat(x,n=n,wait=wait) + data <- ev_repeat(data, n = n, wait = wait) } } - if(as.ev) return(as.ev(x)) - return(x) + if(isTRUE(as.ev)) { + set_ev_case(as.ev(data), case) + } else { + recase_ev(data, case) + } } -##' Repeat a block of dosing events -##' -##' @param x event object or dosing data frame -##' @param n number of times to repeat -##' @param wait time to wait between repeats -##' @param as.ev if \code{TRUE}, an event object is -##' returned; otherwise a data.frame is returned -##' -##' @return -##' See \code{as.ev} argument. -##' -##' @export -ev_repeat <- function(x,n,wait=0,as.ev=FALSE) { - x <- As_data_set(x) +#' Repeat a block of dosing events +#' +#' @param x event object or dosing data frame +#' @param n number of times to repeat +#' @param wait time to wait between repeats +#' @param as.ev if \code{TRUE}, an event object is +#' returned; otherwise a data.frame is returned +#' +#' @return +#' See \code{as.ev} argument. +#' +#' @export +ev_repeat <- function(x, n, wait = 0, as.ev = FALSE) { + if(!inherits(x, c("data.frame", "ev"))) { + stop("x must be a data frame or ev object.") + } + case <- ifelse(is.ev(x), x@case, 1) + x <- to_data_frame(x) if(!exists("ii", x)) { x["ii"] <- 0 } if(!exists("addl", x)) { x["addl"] <- 0 } - start <- x[1,"time"] + start <- x[1, "time"] end <- x$time + x$ii*x$addl + x$ii end <- max(end) + wait out <- vector("list", n) @@ -471,30 +496,29 @@ ev_repeat <- function(x,n,wait=0,as.ev=FALSE) { nxt$time <- start + nxt$time + end*(i-1) out[[i]] <- nxt } - out <- bind_rows(out) - if(exists("ID", out)) { - out <- arrange__(out, c("ID", "time")) - out <- select__(out, unique(c("ID",colnames(out)))) + out <- as.data.frame(bind_rows(out)) + if("ID" %in% names(out)) { + out <- out[order(out$ID, out$time),, drop = FALSE] + out <- out[, unique(c("ID", colnames(out))), drop = FALSE] + rownames(out) <- NULL } - if(as.ev) { - return(as.ev(out)) + if(isTRUE(as.ev)) { + set_ev_case(as.ev(out), case) } else { - return(out) + recase_ev(out, case) } } - - #' Schedule a series of event objects #' #' Use this function when you want to schedule two or more event objects in time #' according the dosing interval (`ii`) and additional doses (`addl`). #' -#' @param ... event objects or numeric arguments named `wait` or `ii` to -#' implement a period of no-dosing activity in the sequence (see details) -#' @param ID numeric vector of subject IDs -#' @param .dots a list of event objects that replaces `...` -#' @param id deprecated; use `ID` +#' @param ... Event objects or numeric arguments named `wait` or `ii` to +#' implement a period of no-dosing activity in the sequence (see details). +#' @param ID Numeric vector of subject IDs. +#' @param .dots A list of event objects that replaces `...`. +#' @param id Deprecated; use `ID`. #' #' @details #' @@ -573,6 +597,7 @@ ev_seq <- function(..., ID = NULL, .dots = NULL, id = NULL) { if(is.null(names(evs))) { names(evs) <- rep(".", length(evs)) } + case <- NULL start <- 0 .ii <- NA_real_ ii <- 0 @@ -586,7 +611,7 @@ ev_seq <- function(..., ID = NULL, .dots = NULL, id = NULL) { when = "0.11.3", what = "ev_seq(.ii='has been renamed to ii')", details = c( - i = "Use `ii` instead of `.ii` to set time between doses." + i = "Use `ii` instead of `.ii` to set time between doses." ) ) this_name <- "ii" @@ -622,11 +647,12 @@ ev_seq <- function(..., ID = NULL, .dots = NULL, id = NULL) { ) stop(err) } - e <- as.data.frame(evs[[i]]) - if(is.null(e[["ii"]])) { + if(is.null(case)) case <- evs[[i]]@case + e <- evs[[i]]@data + if(!("ii" %in% names(e))) { e[["ii"]] <- 0 } - if(is.null(e[["addl"]])) { + if(!("addl" %in% names(e))) { e[["addl"]] <- 0 } after <- ifelse(is.null(e[[".after"]]), 0, e[[".after"]]) @@ -642,19 +668,19 @@ ev_seq <- function(..., ID = NULL, .dots = NULL, id = NULL) { } out <- bind_rows(out) out[[".after"]] <- NULL - if(exists("rate", out)) { + if("rate" %in% names(out)) { out[["rate"]] <- na2zero(out[["rate"]]) } - if(exists("ss",out)) { + if("ss" %in% names(out)) { out[["ss"]] <- na2zero(out[["ss"]]) } if(is.numeric(ID)) { - out <- ev_rep(out,ID) + out <- ev_rep(out, ID) } ans <- as.data.frame(out, stringsAsFactors = FALSE) ans <- ans[order(ans$time),, drop = FALSE] rownames(ans) <- NULL - as.ev(ans) + set_ev_case(as.ev(ans), case) } #' @export diff --git a/R/model_include.R b/R/model_include.R index 5c940cac..049f81c4 100644 --- a/R/model_include.R +++ b/R/model_include.R @@ -173,4 +173,8 @@ plugins[["autodec"]] <- list( name = "autodec", code = "// auto-dec plugin" ) +plugins[["evtools"]] <- list( + name = "evtools", code = "#define _MRGSOLVE_USING_EVTOOLS_" +) + # nocov end diff --git a/R/mrgsolve.R b/R/mrgsolve.R index 377e16b9..8a2e73e8 100644 --- a/R/mrgsolve.R +++ b/R/mrgsolve.R @@ -20,33 +20,21 @@ tran_upper <- c("AMT", "II", "SS", "CMT", "ADDL", "RATE", "EVID","TIME") -nodataset <- matrix( - 0, nrow=0, ncol=8, - dimnames=list( - NULL, - c("ID", "time", "evid", "amt", "cmt","addl", "ii", "ss") - ) -) - null_idata <- matrix( 0, - nrow=0, ncol=1, - dimnames=list(NULL, c("ID")) + nrow = 0, ncol = 1, + dimnames = list(NULL, c("ID")) ) - -no_idata_set <- function() { - structure(null_idata,class="valid_idata_set") -} - null_data <- matrix( 0, - nrow=0, ncol=3, + nrow = 0, ncol = 3, dimnames=list(NULL, c("ID", "time", "cmt")) ) -no_data_set <- function() { - structure(matrix(1,dimnames=list(NULL, "ID")), class="valid_data_set") -} +class(null_idata) <- c("valid_idata_set", "matrix") +class(null_data) <- c("valid_data_set", "matrix") +no_idata_set <- function() null_idata +no_data_set <- function() null_data tgrid_matrix <- function(x) { n <- length(x) @@ -71,13 +59,13 @@ tgrid_id <- function(col,idata) { return(match(col,sort(unique(col)))-1) } - -validate_idata <- function(idata) { - if(is.null(idata)) return(invisible(TRUE)) - if(!(is.data.frame(idata) | is.matrix(idata))) - wstop("idata needs to be either NULL, data.frame, or matrix.") - return(invisible(TRUE)) -} +# TODO: remove +# validate_idata <- function(idata) { +# if(is.null(idata)) return(invisible(TRUE)) +# if(!(is.data.frame(idata) | is.matrix(idata))) +# wstop("idata needs to be either NULL, data.frame, or matrix.") +# return(invisible(TRUE)) +# } #' Simulate from a model object @@ -221,7 +209,7 @@ mrgsim <- function(x, data=NULL, idata=NULL, events=NULL, nid=1, ...) { if(have_data) { if(is.ev(data)) { - data <- as.data.frame(data, add_ID = 1) + data <- ev_to_ds(data) } if(have_idata) { return(mrgsim_di(x, data = data, idata = idata, ...)) @@ -239,7 +227,7 @@ mrgsim <- function(x, data=NULL, idata=NULL, events=NULL, nid=1, ...) { #' @rdname mrgsim #' @export -mrgsim_df <- function(...,output="df") mrgsim(...,output=output) +mrgsim_df <- function(...,output="df") mrgsim(..., output = output) #' mrgsim variant functions #' @@ -285,9 +273,8 @@ mrgsim_e <- function(x, events, idata = NULL, data = NULL, ...) { } wstop("invalid 'events' argument") } - events <- as.data.frame(events, add_ID = 1) + events <- ev_to_ds(events) args <- list(...) - # x <- do.call(update, c(x,args)) args <- combine_list(x@args,args) do.call( do_mrgsim, @@ -300,7 +287,7 @@ mrgsim_e <- function(x, events, idata = NULL, data = NULL, ...) { mrgsim_d <- function(x, data, idata = NULL, events = NULL, ...) { if(!is.mrgmod(x)) mod_first() if(is.ev(data)) { - data <- as_data_set(data) + data <- ev_to_ds(data) } args <- list(...) args <- combine_list(x@args,args) @@ -325,13 +312,13 @@ mrgsim_ei <- function(x, events, idata, data = NULL, ...) { wstop("invalid 'events' argument") } expand <- !has_ID(events) & nrow(idata) > 0 - events <- as.data.frame(events, add_ID = 1) + events <- ev_to_ds(events) idata <- as.data.frame(idata) if(!has_ID(idata)) { - idata[["ID"]] <- seq_len(nrow(idata)) + idata$ID <- seq_len(nrow(idata)) } if(expand) { - events <- expand_event_object(events,idata[["ID"]]) + events <- expand_event_object(events, idata[["ID"]]) } args <- list(...) args <- combine_list(x@args,args) @@ -345,7 +332,7 @@ mrgsim_ei <- function(x, events, idata, data = NULL, ...) { #' @export mrgsim_di <- function(x, data, idata, events = NULL, ...) { if(!is.mrgmod(x)) mod_first() - data <- as.data.frame(data, add_ID = 1) + data <- As_data_set(data) idata <- as.data.frame(idata) if(!has_ID(idata)) { idata <- bind_col(idata, "ID", seq_len(nrow(idata))) @@ -366,7 +353,7 @@ mrgsim_i <- function(x, idata, data = NULL, events = NULL, ...) { if(!has_ID(idata)) { idata <- bind_col(idata, "ID", seq_len(nrow(idata))) } - data <- matrix(idata[["ID"]], ncol = 1, dimnames = list(NULL, "ID")) + data <- matrix(idata$ID, ncol = 1, dimnames = list(NULL, "ID")) args <- list(...) args <- combine_list(x@args,args) do.call( @@ -776,7 +763,7 @@ qsim <- function(x, if(!is.mrgmod(x)) mod_first() if(is.ev(data)) { - data <- as.data.frame.ev(data, add_ID = 1) + data <- as.data.frame.ev(data, add_ID = 1, final = TRUE) } ## data diff --git a/inst/base/modelheader.h b/inst/base/modelheader.h index 0f38a8cb..40c9d078 100644 --- a/inst/base/modelheader.h +++ b/inst/base/modelheader.h @@ -93,6 +93,14 @@ typedef double capture; #define A_0(a) _A_0_[a-1] #define DADT(a) _DADT_[a-1] #define T _ODETIME_[0] +#define EXP(a) exp(a) +#define LOG(a) log(a) +#define SQRT(a) sqrt(a) +#endif + +// EVTOOLS PLUGIN +#ifdef _MRGSOLVE_USING_EVTOOLS_ +#include "mrgsolve-evtools.h" #endif // These are the fundamental macros for @@ -128,10 +136,11 @@ typedef double capture; #define CFONSTOP() (self.CFONSTOP = true); // Carry forward on stop #define SYSTEMNOTADVANCING (self.SYSTEMOFF) #define SOLVINGPROBLEM (self.solving) -#define _SETINIT if(self.newind <=1) // Convenience -#define _STOP_ID() (self.SYSTEMOFF=2); -#define _STOP_ID_CF() (self.SYSTEMOFF=1); -#define _STOP_ERROR() (self.SYSTEMOFF=9); +#define _SETINIT if(self.newind <= 1) // Convenience +#define _STOP_ID() (self.SYSTEMOFF = 1); // Stop this ID, log record, and fill NA after that +#define _STOP_ID_CF() (self.SYSTEMOFF = 2); // Stop this ID and carry forward +#define _STOP_ID_NA() (self.SYSTEMOFF = 3); // Fill na +#define _STOP_ERROR() (self.SYSTEMOFF = 9); // CRUMP // Macro to insert dxdt_CMT = 0; for all compartments #define DXDTZERO() for(int _i_ = 0; _i_ < _nEQ; ++_i_) _DADT_[_i_] = 0; diff --git a/inst/base/mrgsolv.h b/inst/base/mrgsolv.h index ad6c4ab0..d44d99ce 100644 --- a/inst/base/mrgsolv.h +++ b/inst/base/mrgsolv.h @@ -102,8 +102,8 @@ class databox { bool CFONSTOP; ///< carry forward on stop indicator void* envir; ///< model environment void stop() {SYSTEMOFF=9;}///< stops the problem when the next record is started - void stop_id() {SYSTEMOFF=1;}///< stops solving for the current id, filling with NA - void stop_id_cf(){SYSTEMOFF=2;}///< stops solving for the current id, filling last value + void stop_id() {SYSTEMOFF=2;}///< stops solving for the current id, filling with NA + void stop_id_cf(){SYSTEMOFF=1;}///< stops solving for the current id, filling last value std::vector mevector;///< a collection of model events to pass back void mevent(double time, int evid);///< constructor for evdata objects double mtime(double time);///< creates evdata object for simple model event time diff --git a/inst/base/mrgsolve-evtools.h b/inst/base/mrgsolve-evtools.h new file mode 100644 index 00000000..1c73ca02 --- /dev/null +++ b/inst/base/mrgsolve-evtools.h @@ -0,0 +1,7 @@ + +namespace mrgsolve { + namespace evt { + void push(databox& self, mrg::evdata ev) { + self.mevector.push_back(ev); + } +} diff --git a/inst/maintenance/unit/test-nm-vars.R b/inst/maintenance/unit/test-nm-vars.R index 58a02a8d..7d2cb463 100644 --- a/inst/maintenance/unit/test-nm-vars.R +++ b/inst/maintenance/unit/test-nm-vars.R @@ -196,6 +196,9 @@ test_that("nm-vars functional test", { [ main ] F1 = F1I; A_0(3) = 50; + capture a = EXP(CL); + capture b = LOG(VC); + capture c = SQRT(KA); [ ode ] DADT(1) = -KA * A(1); DADT(2) = KA * A(1) - (CL/VC) * A(2); @@ -212,4 +215,7 @@ test_that("nm-vars functional test", { expect_equal(out1$GUT, out2$A1, tolerance = tol) expect_equal(out1$CENT, out2$A2, tolerance = tol) expect_equal(out1$RESP[1], out2$A3[1], tolerance = tol) + expect_equal(out2$a[1], exp(mod2$CL), tolerance = 1e-3) + expect_equal(out2$b[1], log(mod2$VC), tolerance = 1e-3) + expect_equal(out2$c[1], sqrt(mod2$KA), tolerance = 1e-3) }) diff --git a/inst/maintenance/unit/test-on-off.R b/inst/maintenance/unit/test-on-off.R index a2fd2d04..ac126335 100644 --- a/inst/maintenance/unit/test-on-off.R +++ b/inst/maintenance/unit/test-on-off.R @@ -25,10 +25,12 @@ context("test-on-off") ode_on_off_1 <- ' [ param ] R = 1, F1 = 1 -[ cmt ] A +[ cmt ] A [ main ] F_A = F1; [ ode ] dxdt_A = R; +[ capture ] NEWIND ' +mod <- mcode("ode_on_off_1", ode_on_off_1) test_that("compartment is turned on when F is zero", { data <- c( @@ -40,7 +42,7 @@ test_that("compartment is turned on when F is zero", { # run out to 24 hours # with events at time 4 and 6, there would be duplicate records at # those times - mod <- mcode("ode_on_off_1", ode_on_off_1) + out <- mrgsim(mod, data) ans <- out$A time <- out$time @@ -88,3 +90,14 @@ test_that("compartment with active infusion can be turned off", { expect_equal(comp[[1]], comp[[2]]) expect_equal(comp[[1]], comp[[3]]) }) + +test_that("evid 3 doesn't change NEWIND", { + dose <- ev(amt = 0, evid = 3, cmt = 1, time = 5) + out <- mrgsim(mod, dose, output = "df") + expect_equal(out$NEWIND[1], 0) + out <- out[-1,] + expect_true(all(out$NEWIND==2)) + expect_equal(out$A[5], 5) + expect_equal(out$A[6], 0) + expect_equal(out$A[7], 1) +}) diff --git a/inst/maintenance/unit/test-stop-id.R b/inst/maintenance/unit/test-stop-id.R new file mode 100644 index 00000000..87dc9618 --- /dev/null +++ b/inst/maintenance/unit/test-stop-id.R @@ -0,0 +1,64 @@ +library(testthat) +library(mrgsolve) + +Sys.setenv(R_TESTS="") +options("mrgsolve_mread_quiet"=TRUE) + +context("test-stop-id") + +code <- ' +$PROB +We can make the simulation stop for the current ID and fill either +NA or the last value; OR we can just make the simulation stop with error. +$PARAM STOP_CF = 0, STOP_NA = 0, STOP_CRUMP = 0 +$CMT A +$ODE dxdt_A = 1; +$MAIN +if(NEWIND <=1) capture stopped = 0; +$ERROR +if(STOP_CF==1) { + if(TIME==4) { + self.stop_id_cf(); + stopped = 1; + } +} +if(STOP_NA==1) { + if(TIME==3) { + self.stop_id(); + stopped = 1; + } +} +if(STOP_CRUMP==1) { + self.stop(); +} +' + +mod <- mcode("test-stop-id", code, end = 6) + +test_that("Stop the current ID and carry forward", { + mod <- param(mod, STOP_CF = 1, STOP_NA = 0, STOP_CRUMP = 0) + out <- mrgsim_df(mod) + pre <- out[out$time <= 3,,drop=FALSE] + post <- out[out$time > 3,,drop=FALSE] + expect_true(all(pre$stopped==0)) + expect_true(all(post$stopped==1)) + expect_equal(pre$A, c(0,1,2,3)) + expect_true(all(post$A==4)) +}) + +test_that("Stop the current ID and fill NA", { + mod <- param(mod, STOP_NA = 1, STOP_CF = 0, STOP_CRUMP = 0) + out <- mrgsim_df(mod) + pre <- out[!is.na(out$time),,drop=FALSE] + post <- out[is.na(out$time),,drop=FALSE] + expect_equal(nrow(pre), 4) + expect_equal(pre$A, c(0,1,2,3)) + expect_equal(pre$stopped, c(0,0,0,1)) + expect_equal(nrow(post), 3) + expect_true(all(is.na(post))) +}) + +test_that("Stop the entire simulation", { + mod <- param(mod, STOP_NA = 0, STOP_CF = 0, STOP_CRUMP = 1) + expect_error(mrgsim_df(mod), regexp="the problem was stopped at user request.") +}) diff --git a/man/as.ev.Rd b/man/as.ev.Rd index 193b263d..50fbec40 100644 --- a/man/as.ev.Rd +++ b/man/as.ev.Rd @@ -13,14 +13,17 @@ as.ev(x, ...) \S4method{as.ev}{ev}(x, ...) } \arguments{ -\item{x}{an object to coerce} +\item{x}{An object to coerce.} -\item{...}{not used} +\item{...}{Not used.} -\item{keep_id}{if \code{TRUE}, \code{ID} column is retained if it exists} +\item{keep_id}{If \code{TRUE}, \code{ID} column is retained if it exists.} -\item{clean}{if \code{TRUE}, only dosing or ID information is retained in -the result} +\item{clean}{If \code{TRUE}, only dosing or ID information is retained in +the result.} +} +\value{ +An object with class ev. } \description{ Coerce an object to class ev diff --git a/man/as_data_set.Rd b/man/as_data_set.Rd index 251b130d..451d3bf0 100644 --- a/man/as_data_set.Rd +++ b/man/as_data_set.Rd @@ -25,22 +25,32 @@ The goal is to take a series of event objects and combine them into a single data set that can be passed to \code{\link[=data_set]{data_set()}}. } \details{ -Each event object is added to the data frame as an \code{ID} -or set of \code{ID}s that are distinct from the \code{ID}s -in the other event objects. Note that including \code{ID} -argument to the \code{\link[=ev]{ev()}} call where \code{length(ID)} -is greater than one will render that set of -events for all of \code{ID}s that are requested. +Each event object is added to the data frame as an \code{ID} or set of \code{ID}s +that are distinct from the \code{ID}s in the other event objects. Note that +including \code{ID} argument to the \code{\link[=ev]{ev()}} call where \code{length(ID)} is greater +than one will render that set of events for all of \code{ID}s that are requested. + +When determining the case for output names, the \code{case} attribute for +the first \code{ev} object passed will be used to set the case for the output +data.frame. To get a data frame with one row (event) per \code{ID}, look at \code{\link[=expand.ev]{expand.ev()}}. } \examples{ +a <- ev(amt = c(100,200), cmt=1, ID = seq(3)) +b <- ev(amt = 300, time = 24, ID = seq(2)) +c <- ev(amt = 1000, ii = 8, addl = 10, ID = seq(3)) + +as_data_set(a, b, c) -as_data_set(ev(amt = c(100,200), cmt=1, ID = seq(3)), - ev(amt = 300, time = 24, ID = seq(2)), - ev(amt = 1000, ii = 8, addl = 10, ID = seq(3))) +d <- evd(amt = 500) +as_data_set(d, a) + # Instead of this, use expand.ev as_data_set(ev(amt = 100), ev(amt = 200), ev(amt = 300)) } +\seealso{ +\code{\link[=expand.ev]{expand.ev()}}, \code{\link[=ev]{ev()}} +} diff --git a/man/design.Rd b/man/design.Rd index 1a88aaeb..02b3d353 100644 --- a/man/design.Rd +++ b/man/design.Rd @@ -53,7 +53,7 @@ mod \%>\% idata_set(idata) \%>\% design(list(des1, des2),"amt") \%>\% data_set(data) \%>\% - mrgsim \%>\% + mrgsim() \%>\% plot(RESP~time|GRP) } diff --git a/man/ev-class.Rd b/man/ev-class.Rd index a6e7249c..e5cf04bc 100644 --- a/man/ev-class.Rd +++ b/man/ev-class.Rd @@ -11,6 +11,8 @@ S4 events class \describe{ \item{\code{data}}{a data frame of events} + +\item{\code{case}}{indicates how to handle column naming upon coerce to data.frame} }} \keyword{internal} diff --git a/man/ev.Rd b/man/ev.Rd index f6cc92ef..25d5675a 100644 --- a/man/ev.Rd +++ b/man/ev.Rd @@ -97,7 +97,7 @@ reduced_load <- dplyr::mutate(loading, amt = 750) } \seealso{ -\code{\link{ev_rep}}, \code{\link{ev_days}}, +\code{\link{evd}}, \code{\link{ev_rep}}, \code{\link{ev_days}}, \code{\link{ev_repeat}}, \code{\link{ev_assign}}, \code{\link{ev_seq}}, \code{\link{mutate.ev}}, \code{\link{as.ev}}, \code{\link{ev_methods}} diff --git a/man/ev_seq.Rd b/man/ev_seq.Rd index 7a535cc3..415b83a2 100644 --- a/man/ev_seq.Rd +++ b/man/ev_seq.Rd @@ -10,14 +10,14 @@ ev_seq(..., ID = NULL, .dots = NULL, id = NULL) \method{seq}{ev}(...) } \arguments{ -\item{...}{event objects or numeric arguments named \code{wait} or \code{ii} to -implement a period of no-dosing activity in the sequence (see details)} +\item{...}{Event objects or numeric arguments named \code{wait} or \code{ii} to +implement a period of no-dosing activity in the sequence (see details).} -\item{ID}{numeric vector of subject IDs} +\item{ID}{Numeric vector of subject IDs.} -\item{.dots}{a list of event objects that replaces \code{...}} +\item{.dots}{A list of event objects that replaces \code{...}.} -\item{id}{deprecated; use \code{ID}} +\item{id}{Deprecated; use \code{ID}.} } \value{ A single event object sorted by \code{time}. diff --git a/man/evd.Rd b/man/evd.Rd new file mode 100644 index 00000000..aa756174 --- /dev/null +++ b/man/evd.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/class_evd.R +\name{evd} +\alias{evd} +\alias{evd,mrgmod-method} +\alias{evd,missing-method} +\alias{evd,ev-method} +\alias{as.evd} +\title{Create an event object with data-like names} +\usage{ +evd(x, ...) + +\S4method{evd}{mrgmod}(x, ...) + +\S4method{evd}{missing}(x, ...) + +\S4method{evd}{ev}(x, ...) + +as.evd(x) +} +\arguments{ +\item{x}{An event object.} + +\item{...}{Arguments passed to \code{\link[=ev]{ev()}}.} +} +\description{ +This function calls \code{\link[=ev]{ev()}} to create an event object and then sets the +case attribute so that it renders nmtran data names in upper case. An +object created with \code{\link[=evd]{evd()}} can be used in the same way as an object +created with \code{\link[=ev]{ev()}}. +} +\details{ +Note that \code{evd} isn't a separate class; it is just an \code{ev} object with +a specific \code{case} attribute. See examples which illustrate the difference. +} +\examples{ +a <- evd(amt = 100) +b <- ev(amt = 300) +a +as.data.frame(a) +as_data_set(a, b) +as_data_set(b, a) +as.data.frame(seq(a, b)) + +} +\seealso{ +\code{\link[=ev]{ev()}}, \code{\link[=lctran]{lctran()}}, \code{\link[=uctran]{uctran()}} +} diff --git a/man/lctran.Rd b/man/lctran.Rd index 49b3a2f6..06907fb0 100644 --- a/man/lctran.Rd +++ b/man/lctran.Rd @@ -2,30 +2,55 @@ % Please edit documentation in R/data_set.R \name{lctran} \alias{lctran} -\title{Convert select upper case column names to lower case} +\alias{uctran} +\title{Change the case of nmtran-like data items} \usage{ -lctran(data) +lctran(data, warn = TRUE) + +uctran(data, warn = TRUE) } \arguments{ -\item{data}{an nmtran-like data frame} +\item{data}{A data set with nmtran-like format.} + +\item{warn}{If \code{TRUE}, a warning will be issued when there are both upper +and lower case versions of any nmtran-like column in the data frame.} } \value{ -A data.frame with renamed columns +A data frame with possibly renamed columns. The input data set, with select columns made lower case. } \description{ Previous data set requirements included lower case names for data items -like \code{AMT} and \code{EVID}. Lower case is no longer required. +like \code{AMT} and \code{EVID}. Lower case is no longer required. However, it is still +a requirement that nmtran like data column names are either all lower case +or all upper case. } \details{ -Columns that will be renamed with lower case versions: \code{AMT}, -\code{II}, \code{SS}, \code{CMT}, \code{ADDL}, \code{RATE}, \code{EVID}, -\code{TIME}. If a lower case version of these names exist in the data -set, the column will not be renamed. +Columns that will be renamed with lower or upper case versions: +\itemize{ +\item \code{AMT / amt} +\item \code{II / ii} +\item \code{SS / ss} +\item \code{CMT / cmt} +\item \code{ADDL / addl} +\item \code{RATE / rate} +\item \code{EVID / evid} +\item \code{TIME / time} +} + +If both lower and upper case versions of the name are present in the data +frame, no changes will be made. } \examples{ -data <- data.frame(TIME = 0, AMT = 5, II = 24, addl = 2) +data <- data.frame(TIME = 0, AMT = 5, II = 24, addl = 2, WT = 80) +lctran(data) + +data <- data.frame(TIME = 0, AMT = 5, II = 24, addl = 2, wt = 80) +uctran(data) + +# warning +data <- data.frame(TIME = 1, time = 2, CMT = 5) lctran(data) } diff --git a/src/datarecord.cpp b/src/datarecord.cpp index 95478cf4..a94d4330 100644 --- a/src/datarecord.cpp +++ b/src/datarecord.cpp @@ -179,10 +179,7 @@ void datarecord::implement(odeproblem* prob) { prob->on(i); prob->rate0(i,0.0); } - { - prob->newind(1); - prob->init_call(Time); - } + prob->init_call(Time); break; case 8: // replace prob->y(eq_n, Amt); diff --git a/src/devtran.cpp b/src/devtran.cpp index 1458f67e..b928fa76 100644 --- a/src/devtran.cpp +++ b/src/devtran.cpp @@ -414,9 +414,14 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, this_rec->id(id); if(prob.systemoff()) { + // This starts a loop that will finish the remaining records + // for an individual; no other calls to any model functions will + // be made + // SYSTEMOFF = 1 --> copy model results to the line + // SYSTEMOFF != 0, !=1 --> fill NA unsigned short int status = prob.systemoff(); if(status==9) CRUMP("the problem was stopped at user request."); - if(status==999) CRUMP("999 sent from the model"); + if(status==999) CRUMP("999 sent from the model."); if(this_rec->output()) { if(status==1) { ans(crow,0) = this_rec->id(); @@ -430,7 +435,7 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, } else { for(int k=0; k < ans.ncol(); ++k) { ans(crow,k) = NA_REAL; - } + } } ++crow; } @@ -582,7 +587,7 @@ Rcpp::List DEVTRAN(const Rcpp::List parin, CRUMP("Compartment number in modeled event out of range."); } } - rec_ptr new_ev = NEWREC(this_cmt,this_evid,this_amt,this_time,0.0); + rec_ptr new_ev = NEWREC(this_cmt,this_evid,this_amt,this_time,mt[mti].rate); new_ev->phantom_rec(); if(mt[mti].now) { new_ev->implement(&prob); diff --git a/tests/testthat/test-data_set.R b/tests/testthat/test-data_set.R index 53669de6..888df1a1 100644 --- a/tests/testthat/test-data_set.R +++ b/tests/testthat/test-data_set.R @@ -178,4 +178,30 @@ test_that("add position argument to expand observations issue-565", { expect_equal(dat1$time,dat2$time) }) +test_that("Convert names to lower case with lctran", { + data <- data.frame(time = 1, EVID = 2, ss = 2, foo = 5, BAR = 2) + ans <- lctran(data) + expect_equal( + names(ans), + c("time", "evid", "ss", "foo", "BAR") + ) + data <- data.frame(time = 1, EVID = 2, ss = 2, TIME = 5, BAR = 2) + expect_warning( + lctran(data), + regexp = "There are both upper and lower case" + ) +}) +test_that("Convert names to upper case with uctran", { + data <- data.frame(time = 1, EVID = 2, ss = 2, foo = 5, BAR = 2) + ans <- uctran(data) + expect_equal( + names(ans), + c("TIME", "EVID", "SS", "foo", "BAR") + ) + data <- data.frame(time = 1, EVID = 2, ss = 2, TIME = 5, BAR = 2) + expect_warning( + uctran(data), + regexp = "There are both upper and lower case" + ) +}) diff --git a/tests/testthat/test-evd.R b/tests/testthat/test-evd.R new file mode 100644 index 00000000..a1d5edbd --- /dev/null +++ b/tests/testthat/test-evd.R @@ -0,0 +1,148 @@ +# Copyright (C) 2013 - 2022 Metrum Research Group +# +# This file is part of mrgsolve. +# +# mrgsolve is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# mrgsolve is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mrgsolve. If not, see . + +library(testthat) +library(mrgsolve) +library(dplyr) + +Sys.setenv(R_TESTS="") +options("mrgsolve_mread_quiet"=TRUE) + +context("test-evd") + +test_that("create evd object", { + a <- evd(amt = 100) + expect_is(a, "ev") + expect_equal(a@case, 1) + b <- ev(amt = 100) + expect_equal(b@case, 0) + + mod <- house() + mod <- evd(mod, amt = 100) + expect_identical(mod@args$events, a) + + c <- ev(a) + expect_is(c, "ev") + expect_equal(c@case, 0L) + + d <- evd(b) + expect_is(d, "ev") + expect_equal(d@case, 1L) +}) + +test_that("evd object has all lower case names", { + a <- ev(amt = 100, ii = 12, addl = 23) + b <- evd(amt = 100, ii = 12, addl = 23) + expect_identical(names(a), names(b)) + expect_identical(names(a), tolower(names(b))) +}) + +test_that("evd object realize names", { + a <- evd(amt = 100, ii = 12, addl = 23, ss = 1, rate = 2, + cmt = 5, time = 12, evid = 3, kyle = 0) + + b <- as.data.frame(a, add_ID = 1) + tnames <- seq(length(names(a))-2) + expect_identical(names(b)[tnames], toupper(names(a)[tnames])) + c <- mrgsolve:::ev_to_ds(a) + expect_identical(b, c) + expect_identical(names(b)[tnames], toupper(names(a))[tnames]) +}) + +test_that("evd object simulated names", { + a <- evd(amt = 100) + idata <- data.frame(ID = 1) + mod <- update(house(), end = -1) + out1 <- mrgsim(mod, a) + out2 <- mrgsim_e(mod, a) + out3 <- mrgsim_ei(mod, a, idata) + out4 <- qsim(mod, a) + out5 <- mrgsim_q(mod, a) + out6 <- mrgsim_d(mod, a) + out7 <- mrgsim_di(mod, a, idata) + x <- names(out1) + expect_equal(x, toupper(x)) + expect_equal(x, names(out2)) + expect_equal(x, names(out3)) + expect_equal(x, names(out4)) + expect_equal(x, names(out5)) + expect_equal(x, names(out6)) + expect_equal(x, names(out7)) +}) + +test_that("evd object carry out tran names", { + a <- evd(amt = 100, ii = 12, addl = 2, rate = 1) + mod <- update(house(), end = -1) + out1 <- mrgsim(mod, a, carry_out = "AMT, II, ADDL, RATE,CMT") + out2 <- mrgsim(mod, a, carry_out = "amt, ii, addl, rate, cmt") + expect_equal(names(out1), toupper(names(out1))) + expect_equal(names(out2)[1:2], c("ID", "TIME")) + carried <- names(out2)[seq(3,7)] + expect_equal(carried, tolower(carried)) +}) + +test_that("coerce ev object to evd", { + a <- ev(amt = 100) + b <- as.evd(a) + expect_identical(a@case, 0L) + expect_identical(b@case, 1L) +}) + +test_that("ev operations with evd objects", { + a <- evd(amt = 100) + b <- ev(amt = 200) + c <- evd(amt = 300) + + e <- ev_seq(a, b, a) + expect_is(e, "ev") + expect_equal(e@case, 1L) + ee <- as.data.frame(e) + expect_equal(names(ee), toupper(names(ee))) + + e2 <- ev_seq(b, a, b) + expect_is(e2, "ev") + expect_equal(e2@case, 0L) + ee2 <- as.data.frame(e2) + expect_equal(names(ee2), tolower(names(ee2))) + + e3 <- c(a, b) + expect_is(e3, "ev") + expect_equal(e3@case, 1L) + + e4 <- c(b, a) + expect_is(e4, "ev") + expect_equal(e4@case, 0L) + + d <- ev_rep(a, seq(3)) + expect_is(d, "data.frame") + expect_equal(names(d), toupper(names(d))) + + d2 <- ev_rep(a, seq(3), n = 2) + expect_is(d2, "data.frame") + expect_equal(names(d2), toupper(names(d2))) + + d3 <- as_data_set(a, b, c) + expect_is(d3, "data.frame") + expect_equal(names(d3), toupper(names(d3))) + + d4 <- as_data_set(b, a, b) + expect_is(d4, "data.frame") + check <- names(d4)[-1] + expect_equal(check, tolower(check)) + expect_equal(names(d4)[1], "ID") + +})