validatetools
is a utility package for managing validation rule sets that are defined with validate
. In production systems validation rule sets tend to grow organically and accumulate redundant or (partially) contradictory rules. validatetools
helps to identify problems with large rule sets and includes simplification methods for resolving issues.
validatetools
is available from CRAN and can be installed with
install.packages("validatetools")
The adventurous can install an (unstable) development version of validatetools
from github with:
# install.packages("devtools")
devtools::install_github("data-cleaning/validatetools")
or use
install.packages('validatetools', repos = c('https://data-cleaning.r-universe.dev', 'https://cloud.r-project.org'))
rules <- validator( x > 0)
is_infeasible(rules)
#> [1] FALSE
rules <- validator(
rule1 = x > 0,
rule2 = x < 0
)
is_infeasible(rules)
#> [1] TRUE
detect_infeasible_rules(rules, verbose=TRUE)
#> Found:
#> rule1: x > 0
#> [1] "rule1"
# find out the conflict with this rule
is_contradicted_by(rules, "rule1", verbose=TRUE)
#> Rule(s):
#> rule1: x > 0
#> contradicted by:
#> rule2: x < 0
#> [1] "rule2"
# we prefer to keep rule1, so we can give rule1 Inf weight
detect_infeasible_rules(
rules,
weight=c(rule1 = Inf),
verbose=TRUE
)
#> Found:
#> rule2: x < 0
#> [1] "rule2"
make_feasible(rules, weight=c(rule1=Inf), verbose=TRUE)
#> Found:
#> rule2: x < 0
#> Dropping rule(s): "rule2"
#> Object of class 'validator' with 1 elements:
#> rule1: x > 0
#> Rules are evaluated using locally defined options
rules <- validator(
rule1 = if (income > 0) job == "yes",
rule2 = if (job == "yes") income == 0
)
is_infeasible(rules, verbose=TRUE)
#> The rule set is feasible,
#> but may contain contradictions in conditional if-rules.
#> use `detect_contradicting_if_rules()` to find out whether there are
#> contradictions in the if-clauses.
#> [1] FALSE
conflicts <- detect_contradicting_if_rules(rules, verbose=TRUE)
#> 1 contradiction(s) with if clauses found:
#> When income > 0:
#> rule2: if (job == "yes") income == 0
#> rule1: if (income > 0) job == "yes"
print(conflicts)
#> $`income > 0`
#> [1] "rule2" "rule1"
The function simplify_rules
combines most simplification methods of validatetools
to simplify a rule set. For example, it reduces the following rule set to a simpler form:
rules <- validator(
rule1 = if (age < 16) income == 0,
rule2 = job %in% c("yes", "no"),
rule3 = if (job == "yes") income > 0
)
simplify_rules(rules, age = 13)
#> Object of class 'validator' with 3 elements:
#> .const_income: income == 0
#> .const_age : age == 13
#> .const_job : job == "no"
#or
simplify_rules(rules, job = "yes")
#> Object of class 'validator' with 3 elements:
#> rule1 : age >= 16
#> rule3 : income > 0
#> .const_job: job == "yes"
simplify_rules
combines the following simplification and substitution methods:
rules <- validator(
rule1 = height > 4,
rule2 = height <= max_height,
rule3 = if (gender == "male") weight > 100,
rule4 = gender %in% c("male", "female")
)
substitute_values(rules, max_height = 6, gender = "male")
#> Object of class 'validator' with 5 elements:
#> rule1 : height > 4
#> rule2 : height <= 6
#> rule3 : weight > 100
#> .const_max_height: max_height == 6
#> .const_gender : gender == "male"
rules <- validator(
rule1 = x >= 0,
rule2 = x <=0
)
detect_fixed_variables(rules)
#> $x
#> [1] 0
simplify_fixed_variables(rules)
#> Object of class 'validator' with 1 elements:
#> .const_x: x == 0
rules <- validator(
rule1 = x1 + x2 + x3 == 0,
rule2 = x1 + x2 >= 0,
rule3 = x3 >=0
)
simplify_fixed_variables(rules)
#> Object of class 'validator' with 3 elements:
#> rule1 : x1 + x2 + 0 == 0
#> rule2 : x1 + x2 >= 0
#> .const_x3: x3 == 0
# superfluous conditions
rules <- validator(
r1 = if (age > 18) age <= 67,
r2 = if (income > 0 && income > 1000) job == TRUE
)
# implies that age always is <= 67
simplify_conditional(rules)
#> Object of class 'validator' with 2 elements:
#> r1: age <= 67
#> r2: income <= 1000 | (job == TRUE)
# non-relaxing clause
rules <- validator(
r1 = if (income > 0) age >= 16,
r2 = age < 12
)
# age > 16 is always FALSE so r1 can be simplified
simplify_conditional(rules)
#> Object of class 'validator' with 2 elements:
#> r1: income <= 0
#> r2: age < 12
# non-constraining clause
rules <- validator(
rule1 = if (age < 16) income == 0,
rule2 = if (age >=16) income >= 0
)
simplify_conditional(rules)
#> Object of class 'validator' with 2 elements:
#> rule1: age >= 16 | (income == 0)
#> rule2: income >= 0
rules <- validator(
rule1 = age > 12,
rule2 = age > 18
)
# rule1 is superfluous
remove_redundancy(rules, verbose=TRUE)
#> Removed redundant rule(s):
#> rule1: age > 12
#> Object of class 'validator' with 1 elements:
#> rule2: age > 18
rules <- validator(
rule1 = age > 12,
rule2 = age > 12
)
# standout: rule1 and rule2, first rule wins
remove_redundancy(rules, verbose=TRUE)
#> Removed redundant rule(s):
#> rule2: age > 12
#> Object of class 'validator' with 1 elements:
#> rule1: age > 12
# Note that detection signifies both rules!
detect_redundancy(rules, verbose=TRUE)
#> Redundant rule(s):
#> rule1: age > 12
#> rule2: age > 12
#> rule1 rule2
#> TRUE TRUE