Skip to content

Commit dd1fc66

Browse files
authored
v0.9.1 (#34)
* v0.9.1
1 parent b0cc6ac commit dd1fc66

15 files changed

Lines changed: 1147 additions & 192 deletions

.github/workflows/R-CMD-check.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ jobs:
2424
config:
2525
- {os: macOS-latest, r: 'release'}
2626
- {os: windows-latest, r: 'release'}
27-
# Use 3.6 to trigger usage of RTools35
2827
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
2928

3029
env:

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ Meta
88
^\revdep$
99
/doc/
1010
/Meta/
11+
.DS_Store
12+
Rplots.pdf
13+
*.Rcheck/
14+
inst/doc/

DESCRIPTION

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Package: miceFast
22
Title: Fast Imputations Using 'Rcpp' and 'Armadillo'
3-
Version: 0.9.0
3+
Version: 0.9.1
44
Authors@R: person("Maciej", "Nasinski", email = "nasinski.maciej@gmail.com", role = c("aut", "cre"))
55
Description:
66
Fast imputations under the object-oriented programming paradigm.
77
Moreover there are offered a few functions built to work with popular R packages such as 'data.table' or 'dplyr'.
8-
The biggest improvement in time performance could be achieve for a calculation where a grouping variable have to be used.
8+
The biggest improvement in time performance can be achieved for a calculation where a grouping variable is used.
99
A single evaluation of a quantitative model for the multiple imputations is another major enhancement.
1010
A new major improvement is one of the fastest predictive mean matching in the R world because of presorting and binary search.
1111
Depends: R (>= 3.6.0)
@@ -20,7 +20,6 @@ Imports:
2020
Suggests:
2121
knitr,
2222
rmarkdown,
23-
pacman,
2423
testthat,
2524
mice,
2625
magrittr,

NEWS.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
# miceFast 0.9.1
2+
3+
## Bug fixes
4+
5+
* PMM returned predicted values instead of observed values (C++): The `pmm` model returned predicted $\hat{y}$ for missing rows instead of the nearest observed $y$ values. Now it follows Little and Rubin (2002).
6+
* PMM with character/factor variables (R): `fill_NA_N()` with `model = "pmm"` and a character dependent variable failed because it attempted `as.numeric()` on non-numeric strings, producing all NAs.
7+
* Character dependent variable with lm models: `fill_NA()` and `fill_NA_N()` with `model = "lm_pred"`, `"lm_bayes"`, or `"lm_noise"` silently returned all NAs when the dependent variable was character with non-numeric labels (e.g., `"apple"`, `"banana"`).
8+
9+
## Documentation
10+
11+
* README: added sequential-chain MI examples (dplyr and data.table) showing how to impute multiple variables and pool with Rubin's rules.
12+
* Introduction vignette: added full imputation workflow with sequential ordering (impute variables whose predictors are complete first), FCS (chained equations) section with data.table example, and PMM note for the OOP interface.
13+
* MI vignette: expanded Rubin's rules derivations, added PMM MI example using the OOP interface, expanded "Important caveat" section with OOP and data.table FCS code snippets for non-monotone patterns.
14+
* Documented PMM as a proper MI method throughout vignettes and README.
15+
* Improved prose throughout vignettes and README.
16+
17+
## Tests
18+
19+
* Added 20 PMM-specific tests (`test-pmm.R`): observed-value returns, factor/character support, weighted PMM, grouped data.table, reproducibility, stochasticity.
20+
* Added 31 FCS tests (`test-fcs.R`): data.table, data.frame, and OOP FCS helpers; joint-missingness handling; MI+pool workflow; comparison with `mice` (pooled estimates and imputed means).
21+
* Added tests for character dependent variables with non-numeric labels across all models and data types.
22+
* Test suite expanded from 243 to 311 tests.
23+
124
# miceFast 0.9.0
225

326
Kota Hattori, thank you for your feedback and for motivating me for this deep update.

R/fill_NA.R

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,7 @@ fill_NA.data.frame <- function(
188188
f[f > length(l)] <- length(l)
189189
ff <- factor(l[f])
190190
} else if (is_character_y) {
191-
yy <- if (model != "lda") {
192-
factor(yy, levels = sort(as.numeric(unique(yy))))
193-
} else {
194-
factor(yy)
195-
}
191+
yy <- factor(yy)
196192
l <- levels(yy)
197193
yy <- as.numeric(yy)
198194
f <- round(fill_NA_(cbind(yy, xx), model, 1, 2:(ncol(xx) + 1), ww, ridge))
@@ -277,11 +273,7 @@ fill_NA.data.table <- function(
277273
f[f > length(l)] <- length(l)
278274
ff <- factor(l[f])
279275
} else if (is_character_y) {
280-
yy <- if (model != "lda") {
281-
factor(yy, levels = sort(as.numeric(unique(yy))))
282-
} else {
283-
factor(yy)
284-
}
276+
yy <- factor(yy)
285277
l <- levels(yy)
286278
yy <- as.numeric(yy)
287279
f <- round(fill_NA_(cbind(yy, xx), model, 1, 2:(ncol(xx) + 1), ww, ridge))

R/fill_NA_N.R

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
#' @return load imputations in a numeric/character/factor (similar to the input type) vector format
1818
#'
1919
#' @note
20-
#' There is assumed that users add the intercept by their own.
21-
#' The miceFast module provides the most efficient environment, the second recommended option is to use data.table and the numeric matrix data type.
22-
#' The lda model is assessed only if there are more than 15 complete observations
23-
#' and for the lms models if number of independent variables is smaller than number of observations.
20+
#' It is assumed that users add the intercept column themselves.
21+
#' The miceFast module provides the most efficient environment; the second recommended option is data.table with a numeric matrix.
22+
#' Only \code{"lm_bayes"}, \code{"lm_noise"}, and \code{"pmm"} models are supported.
23+
#' The model is fitted only when the number of complete observations exceeds the number of independent variables.
2424
#'
2525
#' @seealso \code{\link{fill_NA}} \code{\link{VIF}} \code{vignette("miceFast-intro", package = "miceFast")}
2626
#'
@@ -187,11 +187,7 @@ fill_NA_N.data.frame <- function(
187187
f[f > length(l)] <- length(l)
188188
ff <- factor(l[f])
189189
} else if (is_character_y) {
190-
yy <- if (model != "lda") {
191-
factor(yy, levels = sort(as.numeric(unique(yy))))
192-
} else {
193-
factor(yy)
194-
}
190+
yy <- factor(yy)
195191
l <- levels(yy)
196192
yy <- as.numeric(yy)
197193
f <- round(fill_NA_N_(
@@ -295,11 +291,7 @@ fill_NA_N.data.table <- function(
295291
f[f > length(l)] <- length(l)
296292
ff <- factor(l[f])
297293
} else if (is_character_y) {
298-
yy <- if (model != "lda") {
299-
factor(yy, levels = sort(as.numeric(unique(yy))))
300-
} else {
301-
factor(yy)
302-
}
294+
yy <- factor(yy)
303295
l <- levels(yy)
304296
yy <- as.numeric(yy)
305297
f <- round(fill_NA_N_(

README.md

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,23 @@ For performance details, see `performance_validity.R` in the `extdata` folder.
2626
- [Introduction and Advanced Usage](https://polkas.github.io/miceFast/articles/miceFast-intro.html)
2727
- [Missing Data Mechanisms and Multiple Imputation](https://polkas.github.io/miceFast/articles/missing-data-and-imputation.html)
2828

29+
30+
## Practical Advice
31+
32+
- **Only need a filled-in dataset for exploration or ML?** A single imputation with `fill_NA()` or averaging draws with `fill_NA_N()` is fast and convenient. For any inferential statement use full MI with `pool()`.
33+
- **Little missing data + MCAR?** Consider using `complete.cases()`. Listwise deletion is unbiased under MCAR and may be sufficient when the fraction of incomplete rows is small.
34+
- **For publication**, always run a **sensitivity analysis**: compare MI results against base methods (`complete.cases()`, mean imputation) and across different imputation models (`lm_bayes`, `lm_noise`, `pmm`). Vary the number of imputations. If conclusions change, investigate why. Report the imputation model, *m*, and any assumptions about the missing-data mechanism.
35+
- See the [MI vignette](https://polkas.github.io/miceFast/articles/missing-data-and-imputation.html) for details on MCAR/MAR/MNAR mechanisms and a practical checklist.
36+
2937
## Multiple Imputation Workflow
3038

31-
[mice](https://cran.r-project.org/package=mice) implements the full MI pipeline (impute, analyze, pool). **miceFast** focuses on the computationally expensive partfitting the imputation models — and is typically **~10× faster** than mice for the imputation step alone (see [benchmarks](#performance-highlights)). Two usage modes:
39+
[mice](https://cran.r-project.org/package=mice) implements the full MI pipeline (impute, analyze, pool). **miceFast** focuses on the computationally expensive part: fitting the imputation models. It is typically **~10× faster** than mice for the imputation step alone (see [benchmarks](#performance-highlights)). Two usage modes:
3240

33-
1. **MI with Rubin's rules** — call `fill_NA()` with a stochastic model (`lm_bayes`, `lm_noise`, or `lda` with a random `ridge`) in a loop to create *m* completed datasets, then `pool()` the fitted models.
41+
1. **MI with Rubin's rules.** Call `fill_NA()` with a stochastic model in a loop to create *m* completed datasets, then `pool()` the fitted models. For continuous variables use `lm_bayes` (strictly **proper**; it draws from the posterior). For both continuous and categorical variables, `pmm` (Predictive Mean Matching) is also **proper**. It draws from the posterior and matches to observed values, preserving the data distribution. Use the OOP interface (`impute("pmm", ...)`) in a loop for MI with PMM. For categorical variables, `lda` with a random `ridge` is **approximate** (ad-hoc perturbation, not a posterior draw, but works well in practice). `lm_noise` is **improper** (no parameter uncertainty); useful for sensitivity checks. See the [MI vignette](https://polkas.github.io/miceFast/articles/missing-data-and-imputation.html).
3442

35-
2. **Single-dataset averaging**`fill_NA_N()` returns the mean of *k* draws per missing value. Handy for exploration, but not for Rubin's rules (between-imputation variance is lost).
43+
2. **Single-dataset imputation.** `fill_NA_N()` with `lm_bayes`/`lm_noise` returns the mean of *k* stochastic draws per missing value. With `pmm`, *k* is the number of nearest neighbours to sample from (no averaging). Handy for exploration, but not for Rubin's rules (between-imputation variance is lost).
44+
45+
3. **Iterative FCS (chained equations).** When multiple variables have interlocking (non-monotone) missingness, you can cycle through variables in a loop, restoring and re-imputing each one — the same algorithm mice uses. With a monotone pattern a single pass suffices and FCS is unnecessary. See the [Introduction vignette](https://polkas.github.io/miceFast/articles/miceFast-intro.html) for details.
3646

3747
See the [MI vignette](https://polkas.github.io/miceFast/articles/missing-data-and-imputation.html) for worked examples.
3848

@@ -56,36 +66,35 @@ devtools::install_github("polkas/miceFast")
5666
library(miceFast)
5767
library(dplyr)
5868

59-
set.seed(1234)
6069
data(air_miss)
6170

6271
# Visualize the NA structure
6372
upset_NA(air_miss, 6)
6473

65-
# Model-based single imputation
66-
air_miss %>%
67-
mutate(Ozone_imp = fill_NA(
68-
x = ., model = "lm_bayes",
69-
posit_y = "Ozone", posit_x = c("Solar.R", "Wind", "Temp")
70-
))
71-
72-
# Proper MI: impute m times, fit models, pool with Rubin's rules
73-
completed <- lapply(1:5, function(i) {
74-
air_miss %>%
75-
mutate(Ozone_imp = fill_NA(
76-
x = ., model = "lm_bayes",
77-
posit_y = "Ozone", posit_x = c("Solar.R", "Wind", "Temp")
78-
))
74+
# Select the 4 core variables for regression: Ozone ~ Solar.R + Wind + Temp
75+
# Ozone has 37 NAs, Solar.R has 7 NAs, Wind and Temp are complete.
76+
df <- air_miss[, c("Ozone", "Solar.R", "Wind", "Temp")]
77+
78+
# MI with Rubin's rules: impute m = 10 datasets, fit model, pool.
79+
# Impute Solar.R first (predictors fully observed), then Ozone
80+
# (can now use the freshly imputed Solar.R). This sequential order
81+
# resolves joint missingness in a single pass.
82+
set.seed(1234)
83+
completed <- lapply(1:10, function(i) {
84+
df %>%
85+
mutate(Solar.R = fill_NA(., "lm_bayes", "Solar.R", c("Wind", "Temp"))) %>%
86+
mutate(Ozone = fill_NA(., "lm_bayes", "Ozone", c("Solar.R", "Wind", "Temp")))
7987
})
80-
fits <- lapply(completed, function(d) lm(Ozone_imp ~ Wind + Temp, data = d))
88+
fits <- lapply(completed, function(d) lm(Ozone ~ Solar.R + Wind + Temp, data = d))
8189
pool(fits)
82-
#> Pooled results from 5 imputed datasets
90+
#> Pooled results from 10 imputed datasets
8391
#> Rubin's rules with Barnard-Rubin df adjustment
8492
#>
85-
#> term estimate std.error statistic df p.value
86-
#> (Intercept) -62.771 23.9022 -2.626 46.95 1.162e-02
87-
#> Wind -3.087 0.6857 -4.502 37.24 6.420e-05
88-
#> Temp 1.736 0.2498 6.951 58.54 3.400e-09
93+
#> term estimate std.error statistic df p.value
94+
#> (Intercept) -49.50313 21.74948 -2.276 78.41 2.557e-02
95+
#> Solar.R 0.05771 0.02294 2.516 72.83 1.407e-02
96+
#> Wind -3.44033 0.62721 -5.485 76.15 5.185e-07
97+
#> Temp 1.47603 0.23404 6.307 97.50 8.345e-09
8998
```
9099

91100
### data.table
@@ -94,27 +103,28 @@ pool(fits)
94103
library(miceFast)
95104
library(data.table)
96105

97-
set.seed(1234)
98106
data(air_miss)
99-
setDT(air_miss)
100-
101-
# Single imputation
102-
air_miss[, Ozone_imp := fill_NA(
103-
x = .SD, model = "lm_bayes",
104-
posit_y = "Ozone", posit_x = c("Solar.R", "Wind", "Temp")
105-
)]
106-
107-
# Grouped imputation — fits a separate model per group
108-
air_miss[, Solar_R_imp := fill_NA(
109-
x = .SD, model = "lm_bayes",
110-
posit_y = "Solar.R", posit_x = c("Wind", "Temp", "Intercept")
111-
), by = .(groups)]
107+
dt <- as.data.table(air_miss[, c("Ozone", "Solar.R", "Wind", "Temp")])
108+
109+
# MI with Rubin's rules: same sequential chain as above.
110+
set.seed(1234)
111+
completed <- lapply(1:10, function(i) {
112+
d <- copy(dt)
113+
d[, Solar.R := fill_NA(.SD, "lm_bayes", "Solar.R", c("Wind", "Temp"))]
114+
d[, Ozone := fill_NA(.SD, "lm_bayes", "Ozone", c("Solar.R", "Wind", "Temp"))]
115+
d
116+
})
117+
fits <- lapply(completed, function(d) lm(Ozone ~ Solar.R + Wind + Temp, data = d))
118+
pool(fits)
112119
```
113120

121+
For iterative FCS (chained equations) with non-monotone missingness,
122+
see the [Introduction vignette](https://polkas.github.io/miceFast/articles/miceFast-intro.html#iterative-fcs-chained-equations-with-micefast).
123+
114124
### Naive imputation (baseline only)
115125

116126
```r
117-
# Quick baseline — biased, does not account for relationships between variables
127+
# Quick baseline. Biased; does not account for relationships between variables.
118128
naive_fill_NA(air_miss)
119129
```
120130

@@ -127,7 +137,7 @@ See the [Introduction vignette](https://polkas.github.io/miceFast/articles/miceF
127137
- **Object-Oriented Interface** via `miceFast` objects (Rcpp modules).
128138
- **Convenient Helpers**:
129139
- `fill_NA()`: Single imputation (`lda`, `lm_pred`, `lm_bayes`, `lm_noise`).
130-
- `fill_NA_N()`: Averaged multiple imputations (mean of N draws) (`pmm`, `lm_bayes`, `lm_noise`).
140+
- `fill_NA_N()`: Multiple imputations. Averaged draws for `lm_bayes`/`lm_noise`; nearest-neighbour sampling for `pmm`.
131141
- `pool()`: Pool multiply imputed results using Rubin's rules.
132142
- `VIF()`: Variance Inflation Factor calculations.
133143
- `naive_fill_NA()`: Automatic naive imputations.
@@ -140,7 +150,7 @@ See the [Introduction vignette](https://polkas.github.io/miceFast/articles/miceF
140150
|-----------------|-----------------------------------------------------------------------------|
141151
| `new(miceFast)` | Creates an OOP instance with numerous imputation methods (see the vignette). |
142152
| `fill_NA()` | Single imputation: `lda`, `lm_pred`, `lm_bayes`, `lm_noise`. |
143-
| `fill_NA_N()` | Averaged multiple imputations (mean of N draws): `pmm`, `lm_bayes`, `lm_noise`. |
153+
| `fill_NA_N()` | `lm_bayes`/`lm_noise`: averages *k* draws. `pmm`: samples from *k* nearest observed values (works for both continuous and categorical). |
144154
| `pool()` | Pools estimates from *m* imputed datasets using Rubin's rules. Works with any model that has `coef()` and `vcov()`. |
145155
| `VIF()` | Computes Variance Inflation Factors. |
146156
| `naive_fill_NA()` | Performs automatic, naive imputations. |
@@ -149,15 +159,6 @@ See the [Introduction vignette](https://polkas.github.io/miceFast/articles/miceF
149159

150160
---
151161

152-
## Practical Advice
153-
154-
- **Only need a filled-in dataset for exploration or ML?** A single imputation with `fill_NA()` or averaging draws with `fill_NA_N()` is fast and convenient. For any inferential statement use full MI with `pool()`.
155-
- **Little missing data + MCAR?** Consider using `complete.cases()` — listwise deletion is unbiased under MCAR and may be sufficient when the fraction of incomplete rows is small.
156-
- **For publication**, always run a **sensitivity analysis**: compare MI results against base methods (`complete.cases()`, mean imputation) and across different imputation models (`lm_bayes`, `lm_noise`, `pmm`). Vary the number of imputations. If conclusions change, investigate why. Report the imputation model, *m*, and any assumptions about the missing-data mechanism.
157-
- See the [MI vignette](https://polkas.github.io/miceFast/articles/missing-data-and-imputation.html) for details on MCAR/MAR/MNAR mechanisms and a practical checklist.
158-
159-
---
160-
161162
## Performance Highlights
162163

163164
Median timings on 100k rows, 10 variables, 100 groups (R 4.4.3, macOS M3 Pro, [optimized BLAS/LAPACK](https://cran.r-project.org/bin/macosx/RMacOSX-FAQ.html#Which-BLAS-is-used-and-how-can-it-be-changed_003f)):

cran-comments.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
github actions:
44

5-
* {os: macOS-latest, r: 'release'}
6-
* {os: windows-latest, r: 'release'}
7-
* {os: windows-latest, r: '3.6'}
8-
* {os: ubuntu-18.04, r: 'devel', http-user-agent: 'release'}
9-
* {os: ubuntu-18.04, r: 'release'}
5+
- {os: macOS-latest, r: 'release'}
6+
- {os: windows-latest, r: 'release'}
7+
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
108

119
and:
1210

man/fill_NA_N.Rd

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)