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

Parse $DATA record for the purpose of filtering the input data #711

Merged
merged 28 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bfae5ff
Parse $DATA record for the purpose of filtering the input data
barrettk Jul 2, 2024
18232f1
extend support for null mapping and dropping of columns
barrettk Jul 3, 2024
594b070
bug fix: revert modify_data_path_ctl change
barrettk Jul 3, 2024
7650823
rename nm_data_drop_skip_records --> nm_data_drop_records
barrettk Jul 3, 2024
ea48228
modularize parsing of nonmem expressions
barrettk Jul 5, 2024
0114008
remove new line addition to modify-records
barrettk Jul 5, 2024
a4f5cd9
fix method of inverting expressions
barrettk Jul 5, 2024
5c8aff6
fix: cols_rename when not dropping any columns
barrettk Jul 5, 2024
a77c84e
Hook up filtering to setup_bootstrap_run and add tests
barrettk Jul 9, 2024
4857202
add `filter` arg to `nm_data()` and add tests for `filter_nm_data()`
barrettk Jul 10, 2024
6a6525f
add support for NONMEM 7.3 filter options `EQN` and `NEN`
barrettk Jul 11, 2024
aadd58e
adjustments based on KyleB's feedback
barrettk Jul 17, 2024
7442e7d
update nm_data() `filter` parameter documentation for clarity
barrettk Jul 17, 2024
170bcf4
test fix: update referenced object
barrettk Jul 17, 2024
b174664
dont run examples for translate_nm_expr
barrettk Jul 17, 2024
857b737
adjust regex for `IGNORE=c1` type filtering
barrettk Jul 17, 2024
16f60f8
fix `@` filtering: Look for first _non-blank_ character
barrettk Jul 18, 2024
11430dd
documentation updates per feedback
barrettk Jul 31, 2024
2807dab
Change handling if parsing filter expressions fails
barrettk Aug 14, 2024
aa5c54b
documentation updates
barrettk Aug 14, 2024
2531941
error out if any unsupported fortran logical operators are found
barrettk Aug 14, 2024
4e0eb9e
Check number of records for finished based on models
barrettk Aug 15, 2024
0e23943
update .bbi_args parameter documentation in setup_bootstrap_run
barrettk Sep 13, 2024
053369f
nm_data() now supports bootstrap models
barrettk Sep 19, 2024
f59c5a4
Change which control stream file is used for parsing NONMEM filter ex…
barrettk Sep 19, 2024
7eddb3e
fix warning from previous commit
barrettk Sep 19, 2024
47c8301
adjust existing test: overwrite bootstrap control stream instead of p…
barrettk Sep 19, 2024
df3566e
more test adjustments and bug fix
barrettk Sep 19, 2024
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
125 changes: 125 additions & 0 deletions R/modify-records.R
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,131 @@ get_table_columns <- function(.mod, from_data = TRUE){
return(table_cols)
}


# Function to translate NONMEM operators to R operators
translate_operator <- function(expr) {
expr <- gsub("\\.EQ\\.", "==", expr)
expr <- gsub("\\.NE\\.", "!=", expr)
expr <- gsub("\\.LT\\.", "<", expr)
expr <- gsub("\\.LE\\.", "<=", expr)
expr <- gsub("\\.GT\\.", ">", expr)
expr <- gsub("\\.GE\\.", ">=", expr)
return(expr)
}

# Function to invert an expression (for ignore statements)
invert_expression <- function(expr) {
expr <- dplyr::case_when(
grepl("==", expr) ~ gsub("==", "!=", expr),
grepl("!=", expr) ~ gsub("!=", "==", expr),
grepl("<=", expr) ~ gsub("<=", ">=", expr),
grepl(">=", expr) ~ gsub(">=", "<=", expr),
grepl("<", expr) ~ gsub("<", ">", expr),
grepl(">", expr) ~ gsub(">", "<", expr),
TRUE ~ expr
)
return(expr)
}


