rulelearn-ripper-interactive 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. rulelearn_ri/__init__.py +6 -0
  2. rulelearn_ri/algorithms/__init__.py +0 -0
  3. rulelearn_ri/algorithms/r2n/__init__.py +0 -0
  4. rulelearn_ri/algorithms/r2n/base.py +28 -0
  5. rulelearn_ri/algorithms/r2n/functions.py +25 -0
  6. rulelearn_ri/algorithms/r2n/mask.py +58 -0
  7. rulelearn_ri/algorithms/r2n/preprocessing_layers.py +192 -0
  8. rulelearn_ri/algorithms/r2n/r2n_algo.py +287 -0
  9. rulelearn_ri/algorithms/r2n/simple_rulenet.py +75 -0
  10. rulelearn_ri/algorithms/r2n/training.py +34 -0
  11. rulelearn_ri/algorithms/r2n/utilities.py +117 -0
  12. rulelearn_ri/algorithms/rbm/__init__.py +0 -0
  13. rulelearn_ri/algorithms/rbm/boolean_rule_cg.py +65 -0
  14. rulelearn_ri/algorithms/rbm/rbm/BRCG.py +69 -0
  15. rulelearn_ri/algorithms/rbm/rbm/GLRM.py +98 -0
  16. rulelearn_ri/algorithms/rbm/rbm/__init__.py +6 -0
  17. rulelearn_ri/algorithms/rbm/rbm/beam_search.py +302 -0
  18. rulelearn_ri/algorithms/rbm/rbm/boolean_rule_cg.py +245 -0
  19. rulelearn_ri/algorithms/rbm/rbm/features.py +544 -0
  20. rulelearn_ri/algorithms/rbm/rbm/linear_regression.py +393 -0
  21. rulelearn_ri/algorithms/rbm/rbm/logistic_regression.py +447 -0
  22. rulelearn_ri/algorithms/rbm/utils.py +13 -0
  23. rulelearn_ri/algorithms/ripper/__init__.py +10 -0
  24. rulelearn_ri/algorithms/ripper/base.py +81 -0
  25. rulelearn_ri/algorithms/ripper/binding.py +541 -0
  26. rulelearn_ri/algorithms/ripper/mdl.py +97 -0
  27. rulelearn_ri/algorithms/ripper/pruning.py +212 -0
  28. rulelearn_ri/algorithms/ripper/ripper.py +600 -0
  29. rulelearn_ri/algorithms/ripper/ripper_ruleset_generator.py +26 -0
  30. rulelearn_ri/algorithms/ripper/single_rule.py +425 -0
  31. rulelearn_ri/dise.py +64 -0
  32. rulelearn_ri/trxf/__init__.py +0 -0
  33. rulelearn_ri/trxf/classifier/__init__.py +0 -0
  34. rulelearn_ri/trxf/classifier/ruleset_classifier.py +153 -0
  35. rulelearn_ri/trxf/core/__init__.py +5 -0
  36. rulelearn_ri/trxf/core/boolean_evaluator.py +15 -0
  37. rulelearn_ri/trxf/core/conjunction.py +50 -0
  38. rulelearn_ri/trxf/core/dnf_ruleset.py +64 -0
  39. rulelearn_ri/trxf/core/feature.py +155 -0
  40. rulelearn_ri/trxf/core/predicate.py +98 -0
  41. rulelearn_ri/trxf/core/ruleset_generator.py +25 -0
  42. rulelearn_ri/trxf/core/utils.py +40 -0
  43. rulelearn_ri/trxf/metrics.py +98 -0
  44. rulelearn_ri/trxf/pmml_export/__init__.py +4 -0
  45. rulelearn_ri/trxf/pmml_export/__version__.py +1 -0
  46. rulelearn_ri/trxf/pmml_export/models/__init__.py +24 -0
  47. rulelearn_ri/trxf/pmml_export/models/attribute.py +24 -0
  48. rulelearn_ri/trxf/pmml_export/models/characteristics.py +20 -0
  49. rulelearn_ri/trxf/pmml_export/models/data_dictionary.py +53 -0
  50. rulelearn_ri/trxf/pmml_export/models/mining_schema.py +25 -0
  51. rulelearn_ri/trxf/pmml_export/models/pmml_ruleset_model.py +16 -0
  52. rulelearn_ri/trxf/pmml_export/models/predicate.py +42 -0
  53. rulelearn_ri/trxf/pmml_export/models/rule.py +26 -0
  54. rulelearn_ri/trxf/pmml_export/models/ruleset.py +28 -0
  55. rulelearn_ri/trxf/pmml_export/models/ruleset_model.py +16 -0
  56. rulelearn_ri/trxf/pmml_export/models/scorecard.py +35 -0
  57. rulelearn_ri/trxf/pmml_export/pmml_exporter.py +18 -0
  58. rulelearn_ri/trxf/pmml_export/reader/__init__.py +2 -0
  59. rulelearn_ri/trxf/pmml_export/reader/abstract_reader.py +16 -0
  60. rulelearn_ri/trxf/pmml_export/reader/trxf_reader.py +113 -0
  61. rulelearn_ri/trxf/pmml_export/serializer/__init__.py +2 -0
  62. rulelearn_ri/trxf/pmml_export/serializer/abstract_serializer.py +11 -0
  63. rulelearn_ri/trxf/pmml_export/serializer/nyoka_serializer.py +162 -0
  64. rulelearn_ri/trxf/scorecard/__init__.py +3 -0
  65. rulelearn_ri/trxf/scorecard/bins.py +253 -0
  66. rulelearn_ri/trxf/scorecard/partition.py +71 -0
  67. rulelearn_ri/trxf/scorecard/scorecard.py +78 -0
  68. rulelearn_ripper_interactive-0.2.3.dist-info/METADATA +164 -0
  69. rulelearn_ripper_interactive-0.2.3.dist-info/RECORD +73 -0
  70. rulelearn_ripper_interactive-0.2.3.dist-info/WHEEL +5 -0
  71. rulelearn_ripper_interactive-0.2.3.dist-info/licenses/LICENSE +201 -0
  72. rulelearn_ripper_interactive-0.2.3.dist-info/licenses/NOTICE +11 -0
  73. rulelearn_ripper_interactive-0.2.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,50 @@
