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,600 @@
1
+ import logging
2
+ import logging.config
3
+ from collections import OrderedDict
4
+
5
+ import numpy as np
6
+
7
+ from rulelearn_ri.dise import DISExplainer
8
+
9
+ from rulelearn_ri.algorithms.ripper.base import _encoding_for_parallel, init_encoder, encode_nominal, _split_instances
10
+ from rulelearn_ri.algorithms.ripper.binding import append_literal, _bind_literal, _bind_rule, \
11
+ unbound_rule_list_index, _unbound, _unbound_rule_list, _rule_list_predict, LE, EQ, _find_best_literal, Literal, \
12
+ _filter_contradicted_instances
13
+ from rulelearn_ri.algorithms.ripper.mdl import _mdl
14
+ from rulelearn_ri.algorithms.ripper.pruning import _pruning_irep, _minimize_rule_list, _pruning_optimization
15
+
16
+ from rulelearn_ri.trxf.core import Conjunction
17
+ from rulelearn_ri.trxf.core.dnf_ruleset import DnfRuleSet
18
+ from rulelearn_ri.trxf.core import Feature
19
+ from rulelearn_ri.trxf.core import Predicate, Relation
20
+
21
+ from pandas import DataFrame, Series
22
+
23
+
24
+ class RipperExplainer(DISExplainer):
25
+ """
26
+ RIPPER (Repeated Incremental Pruning to Produce Error Reduction) is a heuristic rule induction algorithm
27
+ based on separate-and-conquer. The explainer outputs a rule set in Disjunctive Normal Form (DNF) for a single
28
+ target concept.
29
+
30
+ References:
31
+ .. [#ML95] `William W Cohen, "Fast Effective Rule Induction"
32
+ Machine Learning: Proceedings of the Twelfth International Conference, 1995.
33
+ <http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.107.2612&rep=rep1&type=pdf>
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ d: int = 64,
39
+ k: int = 2,
40
+ pruning_threshold: int = 20,
41
+ random_state: int = 0,
42
+ ):
43
+ """
44
+ Args:
45
+ d (int): The number of bits that a new rule need to gain. Defaults to 64.
46
+ k (int): The number of iterations for the optimization loop. Defaults to 2.
47
+ pruning_threshold (int): The minimum number of instances for splitting. Defaults to 20.
48
+ random_state (int): The random seed for the splitting function. Defaults to 0.
49
+ """
50
+
51
+ super().__init__()
52
+ self.d = d
53
+ self.k = k
54
+ self.pruning_threshold = pruning_threshold
55
+ self.random_state = random_state
56
+ self._target_label = None
57
+ self.default_label = None
58
+
59
+ @property
60
+ def target_label(self):
61
+ """
62
+ The latest positive value RIPPER has been fitted for.
63
+ """
64
+ return self._target_label
65
+
66
+ def set_params(self, **kwargs):
67
+ self.d = kwargs.get('d', 64)
68
+ self.k = kwargs.get('k', 2)
69
+ self.pruning_threshold = kwargs.get('pruning_threshold', 20)
70
+ self.random_state = kwargs.get('random_state', 0)
71
+
72
+ def fit(self, train: DataFrame, y: Series, target_label=None):
73
+ """
74
+ The fit function for RIPPER algorithm. Its implementation is limited to DataFrame and Series because the RIPPER
75
+ algorithm needs the information of feature name and have to support nominal data type. Only `float` dtypes are
76
+ considered numerical features. All others (including int) are treated as nominal.
77
+
78
+ If target_label is specified, binary classification is assumed and asserted, and training uses target_label as selection\
79
+ of positive examples.
80
+
81
+ The induction of rules is deterministic by default as all random choices are initialized with self.random_state,
82
+ which is 0 by default.
83
+
84
+ Args:
85
+ train (pd.DataFrame): The features of the training set
86
+ y (pd.Series): The labels of the training set
87
+ target_label (Any): The target label to learn for binary classification, among the unique values of y. \
88
+ If not provided, Ripper will induce a native ordered ruleset with multiple labels/conclusions.
89
+
90
+ Returns:
91
+ self
92
+ """
93
+ logger = logging.getLogger(__name__)
94
+
95
+ self._target_label = target_label
96
+
97
+ self._labels = None
98
+ self._rule_map = None
99
+
100
+ self._nominal_column_encoders = None
101
+ self._column_name_list = None
102
+ self._column_name_index_map = None
103
+ self._condition_mat = None
104
+
105
+ self._gain_n = 0
106
+
107
+ # Check that X and y have correct shape
108
+ assert train.shape[0] == y.shape[0], 'X, y should have same length'
109
+
110
+ self._nominal_column_encoders = init_encoder(train)
111
+ encode_nominal(self._nominal_column_encoders, train)
112
+ self._column_name_list = train.columns.values
113
+
114
+ self._column_name_index_map = {name: index for index, name in enumerate(self._column_name_list)}
115
+
116
+ unique_labels, counts = np.unique(y, return_counts=True)
117
+ label_count_dict = dict(zip(unique_labels, counts))
118
+
119
+ assert len(unique_labels) > 1, 'label has only one value'
120
+
121
+ # Check whenever target_label is given that label has only two values and target_label is one of them
122
+ assert (target_label is None) or (len(unique_labels) == 2), 'Positive label value given but label not binary.'
123
+ assert (target_label is None) or (
124
+ target_label in unique_labels), 'Positive label value given is not a label value.'
125
+
126
+ self._labels = sorted(unique_labels, key=lambda e: label_count_dict[e],
127
+ reverse=False) # returns list from ndarray;
128
+ # the label values are sorted according to their occurrence; least frequent value first
129
+ # Set reverse=True for reversing that order
130
+ if (target_label is not None) and (self._labels[0] != target_label):
131
+ # reverse value order for binary classification when target_label is not the least frequent
132
+ self._labels.reverse()
133
+ self.default_label = self._labels[-1] # the default value is the last label in the sorting order
134
+ logger.debug('Label value ordering: ' + str(self._labels))
135
+
136
+ self._condition_mat = _encoding_for_parallel(train, self._column_name_index_map)
137
+ self._gain_n = self._condition_mat[0].shape[0]
138
+
139
+ # copy=True: under pandas 3 copy-on-write these arrays come back read-only,
140
+ # and the numba kernels in binding.py only accept writable ones
141
+ train = train.to_numpy(dtype=np.dtype(float), copy=True)
142
+ y = y.to_numpy(copy=True)
143
+
144
+ x_irep_list = train
145
+ y_irep = y
146
+
147
+ self._rule_map = OrderedDict()
148
+
149
+ for i in range(len(self._labels) - 1):
150
+ pos = self._labels[i]
151
+ neg = self._labels[i + 1:]
152
+
153
+ indexer_pos = y_irep == pos
154
+ indexer_neg = np.isin(y_irep, neg)
155
+
156
+ train_pos = x_irep_list[indexer_pos]
157
+ train_neg = x_irep_list[indexer_neg]
158
+
159
+ irep_res = self._irep_plus_outer_loop(
160
+ train_pos,
161
+ train_neg,
162
+ d=self.d,
163
+ ratio=2 / 3,
164
+ pruning_threshold=self.pruning_threshold
165
+ )
166
+ if len(irep_res) > 0:
167
+ self._rule_map[pos] = irep_res
168
+ bool_vec_index = unbound_rule_list_index(x_irep_list, self._rule_map[pos])
169
+ x_irep_list = x_irep_list[bool_vec_index]
170
+ y_irep = y_irep[bool_vec_index]
171
+
172
+ logger.debug('# begin optimization')
173
+
174
+ for _ in range(self.k):
175
+ x_optimize = train
176
+ y_optimise = y
177
+ for i in range(len(self._labels) - 1):
178
+ if not self._rule_map.get(self._labels[i]):
179
+ continue
180
+ pos = self._labels[i]
181
+ neg = self._labels[i + 1:]
182
+
183
+ indexer_pos = y_optimise == pos
184
+ indexer_neg = np.isin(y_optimise, neg)
185
+
186
+ train_pos = x_optimize[indexer_pos]
187
+ train_neg = x_optimize[indexer_neg]
188
+
189
+ self._rule_map[pos] = self._optimize(
190
+ train_pos,
191
+ train_neg,
192
+ ratio=2 / 3,
193
+ rules_input=self._rule_map[pos]
194
+ )
195
+
196
+ bool_vec_index = unbound_rule_list_index(x_optimize, self._rule_map[pos])
197
+ x_optimize = x_optimize[bool_vec_index]
198
+ y_optimise = y_optimise[bool_vec_index]
199
+
200
+ return self
201
+
202
+ def predict(self, X: DataFrame) -> np.ndarray:
203
+ """
204
+ The predict function for RIPPER algorithm. Its implementation is limited to DataFrame and Series because the
205
+ RIPPER algorithm needs the information of feature name and have to support nominal data type
206
+
207
+ Args:
208
+ X (pd.DataFrame): DataFrame of features
209
+
210
+ Returns:
211
+ np.array: predicted labels
212
+ """
213
+ test = X.copy()
214
+ encode_nominal(self._nominal_column_encoders, test)
215
+ result_vec = [None for _ in range(len(test))]
216
+
217
+ input_arr = test.to_numpy(dtype=np.dtype(float))
218
+
219
+ result = {label: _rule_list_predict(input_arr, self._rule_map[label]) for label in self._rule_map.keys()}
220
+
221
+ for label in self._rule_map.keys():
222
+ for i in range(len(result_vec)):
223
+ if result_vec[i] is None and result[label][i]:
224
+ result_vec[i] = label
225
+
226
+ return np.array([res if res is not None else self.default_label for res in result_vec])
227
+
228
+ def explain(self):
229
+ """
230
+ Export rule set to technical interchange format trxf from internal representation
231
+ for the positive value (i.e. label value) it has been fitted for.
232
+
233
+ When the internal rule set is empty an empty dnf rule set with the internal pos value
234
+ is returned.
235
+
236
+ Returns:
237
+ trxf.DnfRuleSet
238
+ """
239
+ assert (self.target_label is not None), 'Not fitted or not fitted for a specific pos value. Use export_rules ' \
240
+ 'in the latter case. '
241
+
242
+ if len(self._rule_map.items()) == 0:
243
+ return DnfRuleSet([], self.target_label)
244
+ for label, rules in self._rule_map.items():
245
+ if label == self.target_label:
246
+ return self._rules_to_trxf_dnf_ruleset(rules, label)
247
+ raise Exception('No rules found for label: ' + str(self.target_label))
248
+
249
+ def explain_multiclass(self):
250
+ """
251
+ Export rules to technical interchange format trxf from internal representation
252
+ Returns a list of rule sets.
253
+
254
+ Returns:
255
+ list(trxf.DnfRuleSet): -- Ordered list of rulesets
256
+ """
257
+ res = list()
258
+ if len(self._rule_map.items()) == 0:
259
+ return DnfRuleSet([], self.target_label)
260
+ for label, rules in self._rule_map.items():
261
+ dnf_ruleset = self._rules_to_trxf_dnf_ruleset(rules, label)
262
+ res.append(dnf_ruleset)
263
+ default_rule = DnfRuleSet([], self.default_label)
264
+ res.append(default_rule)
265
+ return res
266
+
267
+ def _irep_plus_outer_loop(
268
+ self,
269
+ pos: np.ndarray,
270
+ neg: np.ndarray,
271
+ d: int,
272
+ ratio: float = 2 / 3,
273
+ pruning_threshold: int = 20
274
+ ):
275
+ """
276
+ The learning phase of RIPPER.
277
+
278
+ Args:
279
+ pos (np.ndarray): Positive instances
280
+ neg (np.ndarray): Negative instances
281
+ d (int): The number to bit that a new rule need to gain
282
+ ratio (float): The percentage of pruning data
283
+ pruning_threshold (int): The minimum number of instances for splitting
284
+
285
+ Returns:
286
+ list: Rule list learned using IREP algorithm
287
+ """
288
+ neg = _filter_contradicted_instances(pos, neg)
289
+ n = self._gain_n
290
+
291
+ rules = []
292
+
293
+ # TODO verify if the intent was to make a copy of the value instead of the reference
294
+ pos_original = pos
295
+ neg_original = neg
296
+
297
+ dl_min = _mdl(pos, neg, rules, n)
298
+
299
+ while len(pos) > 0:
300
+
301
+ if len(pos) > pruning_threshold:
302
+ pos_grow, pos_prune, neg_grow, neg_prune = _split_instances(pos, neg, ratio, self.random_state)
303
+ rule = self._grow_rule(pos_grow, neg_grow)
304
+ rule = _pruning_irep(pos_prune, neg_prune, rule)
305
+ else:
306
+ rule = self._grow_rule(pos, neg)
307
+
308
+ rules.append(rule)
309
+ dl_new = _mdl(pos, neg, rules, n)
310
+
311
+ if dl_new > dl_min + d:
312
+ return _minimize_rule_list(pos_original, neg_original, rules, n)
313
+ else:
314
+ if dl_new < dl_min:
315
+ dl_min = dl_new
316
+ pos = _unbound(pos, rule)
317
+ neg = _unbound(neg, rule)
318
+
319
+ return rules
320
+
321
+ def _replacement(
322
+ self,
323
+ pos: np.ndarray,
324
+ neg: np.ndarray,
325
+ rules,
326
+ index: int,
327
+ ratio: float = 2 / 3
328
+ ):
329
+ """
330
+ The replacement step for RIPPER optimization
331
+
332
+ Parameters
333
+ ----------
334
+ pos: np.ndarray
335
+ Positive instances
336
+ neg: np.ndarray
337
+ Negative instances
338
+ rules : list
339
+ Rules that needs to be optimized
340
+ index : int
341
+ Index of selected rule
342
+ ratio : float
343
+ The percentage of pruning data
344
+
345
+ Returns
346
+ -------
347
+ list
348
+ A replacement rule
349
+ """
350
+ rest = rules[:index] + rules[index + 1:]
351
+
352
+ new_pos = _unbound_rule_list(pos, rest)
353
+
354
+ if len(new_pos) > 2:
355
+
356
+ pos_grow, pos_prune, neg_grow, neg_prune = _split_instances(new_pos, neg, ratio, self.random_state)
357
+
358
+ new_rule = self._grow_rule(pos=pos_grow, neg=neg_grow)
359
+
360
+ pruned_rule = _pruning_optimization(
361
+ pos_prune=pos_prune,
362
+ neg_prune=neg_prune,
363
+ rule=new_rule,
364
+ rules=rules,
365
+ index=index
366
+ )
367
+ return pruned_rule
368
+ else:
369
+ return rules[index]
370
+
371
+ def _revision(
372
+ self,
373
+ pos: np.ndarray,
374
+ neg: np.ndarray,
375
+ rules,
376
+ index: int,
377
+ ratio: float = 2 / 3
378
+ ):
379
+ """
380
+ The revision step for RIPPER optimization
381
+
382
+ Parameters
383
+ ----------
384
+ pos: np.ndarray
385
+ Positive instances
386
+ neg: np.ndarray
387
+ Negative instances
388
+ rules : list
389
+ Rules that needs to be optimized
390
+ index : int
391
+ Index of selected rule
392
+ ratio : float
393
+ The percentage of pruning data
394
+
395
+ Returns
396
+ -------
397
+ list
398
+ A revision rule
399
+ """
400
+ rule = rules[index]
401
+
402
+ new_pos = _unbound_rule_list(pos, rules[:index] + rules[index + 1:])
403
+
404
+ if new_pos.shape[0] > 2:
405
+
406
+ pos_grow, pos_prune, neg_grow, neg_prune = _split_instances(new_pos, neg, ratio, self.random_state)
407
+ new_rule = self._grow_rule(pos_grow, neg_grow, predefined_rule=rule)
408
+ pruned_rule = _pruning_optimization(
409
+ pos_prune=pos_prune,
410
+ neg_prune=neg_prune,
411
+ rule=new_rule,
412
+ rules=rules,
413
+ index=index
414
+ )
415
+ return pruned_rule
416
+ else:
417
+ return rule
418
+
419
+ def _optimize(
420
+ self,
421
+ pos: np.ndarray,
422
+ neg: np.ndarray,
423
+ rules_input,
424
+ ratio: float = 2 / 3
425
+ ):
426
+ """
427
+ The optimization step for RIPPER
428
+
429
+ Parameters
430
+ ----------
431
+ pos: np.ndarray
432
+ Positive instances
433
+ neg: np.ndarray
434
+ Negative instances
435
+ rules_input : list
436
+ Rules that needs to be optimized
437
+ ratio : float
438
+ The percentage of pruning data
439
+
440
+ Returns
441
+ -------
442
+ list
443
+ Optimized rule list
444
+ """
445
+ rules = rules_input.copy()
446
+
447
+ i = 0
448
+ while i < len(rules):
449
+
450
+ # ====================== origin ============================================================================
451
+ dl_origin = _mdl(pos, neg, rules, self._gain_n)
452
+
453
+ # ====================== replacement =======================================================================
454
+ replacement_rule = self._replacement(pos=pos, neg=neg, rules=rules, ratio=ratio, index=i)
455
+ if len(replacement_rule) != 0:
456
+ dl_replacement = _mdl(pos, neg, rules[:i] + [replacement_rule] + rules[i + 1:], self._gain_n)
457
+ else:
458
+ dl_replacement = _mdl(pos, neg, rules[:i] + rules[i + 1:], self._gain_n)
459
+
460
+ # ====================== revision ==========================================================================
461
+ revision_rule = self._revision(pos=pos, neg=neg, rules=rules, ratio=ratio, index=i)
462
+
463
+ dl_revision = _mdl(pos, neg, rules[:i] + [revision_rule] + rules[i + 1:], self._gain_n)
464
+
465
+ # ====================== result ============================================================================
466
+ if dl_origin <= dl_replacement and dl_origin <= dl_revision:
467
+ # if no improvement is made, then the original rule is chosen
468
+ pass
469
+ # secondly, replacement rule is tested
470
+ elif dl_replacement <= dl_origin and dl_replacement <= dl_revision:
471
+ if len(replacement_rule) != 0:
472
+ rules[i] = replacement_rule
473
+ else:
474
+ del rules[i]
475
+ continue
476
+ # finally, if the origin rule and the replacement rule are not satisfied, the revision rule is chosen
477
+ else:
478
+ rules[i] = revision_rule
479
+ i += 1
480
+ return rules
481
+
482
+ def _rule_to_trxf_conjunction(self, rule):
483
+ """
484
+ Transform one rule to a trxf conjunction given its internal presentation
485
+
486
+ Parameters
487
+ ----------
488
+ rule : list
489
+ Input rule
490
+
491
+ Returns
492
+ -------
493
+ trxf conjunction
494
+ String representation of that rule
495
+ """
496
+ # TODO code duplication with decode_rule should be refactored -> deprecate decode_rule as it should go
497
+ # through trxf
498
+
499
+ conjunction = Conjunction([])
500
+
501
+ for condition in rule:
502
+ name = self._column_name_list[condition.name]
503
+ feature = Feature(name)
504
+ if condition.op == EQ:
505
+ relation = Relation.EQ
506
+ value = self._nominal_column_encoders[name].classes_[condition.nom_val]
507
+ elif condition.op == LE:
508
+ relation = Relation.LE
509
+ value = condition.num_val
510
+ else:
511
+ relation = Relation.GE
512
+ value = condition.num_val
513
+
514
+ predicate = Predicate(feature, relation, value)
515
+ conjunction.add_predicate(predicate)
516
+
517
+ return conjunction
518
+
519
+ def _rules_to_trxf_dnf_ruleset(self, rules, label):
520
+ """
521
+ Transform rules to trxf dnf_ruleset given their internal presentation and their label
522
+
523
+ Parameters
524
+ ----------
525
+ rules : list
526
+ Rules for one target
527
+ label : str
528
+ The label of rules
529
+
530
+ Returns
531
+ -------
532
+ DnfRuleSet
533
+ """
534
+ conjunctions = list()
535
+ for rule in rules:
536
+ conjunction = self._rule_to_trxf_conjunction(rule)
537
+ conjunctions.append(conjunction)
538
+ dnf_ruleset = DnfRuleSet(conjunctions, label)
539
+ return dnf_ruleset
540
+
541
+ def _grow_rule(self, pos, neg, predefined_rule=None):
542
+
543
+ """
544
+ The grow function for IREP*
545
+
546
+ Parameters
547
+ ----------
548
+ pos : np.ndarray
549
+ Positive instances
550
+ neg : np.ndarray
551
+ Negative instances
552
+ predefined_rule: const int64_t[::1]
553
+ Existing rules
554
+
555
+ Returns
556
+ -------
557
+ list
558
+ Learned rule
559
+ """
560
+ _validate_grow_rule_input(pos)
561
+ neg = _filter_contradicted_instances(pos, neg)
562
+
563
+ if predefined_rule is not None:
564
+ pos = _bind_rule(pos, predefined_rule)
565
+ neg = _bind_rule(neg, predefined_rule)
566
+
567
+ # the length of pos must be always positive
568
+ if len(pos) == 0 or len(neg) == 0:
569
+ return predefined_rule
570
+
571
+ learned_rule = list(predefined_rule)
572
+ else:
573
+ learned_rule = list()
574
+
575
+ while len(neg) > 0:
576
+ pos = np.asfortranarray(pos)
577
+ neg = np.asfortranarray(neg)
578
+
579
+ best_literal_index = _find_best_literal(pos,
580
+ neg,
581
+ self._condition_mat[0],
582
+ self._condition_mat[1],
583
+ self._condition_mat[2],
584
+ self._condition_mat[3]
585
+ )
586
+ literal = Literal(self._condition_mat[0][best_literal_index],
587
+ self._condition_mat[1][best_literal_index],
588
+ self._condition_mat[2][best_literal_index],
589
+ self._condition_mat[3][best_literal_index]
590
+ )
591
+
592
+ append_literal(learned_rule, literal)
593
+ pos = _bind_literal(pos, literal.name, literal.op, literal.num_val, literal.nom_val)
594
+ neg = _bind_literal(neg, literal.name, literal.op, literal.num_val, literal.nom_val)
595
+ return learned_rule
596
+
597
+
598
+ def _validate_grow_rule_input(pos):
599
+ if len(pos) == 0:
600
+ raise AssertionError('pos must not be empty')
@@ -0,0 +1,26 @@
1
+ from typing import Any
2
+
3
+ import pandas as pd
4
+
5
+ from rulelearn_ri.algorithms.ripper.ripper import RipperExplainer
6
+ from rulelearn_ri.trxf.core.dnf_ruleset import DnfRuleSet
7
+ from rulelearn_ri.trxf.core.ruleset_generator import RuleSetGenerator
8
+
9
+
10
+ class RipperRuleSetGenerator(RuleSetGenerator):
11
+ def generate(self, X: pd.DataFrame, y: pd.Series, to_learn: Any, **kwargs) -> DnfRuleSet:
12
+ """
13
+ Optional kwargs:
14
+
15
+ d : int
16
+ The number to bit that a new rule need to gain (default=64)
17
+ k : int
18
+ The number of optimization iteration (default=2)
19
+ pruning_threshold : int
20
+ The minimum number of instances for splitting (default=20)
21
+ random_state:
22
+ The random seed for grow/prune set splitting (default=0)
23
+ """
24
+ ripper = RipperExplainer(**kwargs)
25
+ ripper.fit(X, y, to_learn)
26
+ return ripper.explain()