Repository navigation
Ordinal "at least / at most" conditions (opt-in ordinal_conditions) - #45
Merged
Merged
Conversation
A rule can now state `x IS at least t` or `x IS at most t` over the ordered terms of a variable, besides a single term. A variable with n terms offers 3n - 4 conditions (the n terms, and at least / at most t_j for j = 1..n-2); the membership is the bounded sum min(1, sum of the covered terms), a shouldered trapezoid on a strong partition. The term gene is widened to the condition codes (no gene is added), so sampling, crossover, mutation, the array / population / streamed / PyTorch objectives and duplicate-rule detection handle it. Estimators: BaseFuzzyRulesClassifier (winning rule, mixture, mixture_search) and BaseFuzzyRulesRegressor (crisp and fuzzy). Printing, rule_conditions() (operator / term without parsing), text persistence, pickles, pattern stability and the latex / chart helpers handle it. Off by default: encoding, rules, files and seeded fits equal 3.4.0 (replayed from a recorded fixture). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qtw1LhwWgNJnXyJzGsLR2p
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #45 +/- ##
==========================================
+ Coverage 91.70% 91.93% +0.23%
==========================================
Files 36 36
Lines 9146 9533 +387
==========================================
+ Hits 8387 8764 +377
- Misses 759 769 +10
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ordinal_conditions now also takes a collection of features (names or
indexes): only those variables get at least / at most, the rest keep single
terms (a cyclic clock variable, for example). The new compound_terms
parameter, {feature: [(name, [terms])...]}, adds one condition `x IS <name>`
per entry whose membership is the bounded sum of the listed terms; the terms
need not be contiguous (a union that wraps around is two shoulders).
Entries are validated (>= 2 distinct terms, not all, no repeated set, no
duplicate of an ordinal condition, a name unique within its variable) and go
through the same codes, compute_memberships rows, gather table, remap and
evaluators. They print by name, read as Condition(operator='any_of', terms,
name), and round-trip through the rule and variable text and pickle. Same
estimators; refused with candidate_rules. Defaults unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qtw1LhwWgNJnXyJzGsLR2p
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
ordinal_conditions=True(defaultFalse) lets a rule statex IS at least torx IS at most tover the ordered terms of a variable, besides a single term:Supported on
BaseFuzzyRulesClassifier(winning-rule,inference='mixture',mixture_search) andBaseFuzzyRulesRegressor(crisp and Mamdani), and on the problems behind them (FitRuleBase,FitRuleBaseRegression). Docs:docs/source/ordinal_conditions.rst; changelog under "Unreleased". No version bump, no tag.Update: per-variable selection and named compound terms (commit 7a0844a)
After seeing
local_hour IS at most Mediumon a cyclic clock variable:ordinal_conditionsacceptsTrue,False(default) or a collection of features, by name or index (['temp', 'level'],[1, 2]). Only those variables getat least/at most; the others keep single terms. A feature named explicitly must be able to have them (3+ terms, not categorical) or aValueErrorsays so; unknown names and bad indexes are errors; a bare string or dict is rejected. Empty/False/Noneare off (the problem is then built with exactly the 3.4.0 arguments, tested).compound_terms(new, defaultNone):{feature: [(name, [term, ...]), ...]}, feature by name or index, terms by name or index. Each entry adds one conditionx IS <name>whose membership is the bounded sum of the listed terms. The terms need not be contiguous, so('Night', ['Very High', 'Low'])wraps around midnight; on a linear axis that union is two shoulders, one at each end (documented and tested with values). The set is stored sorted.ValueError): at least 2 distinct terms; not every term; unknown term / index out of range; no two compounds with the same set; no compound with the set of an ordinal condition the variable has; the name must be unique within its variable (not a term name, another compound's name or an ordinal condition's name; non-empty, no;, line breaks, parentheses,AND/IS/WITH/THEN, not starting withat least/at most); a feature given twice.compute_memberships, the packed gather table, the remap (n_term_codes), canonical codes, and the array,batch_size, population and PyTorch objectives (the latter declines for searched partitions). Works with or without ordinal conditions, with fixed or searched partitions, T1 and interval T2, also on categorical variables. Same estimators (BaseFuzzyRulesClassifierincl. mixture andmixture_search,BaseFuzzyRulesRegressorcrisp/fuzzy,FitRuleBase,FitRuleBaseRegression); refused withcandidate_rulesand for general Type-2.IF local_hour IS Night.Conditionis now(feature, variable, operator, term, term_index, terms, code, name); for'any_of',termandterm_indexareNone,termslists the constituent term names (low to high) andnameis the given name; for the other operatorsnameisNone.fuzzyVariable.condition(code)gives('any_of', position in variable.compound);condition_code('any_of', name)is the inverse.load_fuzzy_variablesreads one lineCompound;<name>;<term indexes>per compound, written only when there are any); pickles, copies, pattern-stability keys,rules_to_latexand the charts handle it.tests/test_ordinal_conditions.py): round trips, every validation error, wrap-around membership values, evaluator equivalence (array = object oracle for fixed/searched, T1/T2, with and without ordinal/batch_size; population and torch), unchanged default fits (the recorded 3.4.0 fixture and a spy that the problem receives no new argument).Design
nordered terms offers thensingle terms,at least t_jandat most t_jforj = 1..n-2:3n - 4conditions (8 for 4 terms).at least t_0(= don't care),at least t_(n-1)(=t_(n-1)) and theirat mosttwins are not offered; no general intervals. No two conditions cover the same set, so a set has exactly one code.min(1, sum of covered terms). On a strong partition it is the exact complement of the other terms (a shouldered trapezoid); the defaultconstruct_partitionstrapezoids andconstruct_breakpoint_partitionare strong (checked). Gaussian terms are not strong and saturate at 1; interval Type-2 sums the lower and upper bounds apart; general Type-2 is refused; categorical variables keep single terms. With the default product t-norm a condition is one antecedent in [0, 1], multiplied like a single term. The bounded sum is the dual of the Lukasiewicz t-norm, not of the product (documented).[-1, max_codes - 1]and the existing proportional remap (_fitness.remap_term) is applied to each variable's code count (FitRuleBase.n_term_codes, equal to the term counts when off).RuleSimple.antecedentsstores the same code (terms first, then theat leastcodes, then theat mostones), so rule equality, hashing,delete_rule_duplicates, the array decoder's per-class dedup and the mixture's duplicate detection work unchanged. Sampling, SBX and polynomial mutation work on the bounds. The extra conditions are extra rows offuzzyVariable.compute_memberships(and columns of the packed gather table), so the array, streamed (batch_size), population and PyTorch (fixed partitions; it declines for searched partitions) objectives handle them; all agree with the object decoder bit for bit (tests).rules.rule_conditions(rule, antecedents)/rule.conditions(antecedents)returnCondition(feature, variable, operator, term, term_index, terms, code),operatorin{'is', 'at_least', 'at_most'}, no text parsing. Printing saysIS at least Fair/IS at most Fair(rules,print_mixture, regression rules,rules_to_latex, pattern-stability reports and charts, rule keys).IS at least X, swapping an unflagged variable for a flagged copy in the returned rule base, never mutating the input);save_fuzzy_variableswrites one extra lineOrdinal conditionsonly for variables that have them (the others are byte-identical); pickles round-trip and old pickles (variable, classifier, regressor) load with the option off.Backward compatibility (off = 3.4.0)
tests/data/ordinal_default_golden.jsonwas recorded onmain(10d811b) before any change: genes, rule text, prediction digests, objective, variable text and rule-text round trip of nine fits (searched T1 and T2 partitions, fixed partitions withds_mode=2,mixture_search, three regression modes).test_default_fits_equal_the_recorded_release_fitsreplays them on this branch and requires equality. The default bounds andn_term_codes == n_lv_possibleare asserted separately, and the option is only passed to the problem when on. I also ran the other 100+ test files one at a time (all pass).Small check (
benchmarks/benchmark_ordinal_conditions.py)Fixed 4-term strong partitions from the training fold, 20 rules, up to 3 conditions, 60 generations x 50 individuals (mixture arm: 20 generations,
mixture_search, non-negative), 5-fold x 2 seeds (mixture: 1 seed). Mean over the held-out folds;on-2xdoubles the generations. Macro F1 (R^2 for diabetes), rules kept, mean conditions per rule, fit time.Standard deviations over the 10 runs are 0.03-0.09 (0.11-0.14 for R^2), so only wine and diabetes are clear gains and iris is within noise; the fits are small and tell nothing about the method in general. Rules with an
at least/at mostcondition were 65-83% of the kept rules. Doubling the generations changed little here; with larger partitions or many features the larger space may need more.Tests
tests/test_ordinal_conditions.py(58): alphabet and codes, membership (strong / Gaussian / interval Type-2 / product t-norm), the golden replay, array = object oracle (fixed and searched, T1 and T2, with and withoutbatch_size), population and PyTorch routes, bounds / SBX / mutation, encode-decode round trip, duplicates, the three estimators, categorical and refused combinations, printing,rule_conditions, text / variable / pickle round trips, pattern-stability keys, reports and charts, latex, p-values. Run single files (the suite is large); ran everything file by file.Not covered / limitations
FuzzyRulesClassifier,RuleMineClassifier,RuleFineTuneClassifier, FERL, and the random-rule null distribution of the bootstrap p-values (single terms) do not take the option. Refitting the same estimator after toggling the option off keeps the previous fit's flaggedlvs(the library already retains partitions on refit); useclone.🤖 Generated with Claude Code
https://claude.ai/code/session_01Qtw1LhwWgNJnXyJzGsLR2p