ks_get_data(): the kstools.get_data.case = "upper" disambiguation
strategy was broken on case-sensitive filesystems (Linux/macOS
case-sensitive volumes). It reconstructed the target as
STEM.EXT with the extension uppercased (DM.RDS), so a real
DM.rds file was never matched and resolution aborted. The lower
and upper strategies now compare the matched file's STEM
(file_path_sans_ext) against the domain in the requested case and
treat the extension case-insensitively, consistent with the rest of
the resolver. This only surfaced on case-sensitive systems because the
unit test (which needs both dm.rds and DM.rds in one directory)
self-skips on case-insensitive filesystems like APFS.ks_write_xpt(): non-finite numeric values (Inf, -Inf) were silently
written as 0. They are now mapped to SAS missing (.) like NA_real_
and NaN (round-1 A2).ks_write_xpt(): SAS XPORT interoperability limits now abort instead of
silently truncating: variable names must be <= 8 bytes (v5) / <= 32 bytes
(v8), column labels <= 40 / 256 bytes, and character data must fit
200 bytes at version 5 (SAS XPORT engine limit, Tech Note 1528) and 32767
bytes at version 8. Case-insensitive name collisions are rejected (round-1 A4).ks_write_xpt(): a failed write no longer leaves a partial .xpt file
behind (round-1 A9).LENGTH(); embedded
NUL bytes no longer truncate the stored value silently (round-1 A10).ks_read_xpt() (internal parser): files declaring a numeric variable with
a storage width other than 8 bytes are now rejected with a clear error
instead of silently decoding shifted garbage (round-1 A8).ks_impute_dtc(): out-of-range components (month 13, day beyond month
length incl. leap years, hour 24-25, minute/second > 59) were silently
rolled over by make_datetime() (2020-13-01 -> 2021-01-01). Such
elements now follow the documented non-matching contract: NA value and
NA imputation flag, never imputed from ref (round-1 A3).ks_auc(): groups with fewer than two concurrent non-NA (value, time)
points now return NA_real_ instead of a misleading 0 (round-1 A5).ks_batch_load(): if any file fails to load, the function aborts and
reports the offending paths (previously try-error objects were kept in
the returned list and persisted by save.path/keep) (round-1 A6).ks_roc(): non-numeric predictor columns are rejected with a clear
message instead of a cryptic internal type error (round-1 A7).ks_n_pct(): numeric method warns on fractional counts and computes
count and percentage from the same truncated integer (the data-frame
method already behaved this way) (round-1 A10).ks_supp_merge(): duplicate (key, QNAM) records (which pivot_wider()
would silently turn into list-columns) and QNAM values colliding with
primary-domain variable names now abort with SDTM-oriented messages
(round-1 A10).ks_mianalyze(): SAS PROC MIANALYZE parity for a zero within-imputation
variance: a warning is issued per zero standard error, and when the pooled within
variance is 0 the degrees of freedom, MI diagnostics (r, FMI, RE),
confidence limits and t-test are NA - previously a degenerate finite
df = m - 1 (and an NA-producing qt() call with EDF=) was reported.
SAS prints "." for these values with or without EDF=; estimate, pooled
SE, B, T and the ranges remain reported (round-1 A1, closed).ks_compare_na(): NA-aware element-wise comparison (SAS NULL= twin).ks_vswitch(): vectorized switch over named cases (SAS select-when map),
with .na / .default fallbacks.ks_hosmer_lemeshow(): Hosmer-Lemeshow goodness-of-fit for logistic models.ks_dunnett_crit(): exact Dunnett critical value by inverting the
equicorrelated multivariate-t (mnormt, Suggests-guarded).ks_mv_normality(): Mardia skewness/kurtosis in base R (agrees with
QuantPsyc::mult.norm) + optional energy test (energy, Suggests).ks_boot_ci(): one-line percentile/BCa bootstrap CI over boot.ks_be_2x2(): 2x2 crossover bioequivalence via nlme::lme with
contr.SAS (T-R on the log scale, 90% CI, ratio interval).ks_swoe(): vector API — call as ks_swoe(x, y) (e.g. inside
dplyr::mutate(SEX_SWOE = ks_swoe(SEX, RESPONSE))). The previous
data-frame + NSE / quoted interface is removed.mianalyze vignette for dark
RStudio themes.ks_check_rules(), ks_collapse_check_msgs(), ks_compare_check_state(), ks_save2xlsx_by().mianalyze: Rubin's rules, Barnard-Rubin edf, and
numerical comparison with SAS PROC MIANALYZE (SAS/STAT 12.3 Examples
58.1 / 58.3 and Table 58.2).tests/testthat/test-ks_mianalyze-sas.R.inst/examples for ks_mianalyze() / mianalyze() with SAS
Example 58.1 inputs.ks_mianalyze(): added summary.ks_mianalyze() method that returns
structured data frames (variance, diagnostics, estimates, tests)
for programmatic access to results.ks_mianalyze(): added knit_print.ks_mianalyze() method for proper
rendering in R Markdown and Quarto documents using knitr::kable().ks_mianalyze(): refactored print.ks_mianalyze() to use summary()
internally, eliminating code duplication and following R best practices.ks_cv(): returns NA_real_ instead of Inf/NaN when the mean of x
is zero or NA.ks_z_score(): returns an all-NA vector instead of NaN when the
standard deviation of x is zero (constant input).ks_n_pct() (numeric and data.frame methods): cells whose denominator
(totals) is zero or NA are now replaced with na.rep rather than
silently producing "Inf%" in the formatted string.ks_get_numdec(): groups where every value of var is NA now yield
NA_integer_ instead of -Inf (double), preserving the integer column
type of NUMDEC.ks_batch_load() (type "xpt"): now calls ks_read_xpt() instead of
haven::read_xpt(), restoring date conversion and SAS-attribute handling
that were bypassed in the previous implementation.ks_williams(): the treatment-name whitespace check used a
double-escaped regex ("\\\\s") that matched a literal \s instead of
actual whitespace. Fixed to "\\s".ks_newcombe_hs() and ks_eairdiff_mn(): replaced Vectorize() wrappers
with mapply() + do.call(rbind, ...), giving a material speedup on
long input vectors.ks_impute_dtc(): date-only strings ("YYYY-MM-DD") with
direction = "start" now produce 00:00:00 with imputation_flag = "H"
instead of 00:00:00 / NA. Consistent with the direction = "end" (Case D)
behaviour added in v0.5.3. A ref-date match (Case C) still takes
priority over this start-of-day fallback (Case E).ks_impute_dtc(): date-only strings ("YYYY-MM-DD") with
direction = "end" now produce 23:59:00 with imputation_flag = "H"
instead of 00:00:00 / NA. A ref-date match (Case C) still takes
priority over this end-of-day fallback (Case D).ks_impute_dtc(): new function that parses truncated ISO 8601 date/datetime
strings and imputes missing components. Accepts direction = "start",
"end", or "no" and an optional ref reference date/datetime vector for
same-period guided imputation (Cases A / B / C). Returns a
tibble(dt = POSIXct, imputation_flag = chr) for direct use with
dplyr::bind_cols().rcpp_xpt.cpp: replaced <stdexcept> with <exception> (correct header for std::exception).ks_read_xpt() / ks_write_xpt(): completely rewritten C++ engine.
Uses POSIX mmap for zero-copy file access, streaming columnar decode
directly into pre-allocated R vectors, and a multi-threaded worker pool
for numeric columns (n_threads argument, defaults to hw - 1).
A 5 GB XPT file now reads in ~4 s at ~1.3 GB/s with parser scratch
well below 4 MB (previously > 100 GB RSS). Writer uses a 64 KB output
buffer with no in-memory row materialisation. Multi-member files are
handled via in-place mmap offsets (no temp files).ks_get_data(): added xpt_reader argument (and global option
kstools.xpt.reader) to select the XPT loading back-end.
Use xpt_reader = "internal" (or options(kstools.xpt.reader = "internal"))
to suppress the "ignored N trailing byte(s) after row parsing" warning
emitted by haven on some XPORT files. "haven" remains the default.
When the internal reader is used and the file contains multiple members,
the domain slot in the output is a named list of tibbles.ks_read_xpt(): read SAS XPORT V5/V8/V9 files into tibbles. Variable labels,
SAS formats, and informats are preserved as column attributes ("label",
"format.sas", "sas.informat"). Multi-member files are returned as a named
list of tibbles.ks_write_xpt(): write a data frame to a SAS XPORT V5 or V8 file. Labels and
formats can be supplied via column attributes or explicit labels/formats
arguments.ks_xpt_labels() / ks_xpt_formats(): extract variable labels and SAS
formats from a data frame as named character vectors.ks_set_labels() / ks_set_formats(): set "label" / "format.sas"
attributes on selected columns from named character vectors.convert_dates = FALSE.ks_get_data(): no longer assigns loaded data to .GlobalEnv. It now
returns the data directly — a tibble for a single-domain call, or a
named list of tibbles for multi-domain calls. Migrate
ks_get_data(dm) to dm <- ks_get_data(dm) and
ks_get_data(dm, ae) to dat <- ks_get_data(dm, ae); dm <- dat$dm.ks_get_data(): file lookup against path is now case-insensitive
for both stem and extension, so ks_get_data(dm) and ks_get_data(DM)
both match dm.xpt, DM.XPT, Dm.Xpt, etc.ks_get_data(): new global option kstools.get_data.case to resolve
ambiguity when multiple files share the same stem but differ in case.
Accepted values: "error" (default), "first", "lower", "upper".ks_mianalyze() / mianalyze(): aligned degrees-of-freedom handling with SAS PROC MIANALYZE behavior by adding edf (SAS EDF= analogue) and applying Barnard-Rubin small-sample adjustment when finite complete-data df are supplied.ks_mianalyze() / mianalyze(): kept backward compatibility for v0 while adding input validation for conflicting edf/v0 specifications.edf argument and SAS-style DF behavior.ks_mianalyze(): corrected pooled total-variance and Rubin degrees-of-freedom calculations used for multiple-imputation inference._pkgdown.yml) and generated docs site workflow.getting-started and clinical-workflows.inst/examples coverage and added run_all_examples.R index runner.DESCRIPTION.ks_mianalyze() / mianalyze() (Rubin pooling; SAS MIANALYZE analogue).ks_roc() (correlated ROC AUC comparison with contrast tests).ks_williams() (randomized Williams cross-over design and schedule generator).ks_collapse_check_msgs(): removed unqualified %>% dependency so function and vignettes run without attaching dplyr.ks_save2xlsx_by(): replaced O(N) per-row wb_add_fill() loops with Excel
Conditional Formatting rules (wb_add_dxfs_style() +
wb_add_conditional_formatting()). Fill calls now scale O(1) per colour
key per sheet regardless of row count — e.g. 3,000 wb_add_fill() calls
for a 1,000-row / 3-colour sheet become 3 CF rules.ks_summary() test: fixed case-insensitive regexp match for the
"Duplicate names" error so the test no longer fails against the
capitalised message.ks_z_score(): fixed inverted formula — was returning (mean - x) / sd
instead of the correct (x - mean) / sd.read.csv3(): fixed potential crash on end-of-file when readLines() returns
character(0) — length() guard now precedes grepl() to avoid a
zero-length condition in if().ks_compare_check_state(): removed unused internal ..row_id.. column that
was allocated and immediately deleted on every call.ks_median_ci() (boot method): guard against NULL $bca component —
boot.ci() omits BCa for very small samples; bounds now fall back to
NA_real_ instead of silently dropping vector elements.ks_save2xlsx_by(): removed dead as_table parameter (implementation was
already removed in 0.3.3); narrowed status-column fill to the status column
only (was incorrectly colouring the entire row); added freeze-pane on the
header row; added thin cell borders on all data cells.ks_cv(), ks_z_score(): replaced bare T with TRUE.ks_del_tmp(): simplified redundant single-character class [t] to t.labelled import; is_missing is now correctly sourced from ksformat.ks_check_rules(), ks_collapse_check_msgs(), ks_compare_check_state(), and ks_save2xlsx_by().ks_get_labels() and ks_code_to_fmt().ksformat, labelled, openxlsx2, purrr, stringr, and tibble.read.csv3(): read a CSV file with optional KeyStat comment headers (#NAME$type(length)$Label) for column types and labels.ks_get_data(): new type values "csv" and "csvks" for loading plain CSV and KeyStat-commented CSV domains; .csv is also auto-detected.ks_summary(): new numdec = TRUE parameter appends a NUMDEC column with the group-level max decimal places of var (via ks_get_numdec logic).ks_current_filename(): detect the basename of the running R script (source or Rscript).ks_get_numdec(): compute max decimal places per group and join as NUMDEC.R/get_numeric.r renamed to R/get_numdec.r for consistency with the exported function name.ks_summary() accepts optional extras: a named list of functions (f(x, na.rm)) for extra per-group statistics; columns appear after the default summary stats and before cv.ks_simulate_ar1(): validation, reproducibility (seed), MASS:: / stats:: usage, documentation, examples, and tests.ks_geom_mean(), ks_median_ci(), ks_n_pct(), ks_get_data(), ks_swap_cols(), ks_swoe(), and related helpers: refactors, bug fixes, and documentation updates (see git history).ks_newcombe_hs(), ks_newcombe_hs_strata().ks_trim in favour of base::trimws(); removed medical-history helpers from earlier versions.?kstools) updated; broken cross-references in the package index fixed.Initial packaged release.