The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
vimpute(method = "restricted") imputes numeric variables
with a least-squares model whose predictions are constrained by edit
rules. The rules are written with validate::validator() and
solved with ECOSolveR.
This is useful when the completed data must satisfy accounting identities, bounded totals, or row-wise consistency rules.
The smallest useful example is a lower bound. Here the observed data
suggest a line through (x, y), but the missing value is
constrained to be at least 4. The imputation indicator
y_imp marks the cell filled by vimpute().
library(VIM)
small_data <- data.frame(
y = c(1, 2, NA),
x = c(1, 2, 3)
)
rules <- validate::validator(y >= 4)
small_imp <- vimpute(
small_data,
method = list(y = "restricted"),
pmm = FALSE,
sequential = FALSE,
learner_params = list(
restricted = list(
rules = rules,
save_optimization_problem = TRUE
)
)
)
small_imp
#> y x y_imp
#> 1 1 1 FALSE
#> 2 2 2 FALSE
#> 3 4 3 TRUEThe completed value satisfies the rule, and the saved optimization
problem can be inspected when
save_optimization_problem = TRUE.
Restricted regression is most natural when variables have row-wise
lower and upper bounds. In this example amount is imputed
while every completed value must remain between its corresponding
lower and upper columns.
set.seed(42)
n <- 100L
bounded_data <- data.frame(
x1 = stats::runif(n, 0, 10),
x2 = stats::runif(n, -2, 2)
)
bounded_data$amount <- 20 + 1.5 * bounded_data$x1 - 0.75 * bounded_data$x2
bounded_data$lower <- bounded_data$amount - 0.5
bounded_data$upper <- bounded_data$amount + 0.5
bounded_data <- bounded_data[, c("amount", "x1", "x2", "lower", "upper")]
missing_idx <- sample.int(n, 20L)
bounded_missing <- bounded_data
bounded_missing$amount[missing_idx] <- NA_real_
bounded_rules <- validate::validator(
amount >= lower,
amount <= upper,
amount >= 0,
lower <= upper
)
bounded_imp <- vimpute(
bounded_missing,
method = list(amount = "restricted"),
pmm = FALSE,
sequential = FALSE,
learner_params = list(amount = list(rules = bounded_rules))
)
head(bounded_imp[bounded_imp$amount_imp, c("amount", "lower", "upper")])
#> amount lower upper
#> 5 28.29882 27.79882 28.79882
#> 10 32.06915 31.56915 32.56915
#> 14 23.97324 23.47324 24.47324
#> 24 33.93099 33.43099 34.43099
#> 25 20.57759 20.07759 21.07759
#> 27 24.59544 24.09544 25.09544completed <- as.data.frame(bounded_imp)[, names(bounded_data), drop = FALSE]
sum(bounded_imp$amount_imp)
#> [1] 20
all(validate::values(validate::confront(completed, bounded_rules)))
#> [1] TRUERules can also involve categorical predictors and conditional logic. The restricted learner uses the edit rules only to constrain the imputed target; the remaining columns can be numeric or categorical predictors.
categorical_data <- bounded_missing
categorical_data$c1 <- factor(rep(c("A", "B", "C"), length.out = n))
categorical_data$c2 <- factor(rep(c("B", "C", "A"), length.out = n))
categorical_data$c3 <- factor(rep(c("C", "A", "B"), length.out = n))
conditional_rules <- validate::validator(
amount >= lower,
amount <= upper,
amount >= 0,
lower <= upper,
(c1 == "A") + (c2 == "A") + (c3 == "A") <= 2,
if (c1 == "A") amount >= 2
)
categorical_imp <- vimpute(
categorical_data,
method = list(amount = "restricted"),
pmm = FALSE,
sequential = FALSE,
learner_params = list(amount = list(rules = conditional_rules))
)
all(validate::values(validate::confront(categorical_imp, conditional_rules)))
#> [1] TRUEformula can be used with
method = "restricted" to choose the predictors in the
regression model while the edit rules still constrain the final
predictions. The left-hand side must be the untransformed target
variable.
n_formula <- 80L
formula_data <- data.frame(
y = 10 + 2 * seq_len(n_formula),
x_signal = seq_len(n_formula),
x_noise = 10 + 2 * seq_len(n_formula),
lower = 0,
upper = 200
)
formula_missing_idx <- c(12L, 24L, 36L)
expected_y <- formula_data$y[formula_missing_idx]
formula_data$y[formula_missing_idx] <- NA_real_
formula_data$x_noise[formula_missing_idx] <- -1000
formula_rules <- validate::validator(y >= lower, y <= upper, lower <= upper)
formula_imp <- vimpute(
formula_data,
method = list(y = "restricted"),
formula = list(y = y ~ x_signal),
pmm = FALSE,
sequential = FALSE,
learner_params = list(y = list(rules = formula_rules))
)
formula_imp[formula_missing_idx, c("y", "x_signal", "x_noise", "y_imp")]
#> y x_signal x_noise y_imp
#> 12 34 12 -1000 TRUE
#> 24 58 24 -1000 TRUE
#> 36 82 36 -1000 TRUE
max(abs(formula_imp$y[formula_missing_idx] - expected_y))
#> [1] 0Set robust = TRUE to use a Huber loss instead of
ordinary least squares. This is helpful when the observed data contain
influential outliers but the final imputed values must still obey the
edit rules.
robust_rules <- validate::validator(y >= 0)
outlier_data <- data.frame(
x = 0:21,
y = 1 + 2 * (0:21)
)
outlier_data$y[21L] <- 500
outlier_data$y[22L] <- NA_real_
ordinary_imp <- vimpute(
outlier_data,
method = list(y = "restricted"),
formula = list(y = y ~ x),
pmm = FALSE,
sequential = FALSE,
learner_params = list(y = list(rules = robust_rules))
)
robust_imp <- vimpute(
outlier_data,
method = list(y = "restricted"),
formula = list(y = y ~ x),
pmm = FALSE,
sequential = FALSE,
learner_params = list(y = list(
rules = robust_rules,
robust = TRUE,
huber_k = 1.345
))
)
c(
ordinary = ordinary_imp$y[22L],
robust = robust_imp$y[22L],
expected_without_outlier = 43
)
#> ordinary robust expected_without_outlier
#> 130 57 43The package includes lse_synthetic and
lse_synthetic_rules, a compact synthetic
business-statistics example with many linear edit rules. The rules ship
as plain data frames of rule text (so the data set does not depend on
validate); validate::validator(.data = ) turns
a rule set into a validator. The test suite uses this data at larger
scale; the vignette uses a smaller slice so it can be knitted
quickly.
data("lse_synthetic", package = "VIM")
data("lse_synthetic_rules", package = "VIM")
lse_rules <- validate::validator(.data = lse_synthetic_rules$edit)
numeric_cols <- c(
"persons_employed",
"employees_paid",
"self_employed",
"employees_male",
"employees_female",
"employees_blue_collar",
"employees_white_collar",
"apprentices",
"marginal_employees",
"turnover_total",
"turnover_domestic",
"turnover_exports",
"e_commerce_turnover",
"material_costs",
"purchased_services",
"rents_leasing",
"other_operating_expense",
"intermediate_consumption",
"gross_value_added",
"personnel_costs",
"wages_salaries",
"social_security_costs",
"other_personnel_costs",
"gross_operating_surplus",
"investments_tangible",
"investment_machinery",
"investment_buildings",
"investment_software"
)
edit_cols <- c(
"reporting_year",
"onace_section",
"onace_group",
"nuts2",
"data_source",
"survey_mode",
"employment_size_class",
"turnover_size_class",
numeric_cols
)
lse_complete <- lse_synthetic[seq_len(80L), edit_cols]
missing_map <- list(
turnover_total = c(2L, 15L),
intermediate_consumption = c(13L, 18L),
gross_value_added = c(24L, 31L),
personnel_costs = c(39L, 45L),
investments_tangible = c(52L, 60L)
)
lse_missing <- lse_complete
for (var in names(missing_map)) {
lse_missing[missing_map[[var]], var] <- NA_real_
}
restricted_formulas <- setNames(
lapply(names(missing_map), function(var) {
stats::reformulate(setdiff(numeric_cols, var), response = var)
}),
names(missing_map)
)
lse_imp <- vimpute(
lse_missing,
method = "restricted",
formula = restricted_formulas,
pmm = FALSE,
sequential = FALSE,
learner_params = list(
restricted = list(
rules = lse_rules,
save_optimization_problem = TRUE
)
)
)
lse_completed <- as.data.frame(lse_imp)[, edit_cols, drop = FALSE]
sum(as.data.frame(lse_imp)[paste0(names(missing_map), "_imp")])
#> [1] 10
all(validate::values(validate::confront(lse_completed, lse_rules)))
#> [1] TRUE
sort(names(attr(lse_imp, "restricted_optimization_problems")))
#> [1] "gross_value_added" "intermediate_consumption"
#> [3] "investments_tangible" "personnel_costs"
#> [5] "turnover_total"The same pattern scales to many variables: define a
validate::validator object for the edits, provide one
formula per imputed target when you need predictor control, and pass the
rules through learner_params for either the method name
(restricted) or the individual variables.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.