///|
/// Confidence/lift are undefined (None), not NaN, when their denominators vanish.
pub struct RuleMetrics {
object_count : Int32
premise_count : Int32
conclusion_count : Int32
joint_count : Int32
counterexample_count : Int32
support : Double
confidence : Double?
lift : Double?
leverage : Double
} derive(Eq, Debug)
///|
pub extend RuleMetrics with Eq::{equal, not_equal}
///|
pub extend RuleMetrics with Debug::{to_repr}
///|
pub struct ScoredRule {
rule : Implication
metrics : RuleMetrics
} derive(Eq, Debug)
///|
pub extend ScoredRule with Eq::{equal, not_equal}
///|
pub extend ScoredRule with Debug::{to_repr}
///|
/// Evaluate A -> B after removing A from B; no statistical significance is inferred.
pub fn Context::rule_metrics(
self : Context,
rule : Implication,
) -> RuleMetrics raise FcaError {
let rule = check_rule(rule, self.attribute_count())
let n = self.object_count()
let a = self.extent(rule.premise).length()
let b = self.extent(rule.conclusion).length()
let joint = self.extent(set_union(rule.premise, rule.conclusion)).length()
let support = if n == 0 { 0.0 } else { joint.to_double() / n.to_double() }
let confidence = if a == 0 {
None
} else {
Some(joint.to_double() / a.to_double())
}
let lift = if a == 0 || b == 0 || n == 0 {
None
} else {
Some(joint.to_double() * n.to_double() / (a.to_double() * b.to_double()))
}
let leverage = if n == 0 {
0.0
} else {
support - a.to_double() * b.to_double() / (n.to_double() * n.to_double())
}
{
object_count: n,
premise_count: a,
conclusion_count: b,
joint_count: joint,
counterexample_count: a - joint,
support,
confidence,
lift,
leverage,
}
}
///|
/// Score an explicit candidate inventory. Undefined confidence never passes a threshold.
pub fn Context::filter_rules(
self : Context,
rules : Array[Implication],
min_support~ : Double = 0.0,
min_confidence~ : Double = 1.0,
) -> Array[ScoredRule] raise FcaError {
if !(min_support >= 0.0 && min_support <= 1.0) ||
!(min_confidence >= 0.0 && min_confidence <= 1.0) {
raise Invalid(
"support and confidence thresholds must be finite values in 0..1",
)
}
if rules.length() > 4096 {
raise Budget("candidate rule inventory exceeds 4096")
}
let result : Array[ScoredRule] = []
for rule in rules {
let rule = check_rule(rule, self.attribute_count())
let metrics = self.rule_metrics(rule)
match metrics.confidence {
Some(confidence) =>
if metrics.support >= min_support && confidence >= min_confidence {
result.push({ rule, metrics, })
}
None => ()
}
}
result
}