#' Filter `NONMEM` input data based on `IGNORE` and `ACCEPT` record options
#'
#' @param .mod a `bbi_nonmem_model` object
#' @param data a starting dataset
#' @keywords internal
filter_nm_data <- function(.mod, data = nm_data(.mod)){
barrettk marked this conversation as resolved.
Show resolved Hide resolved

data_recs <- get_records(.mod, "data")
n_data <- length(data_recs)
if(n_data !=1){
recs_fmt <- purrr::map_chr(data_recs, function(rec) rec$format())
rlang::abort(
c(
glue::glue("Expected a single data record, but found {n_data}:\n\n"),
recs_fmt
)
)
}
data_recs[[1]]$parse()

# Extract & format IGNORE options
ignore_opts <- purrr::keep(data_recs[[1]]$values, function(val){
inherits(val, "nmrec_option_value") && identical(val[["name"]], "ignore")
})
ignore_vals <- purrr::map_chr(ignore_opts, function(ign_val){
gsub("\\(|\\)", "", unquote_filename(ign_val$value))
})

# Extract & format ACCEPT options
accept_opts <- purrr::keep(data_recs[[1]]$values, function(val){
inherits(val, "nmrec_option_value") && identical(val[["name"]], "accept")
})
accept_vals <- purrr::map_chr(accept_opts, function(acc_val){
gsub("\\(|\\)", "", unquote_filename(acc_val$value))
})


# Translate the ignore and accept expressions
ignore_exprs <- lapply(ignore_vals, translate_operator) %>% gsub(",", " &", .)
accept_exprs <- lapply(accept_vals, translate_operator) %>% gsub(",", " &", .)

## Combine the expressions into filter expressions ##
data_cols <- get_input_columns(.mod)

# `IGNORE=#`, `IGNORE=@`, `IGNORE=c1`, `IGNORE=(list)`
ignore_filters <- purrr::map_chr(ignore_exprs, function(expr) {
barrettk marked this conversation as resolved.
Show resolved Hide resolved
if(expr == "#"){
# IGNORE=# is the default. That is, in the absence of IGNORE option, any
# record whose first character is '#' is treated as a comment record.
col_filters <- purrr::map_chr(data_cols, function(col) {
barrettk marked this conversation as resolved.
Show resolved Hide resolved
paste0("!grepl('^#', ", col, ")")
})
return(paste(col_filters, collapse = " & "))
}else if(expr == "@"){
# IGNORE=@ signifies that any data record having an alphabetic character
# or `@` as its first non-blank character (not just in column 1)
# should be ignored. This permits a table file having header lines to be
# used as an NM-TRAN data set.
col_filters <- purrr::map_chr(data_cols, function(col) {
barrettk marked this conversation as resolved.
Show resolved Hide resolved
paste0("!grepl('^[A-Za-z@]', ", col, ")")
})
return(paste(col_filters, collapse = " & "))
}else if(grepl('^[a-zA-Z0-9]{1,}$', expr)){
# This is for `IGNORE=C` columns. Meaning ignore rows if the _first_ column
# contains 'C' (this form always points to the _first_ column)
# - the above regex looks for characters of length>=1, and no symbols
paste0(data_cols[1], "!=", "'", expr, "'")
}else{
# Invert list form expressions
return(invert_expression(expr))
}
})

# Only supports `ACCEPT=(list)` form
accept_filters <- purrr::map_chr(accept_exprs, function(expr) {
return(paste0(expr))
})

# Combine all filter expressions
all_filters <- c(ignore_filters, accept_filters)

# Create the final dplyr::filter expression
filter_expression <- paste(all_filters, collapse = " & ")
barrettk marked this conversation as resolved.
Show resolved Hide resolved

# Apply filters
tryCatch({
data %>% dplyr::filter(eval(parse(text = filter_expression)))
}, error = function(cond){
rlang::abort(
c(
"ignore and/or accept statements could not be converted to filters",
"The following errors occurred:",
cond$parent$message
)
)
})
}

#' Helper for checking if a specified record type is valid.
#'
#' Checks if the specified record type is valid. Note that this does _not_ check
Expand Down
17 changes: 17 additions & 0 deletions man/filter_nm_data.Rd

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