1
+ from typing import Dict, Any, List
2
+
3
+ from rulelearn_ri.trxf.core.boolean_evaluator import BooleanEvaluator
4
+ from rulelearn_ri.trxf.core.predicate import Predicate
5
+ from rulelearn_ri.trxf.core.utils import set_equality
6
+
7
+
8
+ class Conjunction(BooleanEvaluator):
9
+ def __init__(self, predicate_list: List[Predicate]):
10
+ """
11
+ @param predicate_list: a list of predicates intrpreted as a conjunction
12
+ """
13
+ self._predicates = predicate_list
14
+
15
+ @property
16
+ def predicates(self):
17
+ return self._predicates
18
+
19
+ def add_predicate(self, pred: Predicate):
20
+ self._predicates.append(pred)
21
+
22
+ def delete_predicate(self, pred: Predicate):
23
+ self._predicates.remove(pred)
24
+
25
+ def evaluate(self, assignment: Dict[str, Any]) -> bool:
26
+ """
27
+ Evaluate the truth value of the conjunction w.r.t. the variable assignment
28
+
29
+ @param assignment: dict mapping variable name to value
30
+ @return: bool truth value of the predicate
31
+ """
32
+ for pred in self.predicates:
33
+ if not pred.evaluate(assignment):
34
+ return False
35
+ return True
36
+
37
+ def __repr__(self):
38
+ return '%s(%r)' %(self.__class__, self._predicates)
39
+
40
+ def __str__(self):
41
+ if len(self.predicates) == 0:
42
+ return 'true'
43
+ else:
44
+ return ' ^ '.join(['['+str(x)+']' for x in self._predicates])
45
+
46
+ def __eq__(self, other):
47
+ return set_equality(self.predicates, other.predicates)
48
+
49
+ def __len__(self):
50
+ return len(self.predicates)
@@ -0,0 +1,64 @@
1
+ from typing import Dict, List, Any
2
+
3
+ from rulelearn_ri.trxf.core.boolean_evaluator import BooleanEvaluator
4
+ from rulelearn_ri.trxf.core.conjunction import Conjunction
5
+ from rulelearn_ri.trxf.core.utils import set_equality
6
+
7
+
8
+ class DnfRuleSet(BooleanEvaluator):
9
+ """
10
+ This class implements a ruleset in the disjunctive normal form: c_1 v c_2 v .... v c_k. Here, each c_i is a
11
+ conjunction of predicates, i.e., c_i = p_{i1} & p_{i2} & ... & p_{i n_i}. All the conjunctions share the same
12
+ conclusion, specified by the then_part of the ruleset.
13
+ """
14
+
15
+ def __init__(self, conjunctions: List[Conjunction], then_part: Any) -> None:
16
+ """
17
+ @param conjunctions: A list containing conjunctions in this ruleset
18
+ @param then_part: The then part of this RuleSet.
19
+ """
20
+ self._then_part = then_part
21
+ self._conjunctions = []
22
+ for conjunction in conjunctions:
23
+ if conjunction not in self._conjunctions:
24
+ self._conjunctions.append(conjunction)
25
+
26
+ @property
27
+ def conjunctions(self):
28
+ return self._conjunctions
29
+
30
+ @property
31
+ def then_part(self):
32
+ return self._then_part
33
+
34
+ def add_conjunction(self, conjunction: Conjunction) -> None:
35
+ if conjunction not in self._conjunctions:
36
+ self._conjunctions.append(conjunction)
37
+
38
+ def remove_conjunction(self, conjunction: Conjunction) -> None:
39
+ self._conjunctions.remove(conjunction)
40
+
41
+ def list_conjunctions(self) -> List[Conjunction]:
42
+ return self.conjunctions
43
+
44
+ def evaluate(self, assignment: Dict[str, Any]) -> bool:
45
+ for conjunction in self.conjunctions:
46
+ if conjunction.evaluate(assignment):
47
+ return True
48
+ return False
49
+
50
+ def __repr__(self):
51
+ return '%s(%r, %r)' % (self.__class__, self._conjunctions, self._then_part)
52
+
53
+ def __str__(self):
54
+ if len(self.conjunctions) == 0:
55
+ disjunction = 'false'
56
+ else:
57
+ disjunction = ' v\n'.join(['(' + str(x) + ')' for x in self._conjunctions])
58
+ return 'if\n' + disjunction + '\nthen\n' + str(self._then_part)
59
+
60
+ def __eq__(self, other):
61
+ return set_equality(self.conjunctions, other.conjunctions) and self.then_part == other.then_part
62
+
63
+ def __len__(self):
64
+ return len(self.conjunctions)
@@ -0,0 +1,155 @@
1
+ import numbers
2
+ import tokenize
3
+ from io import BytesIO
4
+ from typing import Dict, Any
5
+
6
+ OPERATORS = ['+', '-', '*', '/']
7
+ PARENTHESES = ['(', ')']
8
+
9
+
10
+ def is_number(s):
11
+ try:
12
+ float(s)
13
+ return True
14
+ except ValueError:
15
+ return False
16
+
17
+
18
+ class Feature:
19
+ def __init__(self, expression: str):
20
+ """
21
+ @param expression: Infix arithmetic expression encoded as utf-8 string involving constants, operators (+, -,
22
+ *, /), parentheses, and variable names. Variable names shall not have any of the above operators or
23
+ whitespace as part of the name. Care must be taken not to use the utf-8 long dash '\xe2' for the minus sign,
24
+ when copy-pasting.
25
+ """
26
+ self._expression = expression
27
+ self._rpn = _shunting_yard(self._expression)
28
+
29
+ @property
30
+ def variable_names(self):
31
+ """
32
+ A list of variable names in the feature representation
33
+ """
34
+ tokens = _get_tokens(self._expression)
35
+ return [x for x in tokens if x not in OPERATORS + ['(', ')'] and not is_number(x)]
36
+
37
+ @property
38
+ def reverse_polish_notation(self):
39
+ """
40
+ The Reverse Polish Notation (https://en.wikipedia.org/wiki/Reverse_Polish_notation) representation of the
41
+ feature as a list of token strings. This serves as the symbolic representation of the feature.
42
+ """
43
+ return self._rpn
44
+
45
+ def evaluate(self, assignment: Dict[str, Any]) -> Any:
46
+ """
47
+ Evaluate the value of the feature for the provided variable assignment
48
+
49
+ @param assignment: dict mapping variable name to value
50
+ @return: the feature value w.r.t. the assignment
51
+ """
52
+ operand_stack = []
53
+ for token in self.reverse_polish_notation:
54
+ if token in OPERATORS:
55
+ rhs = operand_stack.pop()
56
+ _validate_number(rhs)
57
+ lhs = operand_stack.pop()
58
+ _validate_number(lhs)
59
+ expression = str(lhs) + token + str(rhs)
60
+ try:
61
+ operand_stack.append(eval(expression))
62
+ except NameError as e:
63
+ raise ValueError(
64
+ 'eval() failed for expression "{}"'.format(expression)) from e
65
+ else:
66
+ operand = assignment[token] if token in assignment else float(token)
67
+ operand_stack.append(operand)
68
+ assert len(operand_stack) == 1
69
+
70
+ result = operand_stack.pop()
71
+ try:
72
+ result = float(result)
73
+ except ValueError:
74
+ pass
75
+ return result
76
+
77
+ def __repr__(self):
78
+ return '%s(%r)' % (self.__class__, self.reverse_polish_notation)
79
+
80
+ def __str__(self):
81
+ return ' '.join(self._expression.split())
82
+
83
+ def __eq__(self, other):
84
+ return self.__repr__() == other.__repr__()
85
+
86
+
87
+ def _shunting_yard(expression):
88
+ """
89
+ Converts infix expression string into Reverse Polish Notation.
90
+ https://en.wikipedia.org/wiki/Shunting-yard_algorithm
91
+ """
92
+ tokens = _get_tokens(expression)
93
+ if len(tokens) > 0 and (tokens[0] == '-' or tokens[0] == '+'):
94
+ tokens.insert(0, 0)
95
+ op_precedence = {'+': 1, '-': 1, '*': 2, '/': 2}
96
+ output = []
97
+ op_stack = []
98
+ for token in tokens:
99
+ if token in OPERATORS:
100
+ while len(op_stack) > 0 and op_stack[-1] != '(' and op_precedence[op_stack[-1]] >= op_precedence[token]:
101
+ output.append(op_stack.pop())
102
+ op_stack.append(token)
103
+ elif token == '(':
104
+ op_stack.append(token)
105
+ elif token == ')':
106
+ while len(op_stack) > 0 and op_stack[-1] != '(':
107
+ output.append(op_stack.pop())
108
+ if len(op_stack) == 0:
109
+ raise ValueError('Unmatched closing parenthesis in expression: ' + expression)
110
+ op_stack.pop()
111
+ else:
112
+ output.append(token)
113
+ while len(op_stack) > 0:
114
+ if op_stack[-1] == '(':
115
+ raise ValueError('Unmatched opening parenthesis in expression: ' + expression)
116
+ output.append(op_stack.pop())
117
+
118
+ _validate_rpn(output, expression)
119
+ return output
120
+
121
+
122
+ def _get_tokens(expression):
123
+ no_whitespace = ''.join(expression.split())
124
+ g = tokenize.tokenize(BytesIO(no_whitespace.encode('UTF-8')).readline)
125
+ assert next(g).string == 'utf-8'
126
+ tokens = []
127
+ buffer = ''
128
+ result = []
129
+ for _, v, _, _, _ in g:
130
+ tokens.append(v)
131
+ for token in tokens:
132
+ if token not in OPERATORS + PARENTHESES:
133
+ buffer += token
134
+ else:
135
+ result.append(buffer)
136
+ result.append(token)
137
+ buffer = ''
138
+ result.append(buffer)
139
+ return [x for x in result if x != '']
140
+
141
+
142
+ def _validate_rpn(rpn, expression):
143
+ stack_size = 0
144
+ for token in rpn:
145
+ valence = 2 if token in OPERATORS else 0
146
+ stack_size += 1 - valence
147
+ if stack_size <= 0:
148
+ raise ValueError('Invalid RPN {} for expression "{}"'.format(rpn, expression))
149
+ if stack_size != 1:
150
+ raise ValueError('Invalid RPN {} for expression "{}"'.format(rpn, expression))
151
+
152
+
153
+ def _validate_number(value):
154
+ if not isinstance(value, numbers.Number) or isinstance(value, bool):
155
+ raise ValueError('{} is not a number.'.format(value))
@@ -0,0 +1,98 @@
1
+ import math
2
+ from enum import Enum
3
+ from typing import Dict, Any
4
+
5
+ from rulelearn_ri.trxf.core.boolean_evaluator import BooleanEvaluator
6
+ from rulelearn_ri.trxf.core.feature import Feature, is_number
7
+
8
+ ABS_TOL = 1e-5
9
+
10
+
11
+ class Relation(Enum):
12
+ NEQ = '!='
13
+ EQ = '=='
14
+ LT = '<'
15
+ LE = '<='
16
+ GT = '>'
17
+ GE = '>='
18
+
19
+ def __neg__(self):
20
+ neg_table = {
21
+ self.NEQ: self.EQ,
22
+ self.EQ: self.NEQ,
23
+ self.LT: self.GE,
24
+ self.GE: self.LT,
25
+ self.LE: self.GT,
26
+ self.GT: self.LE
27
+ }
28
+ return neg_table[self]
29
+
30
+
31
+ class Predicate(BooleanEvaluator):
32
+ def __init__(self, feature: Feature, relation: Relation, value: Any):
33
+ """
34
+ @param feature: the LHS of the relation
35
+ @param value: the RHS of the relation
36
+ """
37
+ self._feature = feature
38
+ self._relation = relation
39
+ self._value = value
40
+ _validate_predicate(self._relation, self._value)
41
+
42
+ @property
43
+ def feature(self):
44
+ return self._feature
45
+
46
+ @property
47
+ def relation(self):
48
+ return self._relation
49
+
50
+ @property
51
+ def value(self):
52
+ return self._value
53
+
54
+ def evaluate(self, assignment: Dict[str, Any]) -> bool:
55
+ """
56
+ Evaluate the truth value of the predicate w.r.t. the variable assignment
57
+
58
+ @param assignment: dict mapping variable name to value
59
+ @return: bool truth value of the predicate
60
+ """
61
+ feature_val = self.feature.evaluate(assignment)
62
+ if self.relation == Relation.EQ:
63
+ return math.isclose(float(feature_val), float(self.value), abs_tol=ABS_TOL) if is_number(self.value) \
64
+ else feature_val == self.value
65
+ elif self.relation == Relation.NEQ:
66
+ return feature_val != self.value
67
+ elif self.relation == Relation.GE:
68
+ return feature_val >= self.value
69
+ elif self.relation == Relation.GT:
70
+ return feature_val > self.value
71
+ elif self.relation == Relation.LE:
72
+ return feature_val <= self.value
73
+ elif self.relation == Relation.LT:
74
+ return feature_val < self.value
75
+ else:
76
+ raise ValueError('Unknown relation operator {}'.format(self.relation))
77
+
78
+ def __neg__(self):
79
+ if self.value is True or self.value is False:
80
+ return Predicate(self.feature, self.relation, not self.value)
81
+ return Predicate(self.feature, -self.relation, self.value)
82
+
83
+ def __repr__(self):
84
+ return '%s(%r, %r, %r)' % (self.__class__, self.feature, self.relation, self.value)
85
+
86
+ def __str__(self):
87
+ return ' '.join([str(self.feature), str(self.relation.value), str(self.value)])
88
+
89
+ def __eq__(self, other):
90
+ return self.feature == other.feature and self.relation == other.relation and self.value == other.value
91
+
92
+
93
+ def _validate_predicate(relation, value):
94
+ if (isinstance(value, str) or isinstance(value, bool)) and relation != Relation.EQ and relation != Relation.NEQ:
95
+ raise ValueError('String or boolean value "{}" can only be compared through "==" or "!=" but was "{}"'
96
+ .format(value, relation.value))
97
+
98
+
@@ -0,0 +1,25 @@
1
+ import abc
2
+ from typing import Any
3
+
4
+ import pandas as pd
5
+
6
+ from rulelearn_ri.trxf.core.dnf_ruleset import DnfRuleSet
7
+
8
+
9
+ class RuleSetGenerator(metaclass=abc.ABCMeta):
10
+ @classmethod
11
+ def __subclasshook__(cls, subclass):
12
+ return callable(subclass.generate)
13
+
14
+ @abc.abstractmethod
15
+ def generate(self, X: pd.DataFrame, y: pd.Series, to_learn: Any, **kwargs) -> DnfRuleSet:
16
+ """
17
+ Train a rule induction algorithm and generate a trxf DnfRuleSet
18
+
19
+ @param X: pandas dataframe representing features
20
+ @param y: pandas series representing labels
21
+ @param to_learn: one of the unique values of y we want to learn, representing the positive class
22
+ @param kwargs: algorithm-specific parameters
23
+ @return: a trxf DnfRuleSet
24
+ """
25
+ raise NotImplementedError
@@ -0,0 +1,40 @@
1
+ import pandas as pd
2
+
3
+ from rulelearn_ri.trxf.core.boolean_evaluator import BooleanEvaluator
4
+
5
+
6
+ def set_equality(list1: list, list2: list) -> bool:
7
+ """
8
+ Equivalent to set(list1) == set(list2) but checks the set inequality manually for lists containing elements that are
9
+ not hashable.
10
+
11
+ @param list1: The first list
12
+ @param list2: The second list
13
+
14
+ @return bool: Whether the two lists are equal when converted to sets
15
+ """
16
+
17
+ equality = True
18
+ for item in list1:
19
+ if item not in list2:
20
+ equality = False
21
+ break
22
+
23
+ for item in list2:
24
+ if item not in list1:
25
+ equality = False
26
+ break
27
+
28
+ return equality
29
+
30
+
31
+ def batch_evaluate(evaluator: BooleanEvaluator, X: pd.DataFrame) -> pd.Series:
32
+ """
33
+ Evaluate the truth value of the evaluator for each row of X, representing the assignments
34
+
35
+ @param evaluator: a BooleanEvaluator, e.g., DnfRuleSet, Conjunction, etc.
36
+ @param X: pandas dataframe where column names represent variable names used by the evaluator
37
+ @return: pandas series of boolean values representing
38
+ """
39
+ assignments = X.to_dict('records')
40
+ return pd.Series(list(map(lambda assignment: evaluator.evaluate(assignment), assignments)))
@@ -0,0 +1,98 @@
1
+ from collections import namedtuple
2
+ from copy import deepcopy
3
+ from typing import List
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from sklearn.metrics import confusion_matrix
8
+
9
+ from rulelearn_ri.trxf.core.dnf_ruleset import DnfRuleSet
10
+ from rulelearn_ri.trxf.core.utils import batch_evaluate
11
+
12
+ RuleContributionMetrics = namedtuple(
13
+ 'RuleContributionMetrics',
14
+ 'conjunction, tp_common, tp_unique, fp_common, fp_unique, n_correct, n_records, laplace_estimate')
15
+ """
16
+ conjunction: a trxf Conjunction object
17
+ tp_common: count of true positives identified based on this and at least one other rule (large values indicate redundancy)
18
+ tp_unique: count of true positives correctly identifying a label based only on this rule (indicates strength of the rule)
19
+ fp_common: count of false positives based on this and at least one other rule
20
+ fp_unique: count of false positives, i.e. mis-classified instances, based on this rule alone
21
+ n_correct: count of true positives + true_negatives
22
+ n_records: total number of instances
23
+ laplace_estimate: metric of confidence (https://en.wikipedia.org/wiki/Additive_smoothing)
24
+ """
25
+
26
+ RuleComplexityMetrics = namedtuple('RuleComplexityMetrics',
27
+ 'num_rules, max_rule_length, sum_rule_length, num_distinct_predicates')
28
+
29
+
30
+ def compute_ruleset_metrics(ruleset: DnfRuleSet, X: pd.DataFrame, y: pd.Series):
31
+ """
32
+ Compute ruleset-level confusion matrix and accuracy when used as a binary classifier
33
+
34
+ @param X: features
35
+ @param y: labels
36
+ @return: a dict describing the confusion matrix and accuracy
37
+ """
38
+ then_part = ruleset.then_part
39
+ y_true = (y == then_part)
40
+ y_pred = batch_evaluate(ruleset, X)
41
+ tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
42
+ accuracy = (tn + tp) / len(y)
43
+ return {'tn': tn, 'fp': fp, 'fn': fn, 'tp': tp, 'accuracy': accuracy}
44
+
45
+
46
+ def compute_rule_metrics(ruleset: DnfRuleSet, X: pd.DataFrame, y: pd.Series) -> List[RuleContributionMetrics]:
47
+ """
48
+ Compute individual rule-level contribution to coverage and redundancy.
49
+ See https://ibm.ent.box.com/file/888961054325?sb=/activity/annotations/956283151925 for more context.
50
+
51
+ @param X: features
52
+ @param y: labels
53
+ @return: a dict mapping the string representation of conjunctions to a dict containing the contribution metrics
54
+ """
55
+ then_part = ruleset.then_part
56
+ res = []
57
+ for conjunction in ruleset.conjunctions:
58
+ complement = deepcopy(ruleset)
59
+ complement.remove_conjunction(conjunction)
60
+ y_pred_compl = batch_evaluate(complement, X)
61
+ tp_compl, tn_compl, fp_compl, fn_compl = get_preaggregated_confusion_matrix(y_pred_compl, y, then_part)
62
+ y_pred_conj = batch_evaluate(conjunction, X)
63
+ tp_conj, tn_conj, fp_conj, fn_conj = get_preaggregated_confusion_matrix(y_pred_conj, y, then_part)
64
+ laplace_estimate = (len(tp_conj) + 1) / (len(tp_conj) + len(fp_conj) + 2)
65
+
66
+ tp_common = len(set(tp_conj).intersection(set(tp_compl)))
67
+ tp_unique = len(set(tp_conj).difference(set(tp_compl)))
68
+ fp_common = len(set(fp_conj).intersection(set(fp_compl)))
69
+ fp_unique = len(set(fp_conj).difference(set(fp_compl)))
70
+ n_correct = len(tn_conj) + len(tp_conj)
71
+ n_records = X.shape[0]
72
+ res.append(RuleContributionMetrics(
73
+ conjunction, tp_common, tp_unique, fp_common, fp_unique, n_correct, n_records, laplace_estimate))
74
+ return res
75
+
76
+
77
+ def compute_ruleset_complexity(ruleset: DnfRuleSet):
78
+ num_rules = len(ruleset)
79
+ rule_lengths = list(map(len, ruleset.conjunctions))
80
+ max_rule_length = max(rule_lengths)
81
+ sum_rule_length = sum(rule_lengths)
82
+ predicates = []
83
+ for c in ruleset.conjunctions:
84
+ predicates += [p for p in c.predicates if p not in predicates]
85
+ num_distinct_predicates = len(predicates)
86
+ return RuleComplexityMetrics(num_rules, max_rule_length, sum_rule_length, num_distinct_predicates)
87
+
88
+
89
+ def get_preaggregated_confusion_matrix(y_pred, y_true, pos_value):
90
+ y_p = y_pred.to_numpy()
91
+ y_t = (y_true.to_numpy() == pos_value)
92
+
93
+ tp = np.where(y_p & y_t)[0]
94
+ tn = np.where(np.logical_not(y_p) & np.logical_not(y_t))[0]
95
+ fp = np.where(y_p & np.logical_not(y_t))[0]
96
+ fn = np.where(y_t & np.logical_not(y_p))[0]
97
+
98
+ return tp, tn, fp, fn
@@ -0,0 +1,4 @@
1
+ from .__version__ import version
2
+ from .reader import AbstractReader, TrxfReader
3
+ from .serializer import AbstractSerializer, NyokaSerializer
4
+ from .pmml_exporter import PmmlExporter
@@ -0,0 +1 @@
1
+ version = '0.0.1'
@@ -0,0 +1,24 @@
1
+ from .data_dictionary import DataDictionary
2
+ from .data_dictionary import DataField
3
+ from .data_dictionary import DataType
4
+ from .data_dictionary import OpType
5
+ from .data_dictionary import Value
6
+ from .data_dictionary import Restriction
7
+ from .mining_schema import MiningField
8
+ from .mining_schema import MiningFieldUsageType
9
+ from .mining_schema import MiningSchema
10
+ from .predicate import BooleanOperator
11
+ from .predicate import CompoundPredicate
12
+ from .predicate import Operator
13
+ from .predicate import SimplePredicate
14
+ from .predicate import TruePredicate
15
+ from .rule import SimpleRule
16
+ from .rule import DEFAULT_WEIGHT
17
+ from .rule import DEFAULT_CONFIDENCE
18
+ from .ruleset import RuleSelectionMethod
19
+ from .ruleset import RuleSet
20
+ from .ruleset_model import RuleSetModel
21
+ from .pmml_ruleset_model import SimplePMMLRuleSetModel
22
+ from .scorecard import Scorecard, Output, OutputField
23
+ from .characteristics import Characteristics, Characteristic
24
+ from .attribute import Attribute, ComplexPartialScore
@@ -0,0 +1,24 @@
1
+ import typing
2
+
3
+ from . import predicate
4
+
5
+ try:
6
+ # python >= 3.7
7
+ from dataclasses import dataclass as dataclass
8
+ from dataclasses import field as field
9
+ except ImportError:
10
+ from attr import s as dataclass
11
+ from attr import ib as field
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ComplexPartialScore:
16
+ feature_name: str = field()
17
+ multiplier: str = field(default=None)
18
+ constant: str = field(default=None)
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class Attribute:
23
+ score: typing.Union[str, ComplexPartialScore]
24
+ predicate: typing.Union[predicate.SimplePredicate, predicate.CompoundPredicate, predicate.TruePredicate]
@@ -0,0 +1,20 @@
1
+ import typing
2
+
3
+ from . import attribute
4
+
5
+ try:
6
+ # python >= 3.7
7
+ from dataclasses import dataclass as dataclass
8
+ except ImportError:
9
+ from attr import s as dataclass
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class Characteristic:
14
+ name: str
15
+ attributes: typing.List[attribute.Attribute]
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class Characteristics:
20
+ characteristics: typing.List[Characteristic]
@@ -0,0 +1,53 @@
1
+ try:
2
+ # python >= 3.7
3
+ from dataclasses import dataclass as dataclass
4
+ from dataclasses import field as field
5
+ except ImportError:
6
+ from attr import s as dataclass
7
+ from attr import ib as field
8
+ import enum
9
+ import typing
10
+
11
+
12
+ class DataType(enum.Enum):
13
+ string = 0
14
+ integer = 1
15
+ float = 2
16
+ double = 3
17
+ boolean = 4
18
+
19
+
20
+ class OpType(enum.Enum):
21
+ categorical = 0
22
+ ordinal = 1
23
+ continuous = 2
24
+
25
+
26
+ class Restriction(enum.Enum):
27
+ valid = 0
28
+ invalid = 1
29
+ missing = 2
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Value:
34
+ value: str = field()
35
+ property: Restriction = field(default=Restriction.valid)
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class DataField:
40
+ name: str = field()
41
+ optype: OpType = field()
42
+ dataType: DataType = field()
43
+ values: typing.Optional[typing.List[Value]] = field(default=None)
44
+
45
+ def __post_init__(self):
46
+ if self.values and \
47
+ (self.dataType is not DataType.string or self.optype not in (OpType.ordinal, OpType.categorical)):
48
+ raise ValueError
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class DataDictionary:
53
+ dataFields: typing.List[DataField] = field()