psyke 0.8.4__py3-none-any.whl → 0.8.5.dev1__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.

Potentially problematic release.


This version of psyke might be problematic. Click here for more details.

psyke/__init__.py CHANGED
@@ -61,7 +61,7 @@ class EvaluableModel(object):
61
61
  raise NotImplementedError('predict')
62
62
 
63
63
  def __convert(self, ys: Iterable) -> Iterable:
64
- if self.normalization is not None:
64
+ if self.normalization is not None and not isinstance([p for p in ys if p is not None][0], str):
65
65
  m, s = self.normalization[list(self.normalization.keys())[-1]]
66
66
  ys = [prediction if prediction is None else prediction * s + m for prediction in ys]
67
67
  return ys
@@ -73,7 +73,7 @@ class EvaluableModel(object):
73
73
  raise NotImplementedError('brute_predict')
74
74
 
75
75
  def unscale(self, values, name):
76
- if self.normalization is None or isinstance(values, LinearRegression):
76
+ if self.normalization is None or name not in self.normalization or isinstance(values, LinearRegression):
77
77
  return values
78
78
  if isinstance(values, Iterable):
79
79
  values = [None if value is None else
@@ -161,17 +161,20 @@ class Extractor(EvaluableModel, ABC):
161
161
  """
162
162
  raise NotImplementedError('extract')
163
163
 
164
- def predict_why(self, data: dict[str, float]):
164
+ def predict_why(self, data: dict[str, float], verbose=True):
165
165
  """
166
166
  Provides a prediction and the corresponding explanation.
167
167
  :param data: is the instance to predict.
168
+ :param verbose: if the explanation has to be printed.
168
169
  """
169
170
  raise NotImplementedError('predict_why')
170
171
 
171
- def predict_counter(self, data: dict[str, float]):
172
+ def predict_counter(self, data: dict[str, float], verbose=True, only_first=True):
172
173
  """
173
174
  Provides a prediction and counterfactual explanations.
174
175
  :param data: is the instance to predict.
176
+ :param verbose: if the counterfactual explanation has to be printed.
177
+ :param only_first: if only the closest counterfactual explanation is provided for each distinct class.
175
178
  """
176
179
  raise NotImplementedError('predict_counter')
177
180
 
@@ -44,8 +44,8 @@ class HyperCubeExtractor(HyperCubePredictor, PedagogicalExtractor, ABC):
44
44
  return theory
45
45
 
46
46
  def pairwise_fairness(self, data: dict[str, float], neighbor: dict[str, float]):
47
- cube1 = self._find_cube(data.copy())
48
- cube2 = self._find_cube(neighbor.copy())
47
+ cube1 = self._find_cube(data)
48
+ cube2 = self._find_cube(neighbor)
49
49
  different_prediction_reasons = []
50
50
 
51
51
  if cube1.output == cube2.output:
@@ -63,76 +63,83 @@ class HyperCubeExtractor(HyperCubePredictor, PedagogicalExtractor, ABC):
63
63
  different_prediction_reasons.append(d)
64
64
  return different_prediction_reasons
65
65
 
66
- def predict_counter(self, data: dict[str, float], verbose=True):
66
+ def predict_counter(self, data: dict[str, float], verbose=True, only_first=True):
67
67
  output = ""
68
68
  prediction = None
69
- cube = self._find_cube(data.copy())
69
+ cube = self._find_cube(data)
70
70
  if cube is None:
71
71
  output += "The extracted knowledge is not exhaustive; impossible to predict this instance"
72
72
  else:
73
73
  prediction = self._predict_from_cubes(data)
74
- output += f"The output is {prediction}"
74
+ output += f"The output is {prediction}\n"
75
75
 
76
76
  point = Point(list(data.keys()), list(data.values()))
77
77
  cubes = self._hypercubes if cube is None else [c for c in self._hypercubes if cube.output != c.output]
78
78
  cubes = sorted([(cube.surface_distance(point), cube.volume(), i, cube) for i, cube in enumerate(cubes)])
79
- outputs = []
80
- different_prediction_reasons = []
79
+
80
+ counter_conditions = []
81
+
81
82
  for _, _, _, c in cubes:
82
- if c.output not in outputs:
83
- outputs.append(c.output)
84
- output += f"\nThe output may be {c.output} if"
85
-
86
- for d in point.dimensions.keys():
87
- lower, upper = c[d]
88
- p = point[d]
89
- if p < lower:
90
- output += f"\n {d} increases above {round(lower, 1)}"
91
- different_prediction_reasons.append((d, '>=', lower))
92
- elif p > upper:
93
- output += f"\n {d} decreses below {round(upper, 1)}"
94
- different_prediction_reasons.append((d, '<=', upper))
83
+ if not only_first or c.output not in [o for o, _ in counter_conditions]:
84
+ counter_conditions.append((c.output, {c: [val for val in v if val is not None and not val.is_in(
85
+ self.unscale(data[c], c))] for c, v in self.__get_conditions(data, c).items()}))
86
+
95
87
  if verbose:
88
+ for o, conditions in counter_conditions:
89
+ output += f"The output may be {o} if\n" + HyperCubeExtractor.__conditions_to_string(conditions)
96
90
  print(output)
97
- return prediction, different_prediction_reasons
98
91
 
99
- def __get_local_conditions(self, data: dict[str, float], cube: GenericCube) -> dict[list[Value]]:
100
- conditions = {d: [cube.interval_to_value(d, self.unscale)] for d in data.keys()}
101
- subcubes = cube.subcubes(self._hypercubes)
102
- subsubcubes = [c for cube_list in [c.subcubes(self._hypercubes) for c in subcubes] for c in cube_list]
103
- for c in [c for c in subcubes if c not in subsubcubes]:
92
+ return prediction, counter_conditions
93
+
94
+ @staticmethod
95
+ def __conditions_to_string(conditions: dict[str, list[Value]]) -> str:
96
+ output = ""
97
+ for d in conditions:
98
+ for i, condition in enumerate(conditions[d]):
99
+ if i == 0:
100
+ output += f' {d} is '
101
+ else:
102
+ output += ' and '
103
+ output += condition.print()
104
+ if i + 1 == len(conditions[d]):
105
+ output += '\n'
106
+ return output
107
+
108
+ def __get_conditions(self, data: dict[str, float], cube: GenericCube) -> dict[str, list[Value]]:
109
+ conditions = {d: [cube.interval_to_value(d, self.unscale)] for d in data.keys()
110
+ if d not in self._dimensions_to_ignore}
111
+ for c in cube.subcubes(self._hypercubes):
104
112
  for d in conditions:
105
113
  condition = c.interval_to_value(d, self.unscale)
106
114
  if condition is None:
107
115
  continue
108
116
  elif conditions[d][-1] is None:
109
117
  conditions[d][-1] = -condition
110
- elif (-condition).is_in(data[d]):
118
+ else:
111
119
  try:
112
120
  conditions[d][-1] *= -condition
113
121
  except Exception:
114
122
  conditions[d].append(-condition)
115
123
  return conditions
116
124
 
117
- def predict_why(self, data: dict[str, float]):
118
- cube = self._find_cube(data.copy())
125
+ def predict_why(self, data: dict[str, float], verbose=True):
126
+ cube = self._find_cube(data)
127
+ output = ""
119
128
  if cube is None:
120
- print("The extracted knowledge is not exhaustive; impossible to predict this instance")
121
- else:
122
- output = self._predict_from_cubes(data)
123
- print(f"The output is {output} because")
124
- conditions = self.__get_local_conditions(data, cube)
125
- for d in conditions:
126
- for i, condition in enumerate(conditions[d]):
127
- if condition is None:
128
- continue
129
- elif i == 0:
130
- print(' ', d, 'is', end=' ')
131
- else:
132
- print('and', end=' ')
133
- print(condition.print(), end='')
134
- if i + 1 == len(conditions[d]):
135
- print()
129
+ output += "The extracted knowledge is not exhaustive; impossible to predict this instance\n"
130
+ if verbose:
131
+ print(output)
132
+ return None, {}
133
+ prediction = self._predict_from_cubes(data)
134
+ output += f"The output is {prediction} because\n"
135
+ conditions = {c: [val for val in v if val is not None and val.is_in(self.unscale(data[c], c))]
136
+ for c, v in self.__get_conditions(data, cube).items()}
137
+
138
+ if verbose:
139
+ output += HyperCubeExtractor.__conditions_to_string(conditions)
140
+ print(output)
141
+
142
+ return prediction, conditions
136
143
 
137
144
  @staticmethod
138
145
  def _create_head(dataframe: pd.DataFrame, variables: list[Var], output: float | LinearRegression) -> Struct:
@@ -25,7 +25,7 @@ class CReEPy(HyperCubeExtractor):
25
25
  self.clustering = clustering(depth, error_threshold, self._output, gauss_components, discretization,
26
26
  normalization, seed)
27
27
  self._default_surrounding_cube = True
28
- self._dimensions_to_ignore = [dimension for dimension, relevance in ranks if relevance < ignore_threshold]
28
+ self._dimensions_to_ignore = set([dimension for dimension, relevance in ranks if relevance < ignore_threshold])
29
29
 
30
30
  def _extract(self, dataframe: pd.DataFrame) -> Theory:
31
31
  if not isinstance(self.clustering, HyperCubeClustering):
@@ -175,8 +175,12 @@ class HyperCube:
175
175
  def barycenter(self) -> Point:
176
176
  return self._barycenter
177
177
 
178
- def subcubes(self, cubes: Iterable[GenericCube]) -> Iterable[GenericCube]:
179
- return [c for c in cubes if c in self and c != self]
178
+ def subcubes(self, cubes: Iterable[GenericCube], only_largest: bool = True) -> Iterable[GenericCube]:
179
+ subcubes = [c for c in cubes if c in self and c.output != self.output]
180
+ if only_largest:
181
+ subsubcubes = [c for cube_list in [c.subcubes(cubes) for c in subcubes] for c in cube_list]
182
+ subcubes = [c for c in subcubes if c not in subsubcubes]
183
+ return subcubes
180
184
 
181
185
  def _fit_dimension(self, dimension: dict[str, tuple[float, float]]) -> dict[str, tuple[float, float]]:
182
186
  new_dimension: dict[str, tuple[float, float]] = {}
@@ -76,8 +76,10 @@ class HyperCubePredictor(EvaluableModel):
76
76
  return round(HyperCubePredictor._get_cube_output(cube, data), get_int_precision())
77
77
 
78
78
  def _find_cube(self, data: dict[str, float]) -> GenericCube | None:
79
+ data = data.copy()
79
80
  for dimension in self._dimensions_to_ignore:
80
- del data[dimension]
81
+ if dimension in data:
82
+ del data[dimension]
81
83
  for cube in self._hypercubes:
82
84
  if data in cube:
83
85
  return cube.copy()
psyke/schema/__init__.py CHANGED
@@ -4,7 +4,7 @@ from typing import Callable
4
4
  from psyke.utils import get_int_precision
5
5
 
6
6
  _EMPTY_INTERSECTION_EXCEPTION: Callable = lambda x, y: \
7
- Exception("Empty intersection between two Value: " + str(x) + ' and ' + str(y))
7
+ Exception(f"Empty intersection between two Value: {str(x)} and {str(y)}")
8
8
 
9
9
  _NOT_IMPLEMENTED_INTERSECTION: Callable = lambda x, y: \
10
10
  Exception("Not implemented intersection between: " + str(x) + ' and ' + str(y))
@@ -250,6 +250,9 @@ class Value:
250
250
  else:
251
251
  raise _INTERSECTION_WITH_WRONG_TYPE(self, other)
252
252
 
253
+ def print(self) -> str:
254
+ pass
255
+
253
256
 
254
257
  class Interval(Value):
255
258
 
@@ -265,7 +268,7 @@ class Interval(Value):
265
268
  def __repr__(self):
266
269
  return f"Interval({self.lower:.2f}, {self.upper:.2f})"
267
270
 
268
- def __eq__(self, other: Between) -> bool:
271
+ def __eq__(self, other: Interval) -> bool:
269
272
  return (self.upper == other.upper) and (self.lower == other.lower) and (self.standard == other.standard)
270
273
 
271
274
 
@@ -291,7 +294,8 @@ class LessThan(Interval):
291
294
  return f"LessThan({self.upper:.2f})"
292
295
 
293
296
  def __eq__(self, other: LessThan) -> bool:
294
- return (self.upper == other.upper) and (self.value == other.value) and (self.standard == other.standard)
297
+ return isinstance(other, LessThan) and (self.upper == other.upper) and \
298
+ (self.value == other.value) and (self.standard == other.standard)
295
299
 
296
300
 
297
301
  class GreaterThan(Interval):
@@ -316,7 +320,8 @@ class GreaterThan(Interval):
316
320
  return f"GreaterThan({self.lower:.2f})"
317
321
 
318
322
  def __eq__(self, other: GreaterThan) -> bool:
319
- return (self.lower == other.lower) and (self.value == other.value) and (self.standard == other.standard)
323
+ return isinstance(other, GreaterThan) and (self.lower == other.lower) and \
324
+ (self.value == other.value) and (self.standard == other.standard)
320
325
 
321
326
 
322
327
  class Between(Interval):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: psyke
3
- Version: 0.8.4
3
+ Version: 0.8.5.dev1
4
4
  Summary: Python-based implementation of PSyKE, i.e. a Platform for Symbolic Knowledge Extraction
5
5
  Home-page: https://github.com/psykei/psyke-python
6
6
  Author: Matteo Magnini
@@ -1,5 +1,5 @@
1
- psyke/__init__.py,sha256=weEnLws23jzCu_wqZVfq-jVlikiGle14coSMrjUnt7w,18155
2
- psyke/hypercubepredictor.py,sha256=XceZtTy70QI0PaoCuowvnJWZWcF3NoWF4Khd3qXHCgk,4507
1
+ psyke/__init__.py,sha256=MSMiwvVth4kqmGvQt6HiYNR3J4EdINe8PBmK8-DQVSo,18545
2
+ psyke/hypercubepredictor.py,sha256=MlSRLky6J1I07qKcH98c9WvEjFHGyBiz_LU9W_oDnqs,4572
3
3
  psyke/clustering/__init__.py,sha256=36MokTVwwWR_-o0mesvXHaYEYVTK2pn2m0ZY4G3Y3qU,581
4
4
  psyke/clustering/utils.py,sha256=S0YwCKyHVYp9qUAQVzCMrTwcQFPJ5TD14Jwn10DE-Z4,1616
5
5
  psyke/clustering/cream/__init__.py,sha256=W6k7vdjuUdA_azYA4vb5JtpWrofhDJ0DbM2jsnRKzfw,2994
@@ -7,12 +7,12 @@ psyke/clustering/exact/__init__.py,sha256=s4MPvGZ6gle3X9WH3YFHOEdinGcXIXh-7EFRcE
7
7
  psyke/extraction/__init__.py,sha256=ziZ8T9eAOZjKipepE5_j1zfZgyFPONjW8MGERSk83nI,743
8
8
  psyke/extraction/cart/__init__.py,sha256=s8tr7jPhONgF0pfetHieFax2MD8cy6ddOS7dRWzZLjc,3579
9
9
  psyke/extraction/cart/predictor.py,sha256=2-2mv5fI0lTwwfTaEonxKh0ZUdhxuIEE6OP_rJxgmqc,3019
10
- psyke/extraction/hypercubic/__init__.py,sha256=vV4xIvyUyeQczrTHYuBzHcQrNc_967x7ZWHT3FommUg,10599
11
- psyke/extraction/hypercubic/hypercube.py,sha256=kDEhYlY7EahWV07AwEZIdEu94HIQUw-CdoKBdle61BQ,25430
10
+ psyke/extraction/hypercubic/__init__.py,sha256=w_NmfSjh8fCWLDXVXpRLiAApq697cvUSPTgju-jtZCA,10620
11
+ psyke/extraction/hypercubic/hypercube.py,sha256=GKjplRl34BegrA3JclvlkrL7hXftdUUMXndmRFFoJic,25697
12
12
  psyke/extraction/hypercubic/strategy.py,sha256=X-roIsfcpJyMdo2px5JtbhP7-XE-zUNkaEK7XGXoWA8,1636
13
13
  psyke/extraction/hypercubic/utils.py,sha256=D2FN5CCm_T3h23DmLFoTnIcFo7LvIq__ktl4hjUqkcA,1525
14
14
  psyke/extraction/hypercubic/cosmik/__init__.py,sha256=XQUvOtMFpR0vMHYtwIVl3G626HMqN8Clt6BqNm4nvFs,1880
15
- psyke/extraction/hypercubic/creepy/__init__.py,sha256=8bT0zVsJs9odlVuMIzQUzMwUpfcFwbULzm7dlbhyJkg,1730
15
+ psyke/extraction/hypercubic/creepy/__init__.py,sha256=js91nWXOSJtRFFjnNzv4eDKnRYthe1GA1a-CGAaHzDM,1735
16
16
  psyke/extraction/hypercubic/divine/__init__.py,sha256=ClO8CITKKXoo7nhlBJagR1yAachsxLHYQlqggl-9eGE,3665
17
17
  psyke/extraction/hypercubic/gridex/__init__.py,sha256=o7tNU3JH8AqA2PRj839-rPb6zhwAdpaCVGC__0DH-b0,5543
18
18
  psyke/extraction/hypercubic/gridrex/__init__.py,sha256=h9usK5tFqd6ngBmRydsgkfQ1jlcQKj2uG72Tr1puFHk,595
@@ -22,7 +22,7 @@ psyke/extraction/real/__init__.py,sha256=fFqiwgWTpu5Jx9lz5CdSfs1QyqWYFLQDG7tc5M6
22
22
  psyke/extraction/real/utils.py,sha256=eHGU-Y0inn_8jrk9lMcuRUKXpsTkI-s_myXSWz4bALQ,2190
23
23
  psyke/extraction/trepan/__init__.py,sha256=KpZpk0btCWV4bS-DOmpgpYscSQ5FEMyP54ekm7ZedME,6583
24
24
  psyke/extraction/trepan/utils.py,sha256=iSUJ1ooNQT_VO1KfBZuIUeUsyUbGdQf_pSEE87vMeQg,2320
25
- psyke/schema/__init__.py,sha256=R3rH22YdA_aEB62YBJXGHpcVAgxZ12jFzgQFzKTzfDc,17306
25
+ psyke/schema/__init__.py,sha256=66Jm4hk9s2ZBdXUF7tg43_zG0X6XicMYOPsBkXyY0wE,17444
26
26
  psyke/tuning/__init__.py,sha256=I-07lLZb02DoIm9AGXPPPOkB55ANu8RU4TMy2j30Pxg,3574
27
27
  psyke/tuning/crash/__init__.py,sha256=zIHEF75EFy_mRIieqzP04qKLG3GLsSc_mYZHpPfkzxU,2623
28
28
  psyke/tuning/orchid/__init__.py,sha256=s64iABbteik27CrRPHSVHNZX25JKlDu7YYjhseOizxw,3618
@@ -33,8 +33,8 @@ psyke/utils/logic.py,sha256=7bbW6qcKof5PlqoQ0n5Kt3Obcot-KqGAvpE8rMXvEPE,12419
33
33
  psyke/utils/metrics.py,sha256=Oo5BOonOSfo0qYsXWT5dmypZ7jiStByFC2MKEU0uMHg,2250
34
34
  psyke/utils/plot.py,sha256=dE8JJ6tQ0Ezosid-r2jqAisREjFe5LqExRzsVi5Ns-c,7785
35
35
  psyke/utils/sorted.py,sha256=C3CPW2JisND30BRk5c1sAAHs3Lb_wsRB2qZrYFuRnfM,678
36
- psyke-0.8.4.dist-info/LICENSE,sha256=KP9K6Hgezf_xdMFW7ORyKz9uA8Y8k52YJn292wcP-_E,11354
37
- psyke-0.8.4.dist-info/METADATA,sha256=EVJVC6zctQogYTfCiBn1qkTar-2J7X7YiSfHom0TK0U,8102
38
- psyke-0.8.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
39
- psyke-0.8.4.dist-info/top_level.txt,sha256=q1HglxOqqoIRukFtyis_ZNHczZg4gANRUPWkD7HAUTU,6
40
- psyke-0.8.4.dist-info/RECORD,,
36
+ psyke-0.8.5.dev1.dist-info/LICENSE,sha256=KP9K6Hgezf_xdMFW7ORyKz9uA8Y8k52YJn292wcP-_E,11354
37
+ psyke-0.8.5.dev1.dist-info/METADATA,sha256=3-oyRQ9djMbYFzaoJ6CXsgEyvDvbEBNCUZdf_g8U14Y,8107
38
+ psyke-0.8.5.dev1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
39
+ psyke-0.8.5.dev1.dist-info/top_level.txt,sha256=q1HglxOqqoIRukFtyis_ZNHczZg4gANRUPWkD7HAUTU,6
40
+ psyke-0.8.5.dev1.dist-info/RECORD,,