model-agnostic-toolkit 1.0.0__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.
- model_agnostic_toolkit/__init__.py +11 -0
- model_agnostic_toolkit/analyzer.py +556 -0
- model_agnostic_toolkit/datasets/__init__.py +8 -0
- model_agnostic_toolkit/datasets/classification_datasets.py +520 -0
- model_agnostic_toolkit/datasets/dataset.py +231 -0
- model_agnostic_toolkit/datasets/development_datasets.py +116 -0
- model_agnostic_toolkit/datasets/interaction_datasets.py +410 -0
- model_agnostic_toolkit/datasets/multiclassification_datasets.py +523 -0
- model_agnostic_toolkit/datasets/multiplication_datasets.py +363 -0
- model_agnostic_toolkit/datasets/preloaded_dataset.py +58 -0
- model_agnostic_toolkit/datasets/regression_datasets.py +500 -0
- model_agnostic_toolkit/model.py +341 -0
- model_agnostic_toolkit/plots/__init__.py +9 -0
- model_agnostic_toolkit/plots/ale_plots.py +145 -0
- model_agnostic_toolkit/plots/importance_plots.py +66 -0
- model_agnostic_toolkit/plots/interaction_plots.py +301 -0
- model_agnostic_toolkit/plots/model_plots.py +77 -0
- model_agnostic_toolkit/plots/pdp_plots.py +134 -0
- model_agnostic_toolkit/plots/pfi_plots.py +48 -0
- model_agnostic_toolkit/plots/shap_plots.py +140 -0
- model_agnostic_toolkit/plots/utils.py +232 -0
- model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/__init__.py +1 -0
- model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/dsa.py +115 -0
- model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/dsa_calc_utils.py +56 -0
- model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/dsa_utils.py +169 -0
- model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/utils.py +114 -0
- model_agnostic_toolkit/tools/__init__.py +12 -0
- model_agnostic_toolkit/tools/ale/__init__.py +2 -0
- model_agnostic_toolkit/tools/ale/ale_importance.py +81 -0
- model_agnostic_toolkit/tools/ale/ale_interaction.py +109 -0
- model_agnostic_toolkit/tools/h_statistic/__init__.py +1 -0
- model_agnostic_toolkit/tools/h_statistic/h_statistic_interaction.py +252 -0
- model_agnostic_toolkit/tools/pdp/__init__.py +2 -0
- model_agnostic_toolkit/tools/pdp/pdp_importance.py +115 -0
- model_agnostic_toolkit/tools/pdp/pdp_interaction.py +232 -0
- model_agnostic_toolkit/tools/pfi/__init__.py +1 -0
- model_agnostic_toolkit/tools/pfi/pfi_importance.py +313 -0
- model_agnostic_toolkit/tools/shap/__init__.py +1 -0
- model_agnostic_toolkit/tools/shap/shap_importance.py +116 -0
- model_agnostic_toolkit/tools/tool.py +435 -0
- model_agnostic_toolkit/tools/utils.py +185 -0
- model_agnostic_toolkit/tools/vip/__init__.py +2 -0
- model_agnostic_toolkit/tools/vip/vip_importance.py +69 -0
- model_agnostic_toolkit/tools/vip/vip_interaction.py +70 -0
- model_agnostic_toolkit/tools/vip/vip_utils.py +39 -0
- model_agnostic_toolkit/types.py +27 -0
- model_agnostic_toolkit-1.0.0.dist-info/METADATA +43 -0
- model_agnostic_toolkit-1.0.0.dist-info/RECORD +50 -0
- model_agnostic_toolkit-1.0.0.dist-info/WHEEL +4 -0
- model_agnostic_toolkit-1.0.0.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Analyzer: # forward declaration
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
# do not change import ordering
|
|
5
|
+
from .model import Model, PreloadedModel
|
|
6
|
+
from .analyzer import Analyzer, ImportanceAnalyzer, InteractionAnalyzer
|
|
7
|
+
from . import types
|
|
8
|
+
from . import datasets
|
|
9
|
+
from . import plots
|
|
10
|
+
from . import tools
|
|
11
|
+
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from typing import Any, Dict, List, Tuple, Type, Union
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from plotly.io import to_json
|
|
6
|
+
|
|
7
|
+
from . import Analyzer # forward declaration to be overwritten
|
|
8
|
+
from . import Model
|
|
9
|
+
from .datasets import Dataset
|
|
10
|
+
from .tools import ImportanceTool, InteractionTool, Tool
|
|
11
|
+
from .types import DataType, ToolType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Analyzer:
|
|
15
|
+
"""Analyzer handling a model and a dataset and managing all tools to use for analysis.
|
|
16
|
+
Provides functions to run analyses with all these tools and stores the computed results.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
tool_type (ToolType): Type of tools used by this analyzer.
|
|
20
|
+
model (Model): Model to run the analysis with.
|
|
21
|
+
dataset (Dataset): Dataset to run the analysis for.
|
|
22
|
+
tools: Dict[str, Tool]: Dictionary storing all tools for analysis.
|
|
23
|
+
results: Dict[str, Dict]: Dictionary storing all results computed with the analyzer's tools.
|
|
24
|
+
reuse_analyzers (List[Analyzer]): List of analyzers from whose tools results can be reused.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, tool_type: ToolType, model: Model, dataset: Dataset, tools: Union[List[Tool], Tool] = None,
|
|
28
|
+
reuse_analyzers: Union[List[Analyzer], Analyzer] = None):
|
|
29
|
+
"""Initializes analyzer for given model and dataset with given tools.
|
|
30
|
+
Sets up dictionaries for managing tools and results.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
tool_type (ToolType): Type of tools to use for the analysis (feature importance or interaction tools).
|
|
34
|
+
model (Model): Model to run the analysis with.
|
|
35
|
+
dataset (Dataset): Dataset to run the analysis for.
|
|
36
|
+
tools (Union[List[Tool], Tool], optional): List of tools to execute the analysis with. Defaults to None.
|
|
37
|
+
reuse_analyzers (Union[List[Analyzer], Analyzer], optional): List of analyzers from whose tools results can be reused. Defaults to None
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# set attributes
|
|
41
|
+
self.tool_type = tool_type
|
|
42
|
+
self.model = model
|
|
43
|
+
self.dataset = dataset
|
|
44
|
+
self.classindex = None
|
|
45
|
+
|
|
46
|
+
if self.dataset.data_type == DataType.CLASSIFICATION:
|
|
47
|
+
self._check_classes()
|
|
48
|
+
|
|
49
|
+
# initialize tool and result dictionaries
|
|
50
|
+
self.tools = {}
|
|
51
|
+
self.results = {}
|
|
52
|
+
self._temp_results = {}
|
|
53
|
+
if tools is not None:
|
|
54
|
+
self.add_tools(tools)
|
|
55
|
+
|
|
56
|
+
# make given analyzers' and own tools' results reusable
|
|
57
|
+
self.reuse_analyzers = [self]
|
|
58
|
+
if reuse_analyzers is not None:
|
|
59
|
+
self.add_reuse_analyzers(reuse_analyzers)
|
|
60
|
+
|
|
61
|
+
# default run settings
|
|
62
|
+
self._run_reuse_tools = True
|
|
63
|
+
self._run_force = False
|
|
64
|
+
|
|
65
|
+
def add_tools(self, tools: Union[List[Tool], Tool], force: bool = False):
|
|
66
|
+
"""Adds the specified tools to the analyzer's list of tools.
|
|
67
|
+
These tools are additionally run when executing the analysis.
|
|
68
|
+
Preexisting tools are not overwritten, except when the `force` keyword is set to `True`.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
tools (Union[List[Tool], Tool]): List of tools to add for the analysis.
|
|
72
|
+
force (bool, optional): Option to overwrite existing tools if True. Defaults to False.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# check tools before adding
|
|
76
|
+
tools = self._check_tools(tools, expected_type=Tool)
|
|
77
|
+
|
|
78
|
+
# add tools to tools dictionary
|
|
79
|
+
for tool in tools:
|
|
80
|
+
if tool.tool_type == self.tool_type:
|
|
81
|
+
if tool.name not in self.tools.keys() or force:
|
|
82
|
+
self.tools[tool.name] = tool
|
|
83
|
+
self.results[tool.name] = {}
|
|
84
|
+
self._temp_results[tool.name] = {}
|
|
85
|
+
else:
|
|
86
|
+
warnings.warn('Tool {name} already exists in analyzer. Skipping.'
|
|
87
|
+
.format(name=tool.name), RuntimeWarning)
|
|
88
|
+
else:
|
|
89
|
+
warnings.warn('Tool {name} has wrong tool type {tool_type}. Expected tool type {exp_type}. Skipping.'
|
|
90
|
+
.format(name=tool.name, tool_type=tool.tool_type.value, exp_type=self.tool_type.value),
|
|
91
|
+
RuntimeWarning)
|
|
92
|
+
|
|
93
|
+
def remove_tools(self, tools: Union[List[str], str]):
|
|
94
|
+
"""Removes the specified tools from the analyzer's list of tools.
|
|
95
|
+
Also removes any associated results already computed and stored by the analyzer.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
tools (Union[List[str], str]): List of tools to remove from the analyzer, specified by their name.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# check tools before removing
|
|
102
|
+
tools = self._check_tools(tools, expected_type=str)
|
|
103
|
+
|
|
104
|
+
# remove tools from dictionaries
|
|
105
|
+
for tool in tools:
|
|
106
|
+
if tool in self.tools.keys():
|
|
107
|
+
self.tools.pop(tool)
|
|
108
|
+
self.results.pop(tool)
|
|
109
|
+
self._temp_results.pop(tool)
|
|
110
|
+
|
|
111
|
+
def add_reuse_analyzers(self, analyzers: Union[List[Analyzer], Analyzer]):
|
|
112
|
+
"""Adds the specified analyzers to the list of analyzers to reuse results from.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
analyzers (Union[List[Analyzer], Analyzer]): List of analyzers to add for reuse.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
if not isinstance(analyzers, list):
|
|
119
|
+
analyzers = [analyzers]
|
|
120
|
+
|
|
121
|
+
# add analyzers to reuse analyzer list
|
|
122
|
+
for analyzer in analyzers:
|
|
123
|
+
if isinstance(analyzer, Analyzer):
|
|
124
|
+
if analyzer.dataset == self.dataset:
|
|
125
|
+
self.reuse_analyzers.append(analyzer)
|
|
126
|
+
if analyzer.model != self.model:
|
|
127
|
+
warnings.warn('Passed reuse analyzer uses different model {model} instead of {exp_model}.'
|
|
128
|
+
.format(model=type(analyzer.model.model), exp_model=type(self.model.model)),
|
|
129
|
+
RuntimeWarning)
|
|
130
|
+
else:
|
|
131
|
+
warnings.warn('Passed reuse analyzer uses different dataset {data}. Expected {exp_data}. Skipping.'
|
|
132
|
+
.format(data=analyzer.dataset.name, exp_data=self.dataset.name), RuntimeWarning)
|
|
133
|
+
else:
|
|
134
|
+
warnings.warn('Passed reuse analyzer has wrong type {obj_type}. Expected type {ana_type}. Skipping.'
|
|
135
|
+
.format(obj_type=type(analyzer), ana_type=Analyzer), RuntimeWarning)
|
|
136
|
+
|
|
137
|
+
def fit_model(self, force: bool = False, *args, **kwargs):
|
|
138
|
+
"""Fits the underlying model to training data if it is not already fitted.
|
|
139
|
+
Also evaluates the model.
|
|
140
|
+
|
|
141
|
+
Positional and keyworded arguments are passed to the model's fit function.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
force (bool, optional): Option to force the fitting of the model if True, even if it is already fitted. Defaults to False.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
# check if model is fitted, otherwise fit
|
|
148
|
+
if not self.model.is_fitted() or force:
|
|
149
|
+
x_train, y_train = self.dataset.get_train()
|
|
150
|
+
self.model.fit(x_train, y_train, *args, **kwargs)
|
|
151
|
+
if 'model' in self.results.keys():
|
|
152
|
+
self.results.pop('model')
|
|
153
|
+
self.evaluate_model(force)
|
|
154
|
+
|
|
155
|
+
def evaluate_model(self, force: bool = False) -> Dict:
|
|
156
|
+
"""Evaluates the underlying model if it has not been evaluated already and stores the results.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
force (bool, optional): Option to force a reevaluation of the model. Defaults to False.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Dict: Results of the model evaluation for training and testing data.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
# check if model has been evaluated, otherwise evaluate
|
|
166
|
+
if 'model' not in self.results.keys() or force:
|
|
167
|
+
# store results
|
|
168
|
+
self.results['model'] = self.model.evaluate(self.dataset)
|
|
169
|
+
|
|
170
|
+
return self.results['model']
|
|
171
|
+
|
|
172
|
+
def run_tool(self, tool: Union[Tool, str], features: Union[List[str], List[Tuple[str, str]]] = None,
|
|
173
|
+
fit: bool = True, reuse_tools: bool = True, force: bool = False, temp: bool = False) -> Dict:
|
|
174
|
+
"""Runs an analysis with the specified tool for the given features and stores the computed results.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
tool (Union[Tool, str]): Tool to run the analysis with.
|
|
178
|
+
features (Union[List[str], List[Tuple[str, str]]]): List of features to compute the analysis results for.
|
|
179
|
+
fit (bool, optional): Whether to fit the model before running the analysis if it is not already fitted. Defaults to True.
|
|
180
|
+
reuse_tools (bool, optional): Whether to reuse existing tool's results for computations of this tool. Kept at previous value if `temp` is True.
|
|
181
|
+
force (bool, optional): Option to ignore existing results and force the analysis to run again and also fit the model again if True. Model fitting is excluded if `fit` is False. Kept at previous value if `temp` is True. Defaults to False.
|
|
182
|
+
temp (bool, optional): Whether the tool is run for temporary intermediate results for other tools. If True, results are saved in `self._temp_results` instead of `self.results`. Defaults to False.
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
KeyError: Raised if the specified tool is not in the analyzer's list of tools.
|
|
186
|
+
TypeError: Raised if the specified tool is not of type str or Tool.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Dict: All computed analysis results for the specified tool.
|
|
190
|
+
"""
|
|
191
|
+
result = None
|
|
192
|
+
missing_features, res_dict, tool = self._initialize_run_tool(features, fit, force, reuse_tools, temp, tool)
|
|
193
|
+
|
|
194
|
+
# run tool and check result
|
|
195
|
+
if self._run_force:
|
|
196
|
+
missing_features = features
|
|
197
|
+
if missing_features:
|
|
198
|
+
result = tool.run(self.model, self.dataset, missing_features, self)
|
|
199
|
+
if not temp:
|
|
200
|
+
tool._check_result_format(result, missing_features)
|
|
201
|
+
|
|
202
|
+
# empty results if needed and save new results
|
|
203
|
+
if self._run_force and not temp:
|
|
204
|
+
res_dict[tool.name] = {}
|
|
205
|
+
if result:
|
|
206
|
+
res_dict[tool.name].update(result)
|
|
207
|
+
return res_dict[tool.name]
|
|
208
|
+
|
|
209
|
+
def _initialize_run_tool(self, features, fit, force, reuse_tools, temp, tool):
|
|
210
|
+
# check passed tool
|
|
211
|
+
if not isinstance(tool, Tool):
|
|
212
|
+
if not isinstance(tool, str):
|
|
213
|
+
raise TypeError('Passed tool must be of type {type_1} or {type_2}. Got {object_type} instead.'
|
|
214
|
+
.format(type_1=str, type_2=Tool, object_type=type(tool)))
|
|
215
|
+
# find tool by name
|
|
216
|
+
if tool in self.tools.keys():
|
|
217
|
+
tool = self.tools[tool]
|
|
218
|
+
else:
|
|
219
|
+
raise KeyError('Tool {tool} could not be found in tools.'
|
|
220
|
+
.format(tool=tool))
|
|
221
|
+
# set flags to force new computation and to reuse tools in this run if desired
|
|
222
|
+
if not temp:
|
|
223
|
+
self._run_force = force
|
|
224
|
+
self._run_reuse_tools = reuse_tools
|
|
225
|
+
# choose correct results dict (temp or not)
|
|
226
|
+
res_dict = self._temp_results if temp else self.results
|
|
227
|
+
other_res_dict = self.results if temp else self._temp_results
|
|
228
|
+
# check for missing and elsewhere existing results and filter features
|
|
229
|
+
features = self._check_features(features)
|
|
230
|
+
missing_features = [f for f in features if not self.result_exists(tool, f, self._run_force, temp)]
|
|
231
|
+
existing_features = [f for f in missing_features if self.result_exists(tool, f, self._run_force, (not temp))]
|
|
232
|
+
# copy results existing in self._temp_results to self.results or vice versa
|
|
233
|
+
for f in existing_features:
|
|
234
|
+
res_dict[tool.name][f] = other_res_dict[tool.name][f]
|
|
235
|
+
missing_features.pop(missing_features.index(f))
|
|
236
|
+
# fit model if needed
|
|
237
|
+
if fit and not temp:
|
|
238
|
+
self.fit_model(self._run_force)
|
|
239
|
+
|
|
240
|
+
return missing_features, res_dict, tool
|
|
241
|
+
|
|
242
|
+
def run_analysis(self, features: Union[List[str], List[Tuple[str, str]]], fit: bool = True,
|
|
243
|
+
reuse_tools: bool = True, raise_tool_errors: bool = False, force: bool = False):
|
|
244
|
+
"""Executes the analysis for all specified features with every tool added to the analyzer.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
features (Union[List[str], List[Tuple[str, str]]]): List of features to compute the analysis results for.
|
|
248
|
+
fit (bool, optional): Whether to fit the model before running the analysis if it is not already fitted. Defaults to True.
|
|
249
|
+
reuse_tools (bool, optional): Whether to reuse existing tool's results for computations of other tools.
|
|
250
|
+
raise_tool_errors (bool, optional): Option to raise errors that occur when running tools if True. Else, only a warning will be issued. Defaults to False.
|
|
251
|
+
force (bool, optional): Option to ignore existing results and force the analysis to run again and also fit the model again for every tool if True. Model fitting is excluded if `fit` is False. Defaults to False.
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
TypeError: Raised if the specified tool type is not of type ToolType.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Dict[str, Dict]: All computed analysis results from all tools. Includes results for features specified in prior executions of this function as well as model results.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
# run every tool
|
|
261
|
+
|
|
262
|
+
for tool in self.tools.values():
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
self.run_tool(tool, features, fit, reuse_tools, force)
|
|
266
|
+
except Exception as e:
|
|
267
|
+
if raise_tool_errors:
|
|
268
|
+
raise e
|
|
269
|
+
else:
|
|
270
|
+
warnings.warn(('An error occurred while running tool {tool}:\n' + str(e) + '\nSkipping.')
|
|
271
|
+
.format(tool=type(tool)), RuntimeWarning)
|
|
272
|
+
|
|
273
|
+
def plot_results(self, class_name=None, include_model: bool = True, plot_config: Dict = None):
|
|
274
|
+
"""Plots the model performance and results for all tools.
|
|
275
|
+
Each tools `plot` method is successively called here.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
class_name (Any, optional): Class to display. Only relevant for classification. Defaults to None.
|
|
279
|
+
include_model (bool, optional): Whether to include a plot with model performance metrics. Defaults to True.
|
|
280
|
+
plot_config (Dict, optional): Configuration dictionary for Plotly's `show()` method ([reference](https://plotly.com/python/configuration-options/)). Defaults to None.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
if self.model.data_type == DataType.CLASSIFICATION and not class_name:
|
|
284
|
+
class_name = self.model.classes[0]
|
|
285
|
+
|
|
286
|
+
self._set_class_index(class_name)
|
|
287
|
+
|
|
288
|
+
# plot model performance metrics
|
|
289
|
+
if include_model:
|
|
290
|
+
if 'model' in self.results.keys():
|
|
291
|
+
self.model.plot(self.results['model'])
|
|
292
|
+
else:
|
|
293
|
+
print('No results for model performance available.')
|
|
294
|
+
|
|
295
|
+
# tool specific plots
|
|
296
|
+
for tool in self.tools.values():
|
|
297
|
+
if self.results[tool.name] == {}:
|
|
298
|
+
print(f'No results for tool {tool.name} available.')
|
|
299
|
+
|
|
300
|
+
elif self.dataset.data_type == DataType.CLASSIFICATION:
|
|
301
|
+
new_results = None
|
|
302
|
+
keys = self.results[tool.name].keys()
|
|
303
|
+
for _ in keys:
|
|
304
|
+
if self.results[tool.name][self.classindex]['class_label'] == class_name:
|
|
305
|
+
new_results = self.results[tool.name][self.classindex]['result']
|
|
306
|
+
if not new_results:
|
|
307
|
+
class_name = self.model.classes[0]
|
|
308
|
+
warnings.warn(f'The requested class is not available showing results for class {class_name}')
|
|
309
|
+
new_results = self.results[tool.name][0]['result']
|
|
310
|
+
name = f"{tool.name} for class {class_name}"
|
|
311
|
+
tool.plot(new_results, title=name, plot_config=plot_config)
|
|
312
|
+
else:
|
|
313
|
+
tool.plot(self.results[tool.name], plot_config=plot_config)
|
|
314
|
+
|
|
315
|
+
def export_plots(
|
|
316
|
+
self, export_format: str = 'json', class_name=None, plot_config: Dict = None, layout: Dict = None):
|
|
317
|
+
"""Exports the plot of the results for all tools.
|
|
318
|
+
Each tools `plot` method is successively called here.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
export_format (str, optional): Export format. Either 'json' (json string of the plotly graphs) or 'html'(html div of the plotly graphs). Defaults to 'json'.
|
|
322
|
+
class_name (Any, optional): Class to display. Only relevant for classification. Defaults to None.
|
|
323
|
+
plot_config (Dict, optional): Configuration dictionary for Plotly's `show()` method ([reference](https://plotly.com/python/configuration-options/)). Defaults to None.
|
|
324
|
+
layout (Dict, optional): Layout dictionary for Plotly ([reference](https://plotly.com/python-api-reference/generated/plotly.graph_objects.Layout.html#plotly.graph_objects.Layout)). Defaults to None.
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
ValueError: Raised if the export_format is not supported.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
List: List of the plot's JSON representations
|
|
331
|
+
"""
|
|
332
|
+
export = {}
|
|
333
|
+
|
|
334
|
+
self._set_class_index(class_name)
|
|
335
|
+
if export_format not in {'json', 'html'}:
|
|
336
|
+
raise ValueError(f'Export export_format {export_format} is not supported. '
|
|
337
|
+
f'Supported formats are "json" and "html").')
|
|
338
|
+
|
|
339
|
+
# tool specific plots
|
|
340
|
+
for tool in self.tools.values():
|
|
341
|
+
plot = self._export_plot(layout, plot_config, tool)
|
|
342
|
+
# add tool results plots to export
|
|
343
|
+
export[tool.name] = to_json(plot) if export_format == 'json' else (
|
|
344
|
+
plot.to_html(include_plotlyjs=False, full_html=False, config=plot_config))
|
|
345
|
+
|
|
346
|
+
return export
|
|
347
|
+
|
|
348
|
+
def result_exists(self, tool: Tool, feature: Union[str, Tuple[str, str]], force: bool = False,
|
|
349
|
+
temp: bool = False) -> bool:
|
|
350
|
+
"""Checks whether a result has already been computed for the given tool and feature.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
tool (Tool): Tool to check result existence for.
|
|
354
|
+
feature (Union[str, Tuple[str, str]]): Feature to check result existence for.
|
|
355
|
+
force (bool, optional): Option to ignore existing results if True. Defaults to False.
|
|
356
|
+
temp (bool, optional): Whether to look in `self._temp_results` instead of `self.results`.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
bool: Whether a result has already been computed.
|
|
360
|
+
"""
|
|
361
|
+
if force:
|
|
362
|
+
return False
|
|
363
|
+
elif not temp and feature in self.results[tool.name].keys():
|
|
364
|
+
return True
|
|
365
|
+
elif temp and feature in self._temp_results[tool.name].keys():
|
|
366
|
+
return True
|
|
367
|
+
else:
|
|
368
|
+
return False
|
|
369
|
+
|
|
370
|
+
def _find_reuse_tool(self, tool_class: Type[Tool], model: Model, dataset: Dataset, **requirements) -> Union[
|
|
371
|
+
Tuple[Analyzer, Tool], Tuple[None, None]]:
|
|
372
|
+
"""Looks for existing tools in `self.reuse_analyzers` that fulfill requirements and can be reused for intermediate result in other tools.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
tool_class (Type[Tool]): Uninitialized class of tool to find an instances of for reusing.
|
|
376
|
+
model(Model): Model to check compatibility with.
|
|
377
|
+
dataset(Dataset): Dataset to check compatibility with.
|
|
378
|
+
**requirements: Keyworded arguments with attributes to be checked in a found tool.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
Tuple[Analyzer, Tool]: Found analyzer and its tool for reusing.
|
|
382
|
+
"""
|
|
383
|
+
if self._run_reuse_tools and not self._run_force:
|
|
384
|
+
for analyzer in self.reuse_analyzers:
|
|
385
|
+
if model == analyzer.model and dataset == analyzer.dataset:
|
|
386
|
+
for tool in analyzer.tools.values():
|
|
387
|
+
if isinstance(tool, tool_class):
|
|
388
|
+
fine = True
|
|
389
|
+
for key, value in requirements.items():
|
|
390
|
+
fine = fine and (getattr(tool, key) == value)
|
|
391
|
+
if not fine:
|
|
392
|
+
break
|
|
393
|
+
if fine:
|
|
394
|
+
reuse_analyzer = analyzer
|
|
395
|
+
reuse_tool = tool
|
|
396
|
+
|
|
397
|
+
return reuse_analyzer, reuse_tool
|
|
398
|
+
|
|
399
|
+
return None, None
|
|
400
|
+
|
|
401
|
+
def _check_features(self, features: Any) -> Union[List[str], List[Tuple[str, str]]]:
|
|
402
|
+
"""Checks the export_format of the passed feature of feature list with respect to the analyzer's tool type.
|
|
403
|
+
Adjusts the output export_format if needed and possible.
|
|
404
|
+
The expected export_format is a str or List[str] for feature importance tools and a Tuple[str, str] or List[Tuple[str, str]] for feature interaction tools.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
features (Any): Feature or feature list to check the export_format of.
|
|
408
|
+
|
|
409
|
+
Raises:
|
|
410
|
+
TypeError: Raised if at least one feature is not a str or tuple.
|
|
411
|
+
ValueError: Raised if a passed tuple does not have the form Tuple[str, str].
|
|
412
|
+
TypeError: Raised if something other than a str is used for an importance tool.
|
|
413
|
+
TypeError: Raised if something other than a tuple is used for an interaction tool.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Union[List[str], List[Tuple[str, str]]]: List of checked and possibly adjusted features or feature tuples.
|
|
417
|
+
"""
|
|
418
|
+
# create list if needed
|
|
419
|
+
if not isinstance(features, list):
|
|
420
|
+
features = [features]
|
|
421
|
+
|
|
422
|
+
for feature in features:
|
|
423
|
+
# check for string or tuple types
|
|
424
|
+
if not (isinstance(feature, (str, tuple))):
|
|
425
|
+
raise TypeError('Passed features must be of type {type_1} or {type_2}. Got {object_type} instead.'
|
|
426
|
+
.format(type_1=str, type_2=tuple, object_type=type(feature)))
|
|
427
|
+
# if tuple, check length and types
|
|
428
|
+
if isinstance(feature, tuple) and [type(f) for f in feature] != [str, str, ]:
|
|
429
|
+
raise ValueError(
|
|
430
|
+
'Passed feature tuples must have the form {exp_form}. Got {form} instead.'.format(
|
|
431
|
+
exp_form=(str, str), form=tuple(type(f) for f in feature)
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
# check if feature type (string or tuple) corresponds to tool type
|
|
435
|
+
if self.tool_type == ToolType.IMPORTANCE and not isinstance(feature, str):
|
|
436
|
+
raise TypeError('Features for {tool_name} must be of type {expected_type}. Got {object_type} instead.'
|
|
437
|
+
.format(tool_name=self.tool_type.value, expected_type=str, object_type=type(feature)))
|
|
438
|
+
elif self.tool_type is ToolType.INTERACTION and not isinstance(feature, tuple):
|
|
439
|
+
raise TypeError('Features for {tool_name} must be of type {expected_type}. Got {object_type} instead.'
|
|
440
|
+
.format(tool_name=self.tool_type.value, expected_type=tuple, object_type=type(feature)))
|
|
441
|
+
|
|
442
|
+
return features
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def _check_tools(tools: Any, expected_type: Union[Type[Tool], Type[str]]) -> Union[List[Tool], List[str]]:
|
|
446
|
+
"""Checks the type of the passed tool or tool list with respect to the expected type str or Tool.
|
|
447
|
+
Adjusts the output export_format if needed and possible.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
tools (Any): Tool or list of tools to check the type of.
|
|
451
|
+
expected_type (Union[type[Tool], type[str]]): Expected type for the specified tools (either str or Tool).
|
|
452
|
+
|
|
453
|
+
Raises:
|
|
454
|
+
TypeError: Raised if at least one tool does not have the expected type.
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
Union[List[Tool], List[str]]: List of checked and possibly adjusted tools with the expected type.
|
|
458
|
+
"""
|
|
459
|
+
# create list if needed
|
|
460
|
+
if not isinstance(tools, list):
|
|
461
|
+
tools = [tools]
|
|
462
|
+
|
|
463
|
+
for tool in tools:
|
|
464
|
+
# check for correct type of tools
|
|
465
|
+
if isinstance(tool, Tool) and expected_type == str:
|
|
466
|
+
tool = tool.name
|
|
467
|
+
if not isinstance(tool, expected_type):
|
|
468
|
+
raise TypeError('Passed tools must be of type {expected_type}. Got {object_type} instead.'
|
|
469
|
+
.format(expected_type=expected_type, object_type=type(tool)))
|
|
470
|
+
|
|
471
|
+
return tools
|
|
472
|
+
|
|
473
|
+
def _check_classes(self):
|
|
474
|
+
"""Checks whether all classes present in y_test are also present in y_train
|
|
475
|
+
|
|
476
|
+
Raises:
|
|
477
|
+
ValueError: If there are classes in y_test that are not present in y_train
|
|
478
|
+
"""
|
|
479
|
+
_, y_train = self.dataset.get_train()
|
|
480
|
+
_, y_test = self.dataset.get_test()
|
|
481
|
+
if not set(np.unique(y_test)).issubset(np.unique(y_train)):
|
|
482
|
+
raise ValueError('All classes present in y_test must be present in y_train.')
|
|
483
|
+
|
|
484
|
+
def _set_class_index(self, class_name):
|
|
485
|
+
"""
|
|
486
|
+
Set the index of the specified class in the model.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
class_name (str): The name of the class to set the index for.
|
|
490
|
+
|
|
491
|
+
Raises:
|
|
492
|
+
Warning: If the specified class is not found in the model.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
None
|
|
496
|
+
"""
|
|
497
|
+
self.classindex = None
|
|
498
|
+
if self.dataset.data_type == DataType.CLASSIFICATION:
|
|
499
|
+
if class_name is None:
|
|
500
|
+
self.classindex = 0
|
|
501
|
+
else:
|
|
502
|
+
classes = self.model.classes
|
|
503
|
+
for class_index, class_label in enumerate(classes):
|
|
504
|
+
if class_label == class_name:
|
|
505
|
+
warnings.warn('Class {name} is found'.format(name=class_name), category=Warning)
|
|
506
|
+
self.classindex = class_index
|
|
507
|
+
if self.classindex is None:
|
|
508
|
+
message = (f'Class {class_name} is not found, check name and type. '
|
|
509
|
+
f'Currently showing class {classes[0]}')
|
|
510
|
+
warnings.warn(message, category=Warning)
|
|
511
|
+
print(message)
|
|
512
|
+
self.classindex = 0
|
|
513
|
+
|
|
514
|
+
def _export_plot(self, layout, plot_config, tool):
|
|
515
|
+
"""
|
|
516
|
+
Export a plot based on the provided layout, plot configuration, and tool.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
layout (dict): The layout configuration for the plot.
|
|
520
|
+
plot_config (dict): The configuration settings for the plot.
|
|
521
|
+
tool: The tool used to generate the plot.
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
plotly.graph_objs.Figure: The exported plot.
|
|
525
|
+
"""
|
|
526
|
+
if self.results[tool.name] == {}:
|
|
527
|
+
print(f'No results for tool {tool.name} available.')
|
|
528
|
+
plot = None
|
|
529
|
+
|
|
530
|
+
elif self.dataset.data_type == DataType.CLASSIFICATION:
|
|
531
|
+
new_results = self.results[tool.name][self.classindex]['result']
|
|
532
|
+
class_name = self.results[tool.name][self.classindex]['class_label']
|
|
533
|
+
name = f"{tool.name} for class {class_name}"
|
|
534
|
+
plot = tool.export_plot(new_results, title=name, plot_config=plot_config)
|
|
535
|
+
else:
|
|
536
|
+
plot = tool.export_plot(self.results[tool.name], plot_config=plot_config)
|
|
537
|
+
plot.update_layout(dict1=layout, overwrite=True)
|
|
538
|
+
return plot
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class ImportanceAnalyzer(Analyzer):
|
|
542
|
+
"""Analyzer for feature importance analysis.
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
def __init__(self, model: Model, dataset: Dataset, tools: Union[List[ImportanceTool], ImportanceTool] = None,
|
|
546
|
+
**kwargs):
|
|
547
|
+
super().__init__(tool_type=ToolType.IMPORTANCE, model=model, dataset=dataset, tools=tools, **kwargs)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class InteractionAnalyzer(Analyzer):
|
|
551
|
+
"""Analyzer for feature interaction analysis.
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
def __init__(self, model: Model, dataset: Dataset, tools: Union[List[InteractionTool], InteractionTool] = None,
|
|
555
|
+
**kwargs):
|
|
556
|
+
super().__init__(tool_type=ToolType.INTERACTION, model=model, dataset=dataset, tools=tools, **kwargs)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from .dataset import Dataset, ArtificialDataset
|
|
2
|
+
from .preloaded_dataset import PreloadedDataset
|
|
3
|
+
from .development_datasets import DevelopmentDataset, DevelopmentBaseDataset
|
|
4
|
+
from .interaction_datasets import InteractionOneDataset, InteractionTwoDataset, InteractionThreeDataset, InteractionFourDataset
|
|
5
|
+
from .multiplication_datasets import MultiplicationOneDataset, MultiplicationTwoDataset, MultiplicationThreeDataset, MultiplicationFourDataset
|
|
6
|
+
from .regression_datasets import RegressionOneDataset, RegressionTwoDataset, RegressionThreeDataset, RegressionFourDataset
|
|
7
|
+
from .classification_datasets import ClassificationOneDataset, ClassificationTwoDataset, ClassificationThreeDataset, ClassificationFourDataset
|
|
8
|
+
from .multiclassification_datasets import MultiClassificationOneDataset, MultiClassificationTwoDataset, MultiClassificationThreeDataset, MultiClassificationFourDataset
|