psyke 0.8.4__py3-none-any.whl → 0.8.5__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 +7 -4
- psyke/extraction/hypercubic/__init__.py +52 -45
- psyke/extraction/hypercubic/creepy/__init__.py +1 -1
- psyke/extraction/hypercubic/hypercube.py +6 -2
- psyke/hypercubepredictor.py +3 -1
- psyke/schema/__init__.py +9 -4
- {psyke-0.8.4.dist-info → psyke-0.8.5.dist-info}/METADATA +1 -1
- {psyke-0.8.4.dist-info → psyke-0.8.5.dist-info}/RECORD +11 -11
- {psyke-0.8.4.dist-info → psyke-0.8.5.dist-info}/LICENSE +0 -0
- {psyke-0.8.4.dist-info → psyke-0.8.5.dist-info}/WHEEL +0 -0
- {psyke-0.8.4.dist-info → psyke-0.8.5.dist-info}/top_level.txt +0 -0
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
|
|
48
|
-
cube2 = self._find_cube(neighbor
|
|
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
|
|
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
|
-
|
|
80
|
-
|
|
79
|
+
|
|
80
|
+
counter_conditions = []
|
|
81
|
+
|
|
81
82
|
for _, _, _, c in cubes:
|
|
82
|
-
if c.output not in
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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]] = {}
|
psyke/hypercubepredictor.py
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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:
|
|
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 (
|
|
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 (
|
|
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,5 +1,5 @@
|
|
|
1
|
-
psyke/__init__.py,sha256=
|
|
2
|
-
psyke/hypercubepredictor.py,sha256=
|
|
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=
|
|
11
|
-
psyke/extraction/hypercubic/hypercube.py,sha256=
|
|
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=
|
|
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=
|
|
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.
|
|
37
|
-
psyke-0.8.
|
|
38
|
-
psyke-0.8.
|
|
39
|
-
psyke-0.8.
|
|
40
|
-
psyke-0.8.
|
|
36
|
+
psyke-0.8.5.dist-info/LICENSE,sha256=KP9K6Hgezf_xdMFW7ORyKz9uA8Y8k52YJn292wcP-_E,11354
|
|
37
|
+
psyke-0.8.5.dist-info/METADATA,sha256=5BPTGj6knIWcdwIdQZaXQTPAsl4CLMADsXHZNU9J10w,8102
|
|
38
|
+
psyke-0.8.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
39
|
+
psyke-0.8.5.dist-info/top_level.txt,sha256=q1HglxOqqoIRukFtyis_ZNHczZg4gANRUPWkD7HAUTU,6
|
|
40
|
+
psyke-0.8.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|