Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9d23b27
Add v0.4 mock_spec foundation
DougManuel May 18, 2026
75484a3
Address mock_spec milestone review
DougManuel May 18, 2026
33d7aee
Add direct mock specification helpers
DougManuel May 18, 2026
f29414d
Tighten mock_spec auditability contracts
DougManuel May 18, 2026
bd7b389
Add recodeflow mock_spec adapter
DougManuel May 18, 2026
b1895f5
Harden recodeflow mock_spec adapter
DougManuel May 18, 2026
a6aa4cf
Add native mock_spec backend
DougManuel May 18, 2026
501698b
Harden native mock_spec backend
DougManuel May 18, 2026
fb6ae3f
Add mock_spec post-processing layer
DougManuel May 18, 2026
6daef47
Harden mock_spec post-processing diagnostics
DougManuel May 18, 2026
2b6aa1e
Promote mock_spec pipeline assertions
DougManuel May 18, 2026
c28354a
Add optional simstudy mock_spec backend
DougManuel May 19, 2026
70da7b8
Harden optional simstudy backend
DougManuel May 19, 2026
e109d54
Polish v0.4 reference documentation
DougManuel May 20, 2026
3d448df
Clarify v0.4 postprocess and simstudy notes
DougManuel May 20, 2026
9fe335e
Route create_mock_data through mock_spec pipeline
DougManuel May 20, 2026
ca00377
Document and test create_mock_data v04 routing
DougManuel May 20, 2026
5404731
Add v04 functions to pkgdown reference index
DougManuel May 20, 2026
223e9bd
Merge pull request #28 from Big-Life-Lab/v04-production-refactor
DougManuel May 20, 2026
a7c224e
Start v04 documentation sprint
DougManuel May 20, 2026
028692f
Add v04 maintainer communication note
DougManuel May 20, 2026
200630c
Add recodeflow metadata how-to
DougManuel May 20, 2026
750bf13
Add diagnostics and garbage how-to
DougManuel May 20, 2026
3d7873f
Add v03 to v04 migration how-to
DougManuel May 20, 2026
9b00ff3
Add backend choice how-to
DougManuel May 20, 2026
d3f47c7
Add v04 design philosophy vignette
DougManuel May 20, 2026
5907d55
Prepare v04 docs for tag
DougManuel May 21, 2026
053cfe6
Merge pull request #29 from Big-Life-Lab/v04-documentation-sprint
DougManuel May 21, 2026
7bc0981
Fix config file name in README: mock_data_config.csv
DougManuel Jun 10, 2026
76043e4
Replace dplyr::case_when with base R and drop dplyr from Imports
DougManuel Jun 10, 2026
7e43162
Correct rType comment and extend characterization test per review
DougManuel Jun 10, 2026
f84e293
Centralize metadata loading in .load_metadata_df helper
DougManuel Jun 10, 2026
361c3f5
Document file-path input for create_wide_survival_data metadata args
DougManuel Jun 10, 2026
ed114db
Cross-reference CSV loaders, reject directory paths, pin verbose message
DougManuel Jun 10, 2026
9b397a9
Replace rType dispatch switch with generator lookup map
DougManuel Jun 10, 2026
3a5a6a3
Make generators stop on missing variables and warn on duplicates
DougManuel Jun 10, 2026
248244c
Refine failure-mode docs, NEWS framing, and duplicate-row tests
DougManuel Jun 10, 2026
6f698e6
Make self-contained roxygen examples runnable
DougManuel Jun 10, 2026
6756afe
Clarify example output and prune broken dontrun snippet
DougManuel Jun 10, 2026
40f4ba9
Fix check failures: portable test fixture paths and Rd link escape
DougManuel Jun 10, 2026
1ef3ecc
Polish docs, NEWS, and loader messages from final review
DougManuel Jun 10, 2026
0bf3b2f
Address PR review: survival path tests, skip tracking, de-silence NULLs
DougManuel Jun 10, 2026
7869dbe
Merge pull request #32 from Big-Life-Lab/v04-code-quality
DougManuel Jun 10, 2026
f69b3ab
Prepare v0.4.0 release: README status and NEWS heading
DougManuel Jun 10, 2026
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
7 changes: 4 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: MockData
Title: Generate Mock Data from Metadata Specifications
Version: 0.3.0
Version: 0.4.0
Authors@R: c(
person("Juan", "Li", role = "aut", email = "juli@ohri.ca"),
person("Douglas", "Manuel", role = c("aut", "cre"), email = "dmanuel@ohri.ca"),
Expand All @@ -25,13 +25,14 @@ VignetteBuilder: quarto
Depends:
R (>= 4.2.0)
Imports:
stats,
dplyr
stats
Suggests:
testthat (>= 3.0.0),
dplyr,
readr,
stringr,
lubridate,
simstudy (>= 0.8.1),
knitr,
quarto,
devtools,
Expand Down
14 changes: 14 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

S3method(print,mock_spec_validation_result)
S3method(print,mockdata_validation_result)
export(add_garbage)
export(apply_garbage)
Expand All @@ -14,6 +15,8 @@ export(create_wide_survival_data)
export(extract_distribution_params)
export(extract_proportions)
export(generate_garbage_values)
export(generate_mock_data_native)
export(generate_mock_data_simstudy)
export(get_cycle_variables)
export(get_enabled_variables)
export(get_raw_var_dependencies)
Expand All @@ -23,14 +26,25 @@ export(get_variables_by_role)
export(has_garbage)
export(identify_derived_vars)
export(import_from_recodeflow)
export(is_mock_spec)
export(make_garbage)
export(mock_categorical)
export(mock_continuous)
export(mock_date)
export(mock_spec)
export(mock_spec_categorical)
export(mock_spec_continuous)
export(mock_spec_date)
export(mock_spec_from_recodeflow)
export(parse_range_notation)
export(parse_variable_start)
export(postprocess_mock_data)
export(read_mock_data_config)
export(read_mock_data_config_details)
export(sample_with_proportions)
export(validate_mock_data_config)
export(validate_mock_data_config_details)
export(validate_mock_spec)
export(validate_mockdata_metadata)
importFrom(stats,rexp)
importFrom(stats,rnorm)
Expand Down
63 changes: 63 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,66 @@
# MockData 0.4.0 (2026-06-10)

## Breaking changes

- `create_cat_var()`, `create_con_var()`, and `create_date_var()` now stop
with the error `Variable '<name>' not found in variables metadata` when the
requested variable is absent, instead of warning and returning `NULL`. This
affects direct generator calls and `create_wide_survival_data()` (a
misspelled date-variable name now errors instead of being skipped with a
warning). `create_mock_data()` itself derives variable names from the
`variables` metadata, so it cannot trigger this error; its `validate = FALSE`
flag continues to convert any generator error to warn-and-skip on the legacy
path. Duplicate `variables` rows for the same variable now produce a warning
in the legacy `create_*` path before the first row is used (the v0.4
`mock_spec` path already errors on duplicate names).
- `create_mock_data()` error messages for missing metadata files changed from
`Configuration file does not exist:` / `Details file does not exist:` to
`variables file does not exist:` / `variable_details file does not exist:`.

## New features

- Started the v0.4 production refactor around a normalized `mock_spec`
architecture.
- Added `mock_spec()`, `mock_spec_continuous()`, `mock_spec_categorical()`,
`mock_spec_date()`, `is_mock_spec()`, and `validate_mock_spec()`.
- Added direct specification helpers `mock_continuous()`,
`mock_categorical()`, and `mock_date()` for simple use without
recodeflow-style metadata tables.
- Added `mock_spec_from_recodeflow()` to adapt recodeflow-style `variables`
and `variable_details` metadata into validated `mock_spec` objects while
preserving role/database filtering, categorical proportions, `recEnd`
missing-code semantics, valid ranges, garbage rules, date ranges, and
survival/date fields.
- Added `generate_mock_data_native()` to generate baseline valid mock data from
`mock_spec` objects with the native R backend.
- Added `postprocess_mock_data()` to apply `mock_spec` missing-code and
garbage-value rules after baseline generation, with diagnostics that
distinguish assigned missing/garbage rows from naturally drawn values.
- Post-processing diagnostics now protect naturally drawn missing-code
collisions from later garbage assignment, apply garbage rules in canonical
`low` -> `high` -> other order, and stop on repeated post-processing. This
prevents silent diagnostic drift when a naturally drawn missing-code value
would otherwise be overwritten by garbage assignment.
- Added `generate_mock_data_simstudy()` as a soft-gated optional backend for
baseline categorical and uniform continuous generation when `simstudy` is
installed, with native generation retained for MockData-specific semantics.
- The optional `simstudy` backend is kept in `Suggests`, requires
`simstudy >= 0.8.1`, and validates categorical labels before converting
generated values back into MockData's `mock_spec` levels.
- The optional `simstudy` backend now rejects variables named `id`, which
conflicts with `simstudy`'s generated row identifier, and normalizes
categorical output through an explicit label-or-index validation path.
- `create_mock_data()` now attempts the v0.4 `mock_spec` pipeline in strict
mode for supported recodeflow metadata, while retaining the legacy `create_*`
dispatch path for unsupported v0.4 backend features and lenient generation.
The v0.4 path attaches `mockdata_diagnostics` and uses `seed` for baseline
generation plus `seed + 1` for post-processing, so exact seeded output may
differ from v0.3.x even when the public seed is unchanged. Verbose mode now
reports whether the v0.4 or legacy path was chosen.
- Added forward-compatible specification fields: `spec_version`, `provenance`,
and `model_hint`.
- Existing v0.3 generator APIs remain available while v0.4 internals are built.

# MockData 0.3.0

## Breaking changes
Expand Down
44 changes: 16 additions & 28 deletions R/add_garbage.R
Original file line number Diff line number Diff line change
Expand Up @@ -82,36 +82,24 @@
#' @export
#'
#' @examples
#' \dontrun{
#' # Load metadata
#' variables <- read.csv(
#' system.file("extdata/minimal-example/variables.csv",
#' package = "MockData"),
#' stringsAsFactors = FALSE, check.names = FALSE
#' variables <- data.frame(
#' variable = c("age", "smoking"),
#' variableType = c("Continuous", "Categorical"),
#' stringsAsFactors = FALSE
#' )
#'
#' # Add garbage to age (high-range only)
#' vars <- add_garbage(variables, "age",
#' garbage_high_prop = 0.03, garbage_high_range = "[150, 200]")
#'
#' # Add garbage to smoking (low-range only)
#' vars <- add_garbage(vars, "smoking",
#' garbage_low_prop = 0.02, garbage_low_range = "[-2, 0]")
#'
#' # Add garbage to BMI (two-sided invalid values)
#' vars <- add_garbage(vars, "BMI",
#' garbage_low_prop = 0.02, garbage_low_range = "[-10, 15)",
#' garbage_high_prop = 0.01, garbage_high_range = "[60, 150]")
#'
#' # Generate data with garbage
#' mock_data <- create_mock_data(
#' databaseStart = "minimal-example",
#' variables = vars,
#' variable_details = variable_details,
#' n = 1000,
#' seed = 123
#' )
#' }
#' # Add high-range garbage to age and low-range garbage to smoking
#' vars_with_garbage <- variables |>
#' add_garbage("age",
#' garbage_high_prop = 0.03, garbage_high_range = "[150, 200]"
#' ) |>
#' add_garbage("smoking",
#' garbage_low_prop = 0.02, garbage_low_range = "[-2, 0]"
#' )
#' vars_with_garbage
#'
#' # Pass the result as the `variables` argument of create_mock_data() —
#' # see ?create_mock_data.
add_garbage <- function(variables, var,
garbage_low_prop = NULL, garbage_low_range = NULL,
garbage_high_prop = NULL, garbage_high_range = NULL) {
Expand Down
80 changes: 40 additions & 40 deletions R/create_cat_var.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
#'
#' @return data.frame with one column (the generated categorical variable), or NULL if:
#' \itemize{
#' \item Variable not found in metadata
#' \item Variable already exists in df_mock
#' \item Variable already exists in df_mock (a message is emitted)
#' \item No valid categories found in variable_details
#' }
#'
#' Errors if the variable is not found in the variables metadata. Warns and
#' uses the first row if multiple variables rows match.
#'
#' @details
#' **v0.3.0 API**: This function now accepts full metadata data frames and filters
#' internally for the specified variable and database. This is the "recodeflow pattern"
Expand Down Expand Up @@ -76,39 +78,37 @@
#' }
#'
#' @examples
#' \dontrun{
#' # Basic usage with metadata data frames
#' smoking <- create_cat_var(
#' var = "smoking",
#' databaseStart = "cchs2001_p",
#' variables = variables,
#' variable_details = variable_details,
#' n = 1000,
#' seed = 123
#' variables <- data.frame(
#' variable = "smoking",
#' variableType = "Categorical",
#' rType = "factor",
#' stringsAsFactors = FALSE
#' )
#' variable_details <- data.frame(
#' variable = "smoking",
#' recStart = c("1", "2", "3", "7"),
#' recEnd = c("1", "2", "3", "NA::b"),
#' proportion = c(0.5, 0.3, 0.17, 0.03),
#' catLabel = c(
#' "Never smoker", "Former smoker", "Current smoker", "Don't know"
#' ),
#' stringsAsFactors = FALSE
#' )
#'
#' # Expected output: data.frame with 1000 rows, 1 column ("smoking")
#' # Values: Factor with levels from metadata (e.g., "1", "2", "3", "7")
#' # Distribution: Based on proportions in variable_details
#' # Example:
#' # smoking
#' # 1 1
#' # 2 3
#' # 3 2
#' # 4 1
#' # 5 7
#' # ...
#'
#' # With missing data (uses proportions from metadata)
#' smoking <- create_cat_var(
#' var = "smoking",
#' databaseStart = "cchs2001_p",
#' databaseStart = "example",
#' variables = variables,
#' variable_details = variable_details,
#' n = 1000
#' n = 100,
#' seed = 123
#' )
#' # Missing codes (recEnd = "NA::b") automatically included based on proportions
#' # Code 7 ("Don't know") is an NA::b missing code, generated at its
#' # configured proportion alongside the substantive categories.
#' table(smoking$smoking)
#'
#' \dontrun{
#' # Not run: requires your own metadata CSV files
#' # With file paths instead of data frames
#' result <- create_cat_var(
#' var = "smoking",
Expand All @@ -133,25 +133,22 @@ create_cat_var <- function(var,
# ========== PARAMETER VALIDATION ==========

# Load metadata from file paths if needed
if (is.character(variables) && length(variables) == 1) {
variables <- read.csv(variables, stringsAsFactors = FALSE, check.names = FALSE)
}
if (is.character(variable_details) && length(variable_details) == 1) {
variable_details <- read.csv(variable_details, stringsAsFactors = FALSE, check.names = FALSE)
}
variables <- .load_metadata_df(variables, "variables")
variable_details <- .load_metadata_df(variable_details, "variable_details")

# ========== INTERNAL FILTERING (recodeflow pattern) ==========

# Filter variables for this var
var_row <- variables[variables$variable == var, ]
var_row <- variables[variables$variable == var, , drop = FALSE]

if (nrow(var_row) == 0) {
warning(paste0("Variable '", var, "' not found in variables metadata"))
return(NULL)
stop("Variable '", var, "' not found in variables metadata", call. = FALSE)
}

# Take first row if multiple matches
if (nrow(var_row) > 1) {
warning("Multiple rows found for '", var, "' in variables metadata (",
nrow(var_row), " rows); using the first row.",
call. = FALSE)
var_row <- var_row[1, ]
}

Expand All @@ -169,15 +166,18 @@ create_cat_var <- function(var,
databaseStart,
allow_empty = TRUE
)),
,
drop = FALSE
]
} else {
# Fallback: no databaseStart filtering (for simple configs)
details_subset <- variable_details[variable_details$variable == var, ]
details_subset <- variable_details[variable_details$variable == var, , drop = FALSE]
}

# ========== CHECK IF VARIABLE ALREADY EXISTS ==========

if (!is.null(df_mock) && var %in% names(df_mock)) {
message("Variable '", var, "' already exists in df_mock; skipping generation.")
return(NULL)
}

Expand All @@ -192,7 +192,7 @@ create_cat_var <- function(var,
"No variable_details rows found for variable '", var,
"' and databaseStart '", databaseStart,
"'. Using fallback categories c('1', '2')."
))
), call. = FALSE)
# Generate simple 2-category variable with uniform distribution
values <- sample(c("1", "2"), size = n, replace = TRUE)
# Fallback still honors rType so output contracts match configured metadata.
Expand Down Expand Up @@ -223,7 +223,7 @@ create_cat_var <- function(var,

# Check if we have valid categories
if (length(props$categories) == 0) {
warning(paste0("No valid categories found for ", var))
warning(paste0("No valid categories found for ", var), call. = FALSE)
return(NULL)
}

Expand Down
Loading
Loading