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.
Files changed (50) hide show
  1. model_agnostic_toolkit/__init__.py +11 -0
  2. model_agnostic_toolkit/analyzer.py +556 -0
  3. model_agnostic_toolkit/datasets/__init__.py +8 -0
  4. model_agnostic_toolkit/datasets/classification_datasets.py +520 -0
  5. model_agnostic_toolkit/datasets/dataset.py +231 -0
  6. model_agnostic_toolkit/datasets/development_datasets.py +116 -0
  7. model_agnostic_toolkit/datasets/interaction_datasets.py +410 -0
  8. model_agnostic_toolkit/datasets/multiclassification_datasets.py +523 -0
  9. model_agnostic_toolkit/datasets/multiplication_datasets.py +363 -0
  10. model_agnostic_toolkit/datasets/preloaded_dataset.py +58 -0
  11. model_agnostic_toolkit/datasets/regression_datasets.py +500 -0
  12. model_agnostic_toolkit/model.py +341 -0
  13. model_agnostic_toolkit/plots/__init__.py +9 -0
  14. model_agnostic_toolkit/plots/ale_plots.py +145 -0
  15. model_agnostic_toolkit/plots/importance_plots.py +66 -0
  16. model_agnostic_toolkit/plots/interaction_plots.py +301 -0
  17. model_agnostic_toolkit/plots/model_plots.py +77 -0
  18. model_agnostic_toolkit/plots/pdp_plots.py +134 -0
  19. model_agnostic_toolkit/plots/pfi_plots.py +48 -0
  20. model_agnostic_toolkit/plots/shap_plots.py +140 -0
  21. model_agnostic_toolkit/plots/utils.py +232 -0
  22. model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/__init__.py +1 -0
  23. model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/dsa.py +115 -0
  24. model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/dsa_calc_utils.py +56 -0
  25. model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/dsa_utils.py +169 -0
  26. model_agnostic_toolkit/tools/GlobalSensitivityAnalysis/utils.py +114 -0
  27. model_agnostic_toolkit/tools/__init__.py +12 -0
  28. model_agnostic_toolkit/tools/ale/__init__.py +2 -0
  29. model_agnostic_toolkit/tools/ale/ale_importance.py +81 -0
  30. model_agnostic_toolkit/tools/ale/ale_interaction.py +109 -0
  31. model_agnostic_toolkit/tools/h_statistic/__init__.py +1 -0
  32. model_agnostic_toolkit/tools/h_statistic/h_statistic_interaction.py +252 -0
  33. model_agnostic_toolkit/tools/pdp/__init__.py +2 -0
  34. model_agnostic_toolkit/tools/pdp/pdp_importance.py +115 -0
  35. model_agnostic_toolkit/tools/pdp/pdp_interaction.py +232 -0
  36. model_agnostic_toolkit/tools/pfi/__init__.py +1 -0
  37. model_agnostic_toolkit/tools/pfi/pfi_importance.py +313 -0
  38. model_agnostic_toolkit/tools/shap/__init__.py +1 -0
  39. model_agnostic_toolkit/tools/shap/shap_importance.py +116 -0
  40. model_agnostic_toolkit/tools/tool.py +435 -0
  41. model_agnostic_toolkit/tools/utils.py +185 -0
  42. model_agnostic_toolkit/tools/vip/__init__.py +2 -0
  43. model_agnostic_toolkit/tools/vip/vip_importance.py +69 -0
  44. model_agnostic_toolkit/tools/vip/vip_interaction.py +70 -0
  45. model_agnostic_toolkit/tools/vip/vip_utils.py +39 -0
  46. model_agnostic_toolkit/types.py +27 -0
  47. model_agnostic_toolkit-1.0.0.dist-info/METADATA +43 -0
  48. model_agnostic_toolkit-1.0.0.dist-info/RECORD +50 -0
  49. model_agnostic_toolkit-1.0.0.dist-info/WHEEL +4 -0
  50. 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