dragon-ml-toolbox 3.10.2__py3-none-any.whl → 3.12.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.
Potentially problematic release.
This version of dragon-ml-toolbox might be problematic. Click here for more details.
- {dragon_ml_toolbox-3.10.2.dist-info → dragon_ml_toolbox-3.12.0.dist-info}/METADATA +1 -1
- {dragon_ml_toolbox-3.10.2.dist-info → dragon_ml_toolbox-3.12.0.dist-info}/RECORD +9 -9
- ml_tools/GUI_tools.py +572 -131
- ml_tools/ensemble_learning.py +2 -1
- ml_tools/keys.py +5 -2
- {dragon_ml_toolbox-3.10.2.dist-info → dragon_ml_toolbox-3.12.0.dist-info}/WHEEL +0 -0
- {dragon_ml_toolbox-3.10.2.dist-info → dragon_ml_toolbox-3.12.0.dist-info}/licenses/LICENSE +0 -0
- {dragon_ml_toolbox-3.10.2.dist-info → dragon_ml_toolbox-3.12.0.dist-info}/licenses/LICENSE-THIRD-PARTY.md +0 -0
- {dragon_ml_toolbox-3.10.2.dist-info → dragon_ml_toolbox-3.12.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
dragon_ml_toolbox-3.
|
|
2
|
-
dragon_ml_toolbox-3.
|
|
1
|
+
dragon_ml_toolbox-3.12.0.dist-info/licenses/LICENSE,sha256=2uUFNy7D0TLgHim1K5s3DIJ4q_KvxEXVilnU20cWliY,1066
|
|
2
|
+
dragon_ml_toolbox-3.12.0.dist-info/licenses/LICENSE-THIRD-PARTY.md,sha256=lY4_rJPnLnMu7YBQaY-_iz1JRDcLdQzNCyeLAF1glJY,1837
|
|
3
3
|
ml_tools/ETL_engineering.py,sha256=yeZsW_7zRvEcuMZbM4E2GV1dxwBoWIeJAcFFk2AK0fY,39502
|
|
4
|
-
ml_tools/GUI_tools.py,sha256=
|
|
4
|
+
ml_tools/GUI_tools.py,sha256=VonZEizPS0ncm8HWU-ik-SgcXKryJU8eSG7NN0QN9cc,42222
|
|
5
5
|
ml_tools/MICE_imputation.py,sha256=rYqvwQDVtoAJJ0agXWoGzoZEHedWiA6QzcEKEIkiZ08,11388
|
|
6
6
|
ml_tools/ML_callbacks.py,sha256=g_9nSzoA22UJOQZCPKeDz-Ayh0ECFZLzRd6rZ8SokrE,13080
|
|
7
7
|
ml_tools/ML_evaluation.py,sha256=oiDV6HItQloUUKCUpltV-2pogubWLBieGpc-VUwosAQ,10106
|
|
@@ -14,13 +14,13 @@ ml_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
14
14
|
ml_tools/_pytorch_models.py,sha256=bpWZsrSwCvHJQkR6UfoPpElsMv9AvmiNErNHC8NYB_I,10132
|
|
15
15
|
ml_tools/data_exploration.py,sha256=M7bn2q5XN9zJZJGAmMMFSFFZh8LGzC2arFelrXw3N6Q,25241
|
|
16
16
|
ml_tools/datasetmaster.py,sha256=S3PKHNQZ9cyAOck8xQltVLZhaD1gFLfgHFL-aRjz4JU,30077
|
|
17
|
-
ml_tools/ensemble_learning.py,sha256=
|
|
17
|
+
ml_tools/ensemble_learning.py,sha256=D-9IbOKtCvyAB-LbPu3sdSRtdp0RZIcQEZcyMnarHmQ,45758
|
|
18
18
|
ml_tools/handle_excel.py,sha256=lwds7rDLlGSCWiWGI7xNg-Z7kxAepogp0lstSFa0590,12949
|
|
19
|
-
ml_tools/keys.py,sha256=
|
|
19
|
+
ml_tools/keys.py,sha256=A3mLrtLZrxL27whAs2F1GPqZ1KzJpxBp6QbhxY5ioPI,636
|
|
20
20
|
ml_tools/logger.py,sha256=UkbiU9ihBhw9VKyn3rZzisdClWV94EBV6B09_D0iUU0,6026
|
|
21
21
|
ml_tools/path_manager.py,sha256=OCpESgdftbi6mOxetDMIaHhazt4N-W8pJx11X3-yNOs,8305
|
|
22
22
|
ml_tools/utilities.py,sha256=FW97hMTLLxjDR1so-C-_yDm_iz2z_YfirRXjG_IwSLo,22843
|
|
23
|
-
dragon_ml_toolbox-3.
|
|
24
|
-
dragon_ml_toolbox-3.
|
|
25
|
-
dragon_ml_toolbox-3.
|
|
26
|
-
dragon_ml_toolbox-3.
|
|
23
|
+
dragon_ml_toolbox-3.12.0.dist-info/METADATA,sha256=JD5pg6MBVM3stGknoD2vwec1pKgykEwNVtRmanRV2sw,3274
|
|
24
|
+
dragon_ml_toolbox-3.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
dragon_ml_toolbox-3.12.0.dist-info/top_level.txt,sha256=wm-oxax3ciyez6VoO4zsFd-gSok2VipYXnbg3TH9PtU,9
|
|
26
|
+
dragon_ml_toolbox-3.12.0.dist-info/RECORD,,
|
ml_tools/GUI_tools.py
CHANGED
|
@@ -3,19 +3,18 @@ from pathlib import Path
|
|
|
3
3
|
import traceback
|
|
4
4
|
import FreeSimpleGUI as sg
|
|
5
5
|
from functools import wraps
|
|
6
|
-
from typing import Any, Dict, Tuple, List, Literal, Union,
|
|
6
|
+
from typing import Any, Dict, Tuple, List, Literal, Union, Optional, Callable
|
|
7
7
|
from .utilities import _script_info
|
|
8
8
|
import numpy as np
|
|
9
9
|
from .logger import _LOGGER
|
|
10
|
-
from abc import ABC, abstractmethod
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
__all__ = [
|
|
14
13
|
"ConfigManager",
|
|
15
14
|
"GUIFactory",
|
|
16
15
|
"catch_exceptions",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"FeatureMaster",
|
|
17
|
+
"GUIHandler"
|
|
19
18
|
]
|
|
20
19
|
|
|
21
20
|
# --- Configuration Management ---
|
|
@@ -105,11 +104,13 @@ class ConfigManager:
|
|
|
105
104
|
'max_size': ''
|
|
106
105
|
}
|
|
107
106
|
config['Layout'] = {
|
|
108
|
-
'; Default size for continuous input boxes (width,height in characters).': '',
|
|
107
|
+
'; Default size for continuous input boxes (width,height in characters/rows).': '',
|
|
109
108
|
'input_size_cont': '16,1',
|
|
110
|
-
'; Default size for combo/binary boxes (width,height in characters).': '',
|
|
109
|
+
'; Default size for combo/binary boxes (width,height in characters/rows).': '',
|
|
111
110
|
'input_size_binary': '14,1',
|
|
112
|
-
';
|
|
111
|
+
'; Size for multiselect listboxes (width,height in characters/rows).': '',
|
|
112
|
+
'input_size_multi': '14,4',
|
|
113
|
+
'; Default size for buttons (width,height in characters/rows).': '',
|
|
113
114
|
'button_size': '15,2'
|
|
114
115
|
}
|
|
115
116
|
config['Fonts'] = {
|
|
@@ -208,7 +209,7 @@ class GUIFactory:
|
|
|
208
209
|
# --- General-Purpose Layout Generators ---
|
|
209
210
|
def generate_continuous_layout(
|
|
210
211
|
self,
|
|
211
|
-
data_dict: Dict[str,
|
|
212
|
+
data_dict: Dict[str, Union[Tuple[Union[int,float,None], Union[int,float,None]],List[Union[int,float,None]]]],
|
|
212
213
|
is_target: bool = False,
|
|
213
214
|
layout_mode: Literal["grid", "row"] = 'grid',
|
|
214
215
|
features_per_column: int = 4
|
|
@@ -232,9 +233,9 @@ class GUIFactory:
|
|
|
232
233
|
columns = []
|
|
233
234
|
for name, value in data_dict.items():
|
|
234
235
|
if value is None:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
raise ValueError(f"Feature '{name}' was assigned a 'None' value.")
|
|
237
|
+
elif len(value) != 2:
|
|
238
|
+
raise ValueError(f"Feature '{name}' must provide exactly 2 values.")
|
|
238
239
|
else:
|
|
239
240
|
val_min, val_max = value
|
|
240
241
|
key = name
|
|
@@ -268,7 +269,7 @@ class GUIFactory:
|
|
|
268
269
|
|
|
269
270
|
def generate_combo_layout(
|
|
270
271
|
self,
|
|
271
|
-
data_dict: Dict[str, List[Any]],
|
|
272
|
+
data_dict: Dict[str, Union[List[Any],Tuple[Any,...]]],
|
|
272
273
|
layout_mode: Literal["grid", "row"] = 'grid',
|
|
273
274
|
features_per_column: int = 4
|
|
274
275
|
) -> List[List[sg.Column]]:
|
|
@@ -304,6 +305,57 @@ class GUIFactory:
|
|
|
304
305
|
|
|
305
306
|
# Default to 'grid' layout
|
|
306
307
|
return [columns[i:i + features_per_column] for i in range(0, len(columns), features_per_column)]
|
|
308
|
+
|
|
309
|
+
def generate_multiselect_layout(
|
|
310
|
+
self,
|
|
311
|
+
data_dict: Dict[str, Union[List[Any], Tuple[Any, ...]]],
|
|
312
|
+
layout_mode: Literal["grid", "row"] = 'grid',
|
|
313
|
+
features_per_column: int = 4
|
|
314
|
+
) -> List[List[sg.Column]]:
|
|
315
|
+
"""
|
|
316
|
+
Generates a layout for features using Listbox elements for multiple selections.
|
|
317
|
+
|
|
318
|
+
This allows the user to select zero or more options from a list without
|
|
319
|
+
being able to input custom text.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
data_dict (dict): Keys are feature names, values are lists of options.
|
|
323
|
+
layout_mode (str): 'grid' for a multi-row grid layout, or 'row' for a single horizontal row.
|
|
324
|
+
features_per_column (int): Number of features per column when `layout_mode` is 'grid'.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
A list of lists of sg.Column elements, ready to be used in a window layout.
|
|
328
|
+
"""
|
|
329
|
+
cfg = self.config
|
|
330
|
+
bg_color = sg.theme_background_color()
|
|
331
|
+
label_font = (cfg.fonts.font_family, cfg.fonts.label_size, cfg.fonts.label_style) # type: ignore
|
|
332
|
+
|
|
333
|
+
columns = []
|
|
334
|
+
for name, values in data_dict.items():
|
|
335
|
+
label = sg.Text(name, font=label_font, background_color=bg_color, key=f"_text_{name}")
|
|
336
|
+
|
|
337
|
+
# Use sg.Listbox for multiple selections.
|
|
338
|
+
element = sg.Listbox(
|
|
339
|
+
values,
|
|
340
|
+
key=name,
|
|
341
|
+
select_mode=sg.LISTBOX_SELECT_MODE_MULTIPLE,
|
|
342
|
+
size=cfg.layout.input_size_multi, # type: ignore
|
|
343
|
+
no_scrollbar=False
|
|
344
|
+
)
|
|
345
|
+
# -------------------
|
|
346
|
+
|
|
347
|
+
layout = [[label], [element]]
|
|
348
|
+
# Add a small spacer for consistent vertical alignment.
|
|
349
|
+
layout.append([sg.Text(" ", font=(cfg.fonts.font_family, 2), background_color=bg_color)]) # type: ignore
|
|
350
|
+
|
|
351
|
+
# Each feature is wrapped in a Column element for proper alignment.
|
|
352
|
+
columns.append(sg.Column(layout, background_color=bg_color))
|
|
353
|
+
|
|
354
|
+
if layout_mode == 'row':
|
|
355
|
+
return [columns] # A single row containing all columns
|
|
356
|
+
|
|
357
|
+
# Default to 'grid' layout
|
|
358
|
+
return [columns[i:i + features_per_column] for i in range(0, len(columns), features_per_column)]
|
|
307
359
|
|
|
308
360
|
# --- Window Creation ---
|
|
309
361
|
def create_window(self, title: str, layout: List[List[sg.Element]], **kwargs) -> sg.Window:
|
|
@@ -357,175 +409,564 @@ def catch_exceptions(show_popup: bool = True):
|
|
|
357
409
|
return decorator
|
|
358
410
|
|
|
359
411
|
|
|
360
|
-
# ---
|
|
361
|
-
class
|
|
412
|
+
# --- Feature Handler ---
|
|
413
|
+
class FeatureMaster:
|
|
362
414
|
"""
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
415
|
+
Manages and organizes feature definitions for a machine learning model.
|
|
416
|
+
|
|
417
|
+
This class serves as a centralized registry for all features and targets
|
|
418
|
+
used by a model. It is designed to bridge the gap between a user-facing
|
|
419
|
+
application (like a GUI) and the underlying model's data representation.
|
|
420
|
+
|
|
421
|
+
It takes various types of features (continuous, binary, one-hot encoded,
|
|
422
|
+
categorical) and targets, processing them into two key formats:
|
|
423
|
+
1. A mapping from a user-friendly "GUI name" to the corresponding "model name"
|
|
424
|
+
used in the dataset or model training.
|
|
425
|
+
2. A structure containing the acceptable values or ranges for each feature,
|
|
426
|
+
suitable for populating GUI elements like sliders, dropdowns, or checkboxes.
|
|
427
|
+
|
|
428
|
+
By separating the GUI representation from the model's internal logic, this
|
|
429
|
+
class simplifies the process of building user interfaces for model interaction
|
|
430
|
+
and ensures that user input is correctly formatted. At least one type of
|
|
431
|
+
feature must be provided upon initialization.
|
|
432
|
+
|
|
433
|
+
Properties are available to access the processed mappings and GUI-ready values
|
|
434
|
+
for each feature type.
|
|
366
435
|
"""
|
|
367
|
-
def __init__(self,
|
|
436
|
+
def __init__(self,
|
|
437
|
+
targets: Dict[str, str],
|
|
438
|
+
continuous_features: Optional[Dict[str, Tuple[str, float, float]]] = None,
|
|
439
|
+
binary_features: Optional[Dict[str, str]] = None,
|
|
440
|
+
multi_binary_features: Optional[Dict[str, Dict[str, str]]] = None,
|
|
441
|
+
one_hot_features: Optional[Dict[str, Dict[str, str]]] = None,
|
|
442
|
+
categorical_features: Optional[List[Tuple[str, str, Dict[str, int]]]] = None) -> None:
|
|
368
443
|
"""
|
|
369
|
-
|
|
370
|
-
|
|
444
|
+
Initializes the FeatureMaster instance by processing feature and target definitions.
|
|
445
|
+
|
|
446
|
+
This constructor creates internal mappings to translate between GUI-friendly names and model-specific feature names. It also
|
|
447
|
+
prepares data structures needed to populate UI components.
|
|
448
|
+
|
|
371
449
|
Args:
|
|
372
|
-
|
|
450
|
+
targets (Dict[str, str]):
|
|
451
|
+
A dictionary defining the model's target variables.
|
|
452
|
+
- **key** (str): The name to be displayed in the GUI.
|
|
453
|
+
- **value** (str): The corresponding column name in the model's dataset.
|
|
454
|
+
|
|
455
|
+
continuous_features (Dict[str, Tuple[str, float, float]]):
|
|
456
|
+
A dictionary for continuous numerical features.
|
|
457
|
+
- **key** (str): The name to be displayed in the GUI (e.g., for a slider).
|
|
458
|
+
- **value** (Tuple[str, float, float]): A tuple containing:
|
|
459
|
+
- `[0]` (str): The model's internal feature name.
|
|
460
|
+
- `[1]` (float): The minimum allowed value (inclusive).
|
|
461
|
+
- `[2]` (float): The maximum allowed value (inclusive).
|
|
462
|
+
|
|
463
|
+
binary_features (Dict[str, str]):
|
|
464
|
+
A dictionary for binary (True/False) features.
|
|
465
|
+
- **key** (str): The name to be displayed in the GUI (e.g., for a checkbox).
|
|
466
|
+
- **value** (str): The model's internal feature name.
|
|
467
|
+
|
|
468
|
+
multi_binary_features (Dict[str, Dict[str, str]]):
|
|
469
|
+
A dictionary for features where multiple binary-like options can be
|
|
470
|
+
selected at once (e.g., from a multi-select listbox).
|
|
471
|
+
- **key** (str): The name for the group to be displayed in the GUI.
|
|
472
|
+
- **value** (Dict[str, str]): A nested dictionary where:
|
|
473
|
+
- key (str): The user-selectable option.
|
|
474
|
+
- value (str): The corresponding model's internal feature name.
|
|
475
|
+
|
|
476
|
+
one_hot_features (Dict[str, Dict[str, str]]):
|
|
477
|
+
A dictionary for features that will be one-hot encoded from a single
|
|
478
|
+
categorical input.
|
|
479
|
+
- **key** (str): The name for the group to be displayed in the GUI (e.g.,
|
|
480
|
+
for a dropdown menu).
|
|
481
|
+
- **value** (Dict[str, str]): A nested dictionary where:
|
|
482
|
+
- key (str): The user-selectable option (e.g., 'Category A').
|
|
483
|
+
- value (str): The corresponding model column name.
|
|
484
|
+
|
|
485
|
+
categorical_features (List[Tuple[str, str, Dict[str, int]]]):
|
|
486
|
+
A list for ordinal or label-encoded categorical features.
|
|
487
|
+
- **Each element is a tuple** containing:
|
|
488
|
+
- `[0]` (str): The name to be displayed in the GUI (e.g., for a
|
|
489
|
+
dropdown menu).
|
|
490
|
+
- `[1]` (str): The model's internal feature name.
|
|
491
|
+
- `[2]` (Dict[str, int]): A dictionary mapping the user-selectable
|
|
492
|
+
options to their corresponding integer values.
|
|
373
493
|
"""
|
|
374
|
-
#
|
|
375
|
-
if
|
|
376
|
-
raise
|
|
494
|
+
# Validation
|
|
495
|
+
if continuous_features is None and binary_features is None and one_hot_features is None and categorical_features is None and multi_binary_features is None:
|
|
496
|
+
raise ValueError("No features provided.")
|
|
497
|
+
|
|
498
|
+
# Targets
|
|
499
|
+
self._targets_values = self._handle_targets(targets)
|
|
500
|
+
self._targets_mapping = targets
|
|
501
|
+
|
|
502
|
+
# continuous features
|
|
503
|
+
if continuous_features is not None:
|
|
504
|
+
self._continuous_values, self._continuous_mapping = self._handle_continuous_features(continuous_features)
|
|
505
|
+
self.has_continuous = True
|
|
506
|
+
else:
|
|
507
|
+
self._continuous_values, self._continuous_mapping = None, None
|
|
508
|
+
self.has_continuous = False
|
|
377
509
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
510
|
+
# binary features
|
|
511
|
+
if binary_features is not None:
|
|
512
|
+
self._binary_values = self._handle_binary_features(binary_features)
|
|
513
|
+
self._binary_mapping = binary_features
|
|
514
|
+
self.has_binary = True
|
|
515
|
+
else:
|
|
516
|
+
self._binary_values, self._binary_mapping = None, None
|
|
517
|
+
self.has_binary = False
|
|
518
|
+
|
|
519
|
+
# multi-binary features
|
|
520
|
+
if multi_binary_features is not None:
|
|
521
|
+
self._multi_binary_values = self._handle_multi_binary_features(multi_binary_features)
|
|
522
|
+
self._multi_binary_mapping = multi_binary_features
|
|
523
|
+
self.has_multi_binary = True
|
|
524
|
+
else:
|
|
525
|
+
self._multi_binary_values, self._multi_binary_mapping = None, None
|
|
526
|
+
self.has_multi_binary = False
|
|
527
|
+
|
|
528
|
+
# one-hot features
|
|
529
|
+
if one_hot_features is not None:
|
|
530
|
+
self._one_hot_values = self._handle_one_hot_features(one_hot_features)
|
|
531
|
+
self._one_hot_mapping = one_hot_features
|
|
532
|
+
self.has_one_hot = True
|
|
533
|
+
else:
|
|
534
|
+
self._one_hot_values, self._one_hot_mapping = None, None
|
|
535
|
+
self.has_one_hot = False
|
|
536
|
+
|
|
537
|
+
# categorical features
|
|
538
|
+
if categorical_features is not None:
|
|
539
|
+
self._categorical_values, self._categorical_mapping = self._handle_categorical_features(categorical_features)
|
|
540
|
+
self.has_categorical = True
|
|
541
|
+
else:
|
|
542
|
+
self._categorical_values, self._categorical_mapping = None, None
|
|
543
|
+
self.has_categorical = False
|
|
544
|
+
|
|
545
|
+
# all features attribute
|
|
546
|
+
self._all_features = self._get_all_gui_features()
|
|
547
|
+
|
|
548
|
+
def _handle_targets(self, targets: Dict[str, str]):
|
|
549
|
+
# Make dictionary GUI name: range values
|
|
550
|
+
gui_values: dict[str, tuple[None,None]] = {gui_key: (None, None) for gui_key in targets.keys()}
|
|
551
|
+
# Map GUI name to Model name (same as input)
|
|
552
|
+
return gui_values
|
|
381
553
|
|
|
382
|
-
|
|
554
|
+
def _handle_continuous_features(self, continuous_features: Dict[str, Tuple[str, float, float]]):
|
|
555
|
+
# Make dictionary GUI name: range values
|
|
556
|
+
gui_values: dict[str, tuple[float,float]] = {gui_key: (tuple_values[1], tuple_values[2]) for gui_key, tuple_values in continuous_features.items()}
|
|
557
|
+
# Map GUI name to Model name
|
|
558
|
+
gui_to_model: dict[str,str] = {gui_key: tuple_values[0] for gui_key, tuple_values in continuous_features.items()}
|
|
559
|
+
return gui_values, gui_to_model
|
|
560
|
+
|
|
561
|
+
def _handle_binary_features(self, binary_features: Dict[str, str]):
|
|
562
|
+
# Make dictionary GUI name: range values
|
|
563
|
+
gui_values: dict[str, tuple[Literal["False"],Literal["True"]]] = {gui_key: ("False", "True") for gui_key in binary_features.keys()}
|
|
564
|
+
# Map GUI name to Model name (same as input)
|
|
565
|
+
return gui_values
|
|
566
|
+
|
|
567
|
+
def _handle_multi_binary_features(self, multi_binary_features: Dict[str, Dict[str, str]]):
|
|
568
|
+
# Make dictionary GUI name: range values
|
|
569
|
+
gui_values: dict[str, tuple[str,...]] = {
|
|
570
|
+
gui_key: tuple(nested_dict.keys())
|
|
571
|
+
for gui_key, nested_dict in multi_binary_features.items()}
|
|
572
|
+
# Map GUI name to Model name and preserve internal mapping (same as input)
|
|
573
|
+
return gui_values
|
|
574
|
+
|
|
575
|
+
def _handle_one_hot_features(self, one_hot_features: Dict[str, Dict[str,str]]):
|
|
576
|
+
# Make dictionary GUI name: range values
|
|
577
|
+
gui_values: dict[str, tuple[str,...]] = {gui_key: tuple(nested_dict.keys()) for gui_key, nested_dict in one_hot_features.items()}
|
|
578
|
+
# Map GUI name to Model name and preserve internal mapping (same as input)
|
|
579
|
+
return gui_values
|
|
383
580
|
|
|
581
|
+
def _handle_categorical_features(self, categorical_features: List[Tuple[str, str, Dict[str, int]]]):
|
|
582
|
+
# Make dictionary GUI name: range values
|
|
583
|
+
gui_values: dict[str, tuple[str,...]] = {gui_key: tuple(gui_options.keys()) for gui_key, _, gui_options in categorical_features}
|
|
584
|
+
# Map GUI name to Model name and preserve internal mapping
|
|
585
|
+
gui_to_model: dict[str, tuple[str, dict[str, int]]] = {gui_key: (model_key, gui_options) for gui_key, model_key, gui_options in categorical_features}
|
|
586
|
+
return gui_values, gui_to_model
|
|
587
|
+
|
|
588
|
+
def _get_all_gui_features(self) -> dict[str,Any]:
|
|
589
|
+
all_dict: dict[str,Any] = dict()
|
|
590
|
+
# Add all feature GUI keys
|
|
591
|
+
if self._continuous_mapping is not None:
|
|
592
|
+
all_dict.update(self._continuous_mapping)
|
|
593
|
+
if self._binary_mapping is not None:
|
|
594
|
+
all_dict.update(self._binary_mapping)
|
|
595
|
+
if self._multi_binary_mapping is not None:
|
|
596
|
+
all_dict.update(self._multi_binary_mapping)
|
|
597
|
+
if self._one_hot_mapping is not None:
|
|
598
|
+
all_dict.update(self._one_hot_mapping)
|
|
599
|
+
if self._categorical_mapping is not None:
|
|
600
|
+
all_dict.update(self._categorical_mapping)
|
|
601
|
+
return all_dict
|
|
602
|
+
|
|
384
603
|
@property
|
|
385
|
-
|
|
386
|
-
def gui_input_map(self) -> Dict[str, Literal["continuous","categorical"]]:
|
|
604
|
+
def all_features(self):
|
|
387
605
|
"""
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
Should return a dictionary mapping each GUI input name to its type ('continuous' or 'categorical').
|
|
606
|
+
A merged dictionary of all feature mappings.
|
|
391
607
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
'Material Type': 'categorical'
|
|
397
|
-
}
|
|
398
|
-
```
|
|
608
|
+
The value type varies based on the feature type (str, dict, or tuple).
|
|
609
|
+
|
|
610
|
+
Structure:
|
|
611
|
+
Dict[str, Any]
|
|
399
612
|
"""
|
|
400
|
-
|
|
613
|
+
return self._all_features
|
|
401
614
|
|
|
402
615
|
@property
|
|
403
|
-
|
|
404
|
-
def map_gui_to_real(self) -> Dict[str,str]:
|
|
616
|
+
def targets(self):
|
|
405
617
|
"""
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
Should return a dictionary mapping each GUI continuous feature name to its expected model feature name.
|
|
618
|
+
The mapping for target variables from GUI name to model name.
|
|
409
619
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
620
|
+
Structure:
|
|
621
|
+
Dict[str, str]
|
|
622
|
+
"""
|
|
623
|
+
return self._targets_mapping
|
|
624
|
+
|
|
625
|
+
@property
|
|
626
|
+
def targets_gui(self):
|
|
627
|
+
"""
|
|
628
|
+
The GUI value structure for targets.
|
|
629
|
+
|
|
630
|
+
Structure:
|
|
631
|
+
Dict[str, Tuple[None, None]]
|
|
632
|
+
"""
|
|
633
|
+
return self._targets_values
|
|
634
|
+
|
|
635
|
+
@property
|
|
636
|
+
def continuous(self):
|
|
637
|
+
"""
|
|
638
|
+
The mapping for continuous features from GUI name to model name.
|
|
639
|
+
|
|
640
|
+
Structure:
|
|
641
|
+
Dict[str, str]
|
|
642
|
+
"""
|
|
643
|
+
if self._continuous_mapping is not None:
|
|
644
|
+
return self._continuous_mapping
|
|
645
|
+
|
|
646
|
+
@property
|
|
647
|
+
def continuous_gui(self):
|
|
648
|
+
"""
|
|
649
|
+
The GUI value ranges (min, max) for continuous features.
|
|
650
|
+
|
|
651
|
+
Structure:
|
|
652
|
+
Dict[str, Tuple[float, float]]
|
|
653
|
+
"""
|
|
654
|
+
if self._continuous_values is not None:
|
|
655
|
+
return self._continuous_values
|
|
656
|
+
|
|
657
|
+
@property
|
|
658
|
+
def binary(self):
|
|
659
|
+
"""
|
|
660
|
+
The mapping for binary features from GUI name to model name.
|
|
661
|
+
|
|
662
|
+
Structure:
|
|
663
|
+
Dict[str, str]
|
|
664
|
+
"""
|
|
665
|
+
if self._binary_mapping is not None:
|
|
666
|
+
return self._binary_mapping
|
|
667
|
+
|
|
668
|
+
@property
|
|
669
|
+
def binary_gui(self):
|
|
670
|
+
"""
|
|
671
|
+
The GUI options ('False', 'True') for binary features.
|
|
672
|
+
|
|
673
|
+
Structure:
|
|
674
|
+
Dict[str, Tuple['False', 'True']]
|
|
675
|
+
"""
|
|
676
|
+
if self._binary_values is not None:
|
|
677
|
+
return self._binary_values
|
|
678
|
+
|
|
679
|
+
@property
|
|
680
|
+
def multi_binary(self):
|
|
417
681
|
"""
|
|
418
|
-
|
|
682
|
+
The mapping for multi-binary features.
|
|
683
|
+
|
|
684
|
+
Structure:
|
|
685
|
+
{"GUI NAME": {"GUI OPTION 1": "model_column"}}
|
|
686
|
+
"""
|
|
687
|
+
if self._multi_binary_mapping is not None:
|
|
688
|
+
return self._multi_binary_mapping
|
|
689
|
+
|
|
690
|
+
@property
|
|
691
|
+
def multi_binary_gui(self):
|
|
692
|
+
"""
|
|
693
|
+
The GUI options for multi-binary feature groups.
|
|
694
|
+
|
|
695
|
+
Structure:
|
|
696
|
+
Dict[str, Tuple[str, ...]]
|
|
697
|
+
"""
|
|
698
|
+
if self._multi_binary_values is not None:
|
|
699
|
+
return self._multi_binary_values
|
|
419
700
|
|
|
420
|
-
@
|
|
421
|
-
def
|
|
701
|
+
@property
|
|
702
|
+
def one_hot(self):
|
|
703
|
+
"""
|
|
704
|
+
The mapping for one-hot encoded features.
|
|
705
|
+
|
|
706
|
+
{"GUI NAME": {"GUI OPTION 1": "model_column"}}
|
|
707
|
+
|
|
708
|
+
Structure:
|
|
709
|
+
Dict[str, Dict[str, str]]
|
|
422
710
|
"""
|
|
423
|
-
|
|
711
|
+
if self._one_hot_mapping is not None:
|
|
712
|
+
return self._one_hot_mapping
|
|
713
|
+
|
|
714
|
+
@property
|
|
715
|
+
def one_hot_gui(self):
|
|
716
|
+
"""
|
|
717
|
+
The GUI options for one-hot encoded feature groups.
|
|
718
|
+
|
|
719
|
+
Structure:
|
|
720
|
+
Dict[str, Tuple[str, ...]]
|
|
721
|
+
"""
|
|
722
|
+
if self._one_hot_values is not None:
|
|
723
|
+
return self._one_hot_values
|
|
424
724
|
|
|
425
|
-
|
|
426
|
-
|
|
725
|
+
@property
|
|
726
|
+
def categorical(self):
|
|
727
|
+
"""
|
|
728
|
+
The mapping for categorical features.
|
|
427
729
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
730
|
+
{"GUI NAME": ("model_column", {"GUI OPTION 1": column_value})}
|
|
731
|
+
|
|
732
|
+
Structure:
|
|
733
|
+
Dict[str, Tuple[str, Dict[str, int]]]
|
|
734
|
+
"""
|
|
735
|
+
if self._categorical_mapping is not None:
|
|
736
|
+
return self._categorical_mapping
|
|
737
|
+
|
|
738
|
+
@property
|
|
739
|
+
def categorical_gui(self):
|
|
438
740
|
"""
|
|
439
|
-
|
|
741
|
+
The GUI options for categorical features.
|
|
742
|
+
|
|
743
|
+
Structure:
|
|
744
|
+
Dict[str, Tuple[str, ...]]
|
|
745
|
+
"""
|
|
746
|
+
if self._categorical_values is not None:
|
|
747
|
+
return self._categorical_values
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
# --- GUI-Model API ---
|
|
751
|
+
class GUIHandler:
|
|
752
|
+
"""
|
|
753
|
+
Translates data between a GUI and a machine learning model.
|
|
440
754
|
|
|
441
|
-
|
|
755
|
+
This class acts as the primary interface between a user-facing application
|
|
756
|
+
(FreeSimpleGUI) and the model's expected data format. It uses a `FeatureMaster` instance to correctly process
|
|
757
|
+
and encode user inputs.
|
|
758
|
+
|
|
759
|
+
Its main responsibilities are:
|
|
760
|
+
1. To take raw values from GUI elements and, using the definitions from
|
|
761
|
+
`FeatureMaster`, convert them into a single, ordered `numpy.ndarray`
|
|
762
|
+
that can be fed directly into a model for inference.
|
|
763
|
+
2. To take the results of a model's inference and update the
|
|
764
|
+
corresponding target fields in the GUI to display the prediction.
|
|
765
|
+
|
|
766
|
+
This handler ensures a clean separation of concerns, where the GUI is
|
|
767
|
+
only responsible for presentation, and the model sees correctly formatted numerical data.
|
|
768
|
+
"""
|
|
769
|
+
def __init__(self, feature_handler: FeatureMaster, model_expected_features: list[str]) -> None:
|
|
442
770
|
"""
|
|
443
|
-
|
|
771
|
+
Initializes the GUIHandler.
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
feature_handler (FeatureMaster):
|
|
775
|
+
An initialized instance of the `FeatureMaster` class. This object
|
|
776
|
+
contains all the necessary mappings and definitions for the model's
|
|
777
|
+
features and targets.
|
|
778
|
+
model_expected_features (list[str]):
|
|
779
|
+
A list of strings specifying the exact names of the features the
|
|
780
|
+
machine learning model expects in its input vector. The **order**
|
|
781
|
+
of features in this list is critical, as it dictates the final
|
|
782
|
+
column order of the output numpy array.
|
|
783
|
+
|
|
784
|
+
Raises:
|
|
785
|
+
TypeError: If `model_expected_features` is not a list or if any of its elements are not strings.
|
|
786
|
+
"""
|
|
787
|
+
if not isinstance(model_expected_features, list):
|
|
788
|
+
raise TypeError("Input 'model_expected_features' must be a list.")
|
|
789
|
+
if not all(isinstance(col, str) for col in model_expected_features):
|
|
790
|
+
raise TypeError("All elements in the 'model_expected_features' must be strings.")
|
|
444
791
|
|
|
445
|
-
|
|
792
|
+
# Model expected features
|
|
793
|
+
self.model_expected_features = tuple(model_expected_features)
|
|
794
|
+
# Feature master instance
|
|
795
|
+
self.master = feature_handler
|
|
796
|
+
|
|
797
|
+
def _process_continuous(self, gui_feature: str, chosen_value: Any) -> Tuple[str,float]:
|
|
798
|
+
"""
|
|
799
|
+
Maps GUI name to model expected name and casts the value to float.
|
|
446
800
|
"""
|
|
447
801
|
try:
|
|
448
|
-
|
|
802
|
+
model_name = self.master.continuous[gui_feature] # type: ignore
|
|
449
803
|
float_value = float(chosen_value)
|
|
450
804
|
except KeyError as e:
|
|
451
|
-
_LOGGER.error(f"No matching name for '{
|
|
805
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as continuous.")
|
|
452
806
|
raise e
|
|
453
807
|
except (ValueError, TypeError) as e2:
|
|
454
|
-
_LOGGER.error(f"Invalid number conversion for '{chosen_value}' of '{
|
|
808
|
+
_LOGGER.error(f"Invalid number conversion for '{chosen_value}' of '{gui_feature}'.")
|
|
455
809
|
raise e2
|
|
456
810
|
else:
|
|
457
|
-
return
|
|
811
|
+
return model_name, float_value
|
|
812
|
+
|
|
813
|
+
def _process_binary(self, gui_feature: str, chosen_value: str) -> Tuple[str,int]:
|
|
814
|
+
"""
|
|
815
|
+
Maps GUI name to model expected name and casts the value to binary (0,1).
|
|
816
|
+
"""
|
|
817
|
+
try:
|
|
818
|
+
model_name = self.master.binary[gui_feature] # type: ignore
|
|
819
|
+
binary_mapping_keys = self.master.binary_gui[gui_feature] # type: ignore
|
|
820
|
+
except KeyError as e:
|
|
821
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as binary.")
|
|
822
|
+
raise e
|
|
823
|
+
else:
|
|
824
|
+
mapping_dict = {
|
|
825
|
+
binary_mapping_keys[0]: 0,
|
|
826
|
+
binary_mapping_keys[1]: 1
|
|
827
|
+
}
|
|
828
|
+
result = mapping_dict[chosen_value]
|
|
829
|
+
return model_name, result
|
|
458
830
|
|
|
459
|
-
def
|
|
831
|
+
def _process_multi_binary(self, gui_feature: str, chosen_values: list[str]) -> dict[str, int]:
|
|
832
|
+
"""
|
|
833
|
+
Maps GUI names to model expected names and casts values to multi-binary encoding.
|
|
834
|
+
|
|
835
|
+
For a given feature group, this sets all selected options to 1 and all
|
|
836
|
+
unselected options to 0.
|
|
837
|
+
"""
|
|
838
|
+
try:
|
|
839
|
+
# Get the mapping for the group
|
|
840
|
+
multi_binary_mapping = self.master.multi_binary[gui_feature] # type: ignore
|
|
841
|
+
except KeyError as e:
|
|
842
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as multi-binary.")
|
|
843
|
+
raise e
|
|
844
|
+
else:
|
|
845
|
+
# Start with all possible features for this group set to 0 (unselected)
|
|
846
|
+
results = {model_key: 0 for model_key in multi_binary_mapping.values()}
|
|
847
|
+
# Set the features for the chosen options to 1
|
|
848
|
+
for chosen_option in chosen_values:
|
|
849
|
+
model_name = multi_binary_mapping[chosen_option]
|
|
850
|
+
results[model_name] = 1
|
|
851
|
+
|
|
852
|
+
return results
|
|
853
|
+
|
|
854
|
+
def _process_one_hot(self, gui_feature: str, chosen_value: str) -> Dict[str,int]:
|
|
855
|
+
"""
|
|
856
|
+
Maps GUI names to model expected names and casts values to one-hot encoding.
|
|
460
857
|
"""
|
|
461
|
-
|
|
858
|
+
try:
|
|
859
|
+
one_hot_mapping = self.master.one_hot[gui_feature] # type: ignore
|
|
860
|
+
except KeyError as e:
|
|
861
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as one-hot.")
|
|
862
|
+
raise e
|
|
863
|
+
else:
|
|
864
|
+
mapped_chosen_value = one_hot_mapping[chosen_value]
|
|
865
|
+
# base results mapped to 0
|
|
866
|
+
results = {model_key: 0 for model_key in one_hot_mapping.values()}
|
|
867
|
+
# update chosen value
|
|
868
|
+
results[mapped_chosen_value] = 1
|
|
869
|
+
return results
|
|
462
870
|
|
|
463
|
-
|
|
871
|
+
def _process_categorical(self, gui_feature: str, chosen_value: str) -> Tuple[str,int]:
|
|
464
872
|
"""
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
873
|
+
Maps GUI name to model expected name and casts the value to a categorical number.
|
|
874
|
+
"""
|
|
875
|
+
try:
|
|
876
|
+
categorical_tuple = self.master.categorical[gui_feature] # type: ignore
|
|
877
|
+
except KeyError as e:
|
|
878
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as categorical.")
|
|
879
|
+
raise e
|
|
880
|
+
else:
|
|
881
|
+
model_name = categorical_tuple[0]
|
|
882
|
+
categorical_mapping = categorical_tuple[1]
|
|
883
|
+
result = categorical_mapping[chosen_value]
|
|
884
|
+
return model_name, result
|
|
885
|
+
|
|
886
|
+
def update_target_fields(self, window: sg.Window, inference_results: Dict[str, Any]):
|
|
887
|
+
"""
|
|
888
|
+
Updates the GUI's target fields with inference results.
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
window (sg.Window): The application's window object.
|
|
892
|
+
inference_results (dict): A dictionary where keys are target names (as used by the model) and values are the predicted results to update.
|
|
893
|
+
"""
|
|
894
|
+
# Target values to update
|
|
895
|
+
gui_targets_values = {gui_key: inference_results[model_key] for gui_key, model_key in self.master.targets.items()}
|
|
896
|
+
|
|
897
|
+
# Update window
|
|
898
|
+
for gui_key, result in gui_targets_values.items():
|
|
899
|
+
# Format numbers to 2 decimal places, leave other types as-is
|
|
900
|
+
display_value = f"{result:.2f}" if isinstance(result, (int, float)) else result
|
|
901
|
+
window[gui_key].update(display_value) # type: ignore
|
|
469
902
|
|
|
903
|
+
def _call_subprocess(self, window_values: Dict[str,Any], master_feature: Dict[str,str], processor: Callable) -> Dict[str, Union[float,int]]:
|
|
904
|
+
processed_features_subset: Dict[str, Union[float,int]] = dict()
|
|
905
|
+
|
|
906
|
+
for gui_name in master_feature.keys():
|
|
907
|
+
chosen_value = window_values.get(gui_name)
|
|
470
908
|
# value validation
|
|
471
909
|
if chosen_value is None or str(chosen_value) == '':
|
|
472
910
|
raise ValueError(f"GUI input '{gui_name}' is missing a value.")
|
|
911
|
+
# process value
|
|
912
|
+
raw_result = processor(gui_name, chosen_value)
|
|
913
|
+
if isinstance(raw_result, tuple):
|
|
914
|
+
model_name, result = raw_result
|
|
915
|
+
processed_features_subset[model_name] = result
|
|
916
|
+
elif isinstance(raw_result, dict):
|
|
917
|
+
processed_features_subset.update(raw_result)
|
|
918
|
+
else:
|
|
919
|
+
raise TypeError(f"Processor returned an unrecognized type: {type(raw_result)}")
|
|
920
|
+
|
|
921
|
+
return processed_features_subset
|
|
473
922
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
923
|
+
def process_features(self, window_values: Dict[str, Any]) -> np.ndarray:
|
|
924
|
+
"""
|
|
925
|
+
Translates GUI values to a model-expected input array, returning a 1D numpy array.
|
|
926
|
+
"""
|
|
927
|
+
# Stage 1: Process GUI inputs into a dictionary
|
|
928
|
+
processed_features: Dict[str, Union[float,int]] = {}
|
|
929
|
+
|
|
930
|
+
if self.master.has_continuous:
|
|
931
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
932
|
+
master_feature=self.master.continuous, # type: ignore
|
|
933
|
+
processor=self._process_continuous)
|
|
934
|
+
processed_features.update(processed_subset)
|
|
935
|
+
|
|
936
|
+
if self.master.has_binary:
|
|
937
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
938
|
+
master_feature=self.master.binary, # type: ignore
|
|
939
|
+
processor=self._process_binary)
|
|
940
|
+
processed_features.update(processed_subset)
|
|
478
941
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
942
|
+
if self.master.has_multi_binary:
|
|
943
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
944
|
+
master_feature=self.master.multi_binary, # type: ignore
|
|
945
|
+
processor=self._process_multi_binary)
|
|
946
|
+
processed_features.update(processed_subset)
|
|
947
|
+
|
|
948
|
+
if self.master.has_one_hot:
|
|
949
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
950
|
+
master_feature=self.master.one_hot, # type: ignore
|
|
951
|
+
processor=self._process_one_hot)
|
|
952
|
+
processed_features.update(processed_subset)
|
|
953
|
+
|
|
954
|
+
if self.master.has_categorical:
|
|
955
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
956
|
+
master_feature=self.master.categorical, # type: ignore
|
|
957
|
+
processor=self._process_categorical)
|
|
958
|
+
processed_features.update(processed_subset)
|
|
483
959
|
|
|
484
960
|
# Stage 2: Assemble the final vector using the model's required order
|
|
485
|
-
final_vector: List[float] =
|
|
961
|
+
final_vector: List[float] = list()
|
|
486
962
|
|
|
487
963
|
try:
|
|
488
|
-
for feature_name in self.
|
|
964
|
+
for feature_name in self.model_expected_features:
|
|
489
965
|
final_vector.append(processed_features[feature_name])
|
|
490
966
|
except KeyError as e:
|
|
491
|
-
raise RuntimeError(
|
|
492
|
-
|
|
493
|
-
f"the required model feature: '{e}'"
|
|
494
|
-
f"Check the gui_input_map and process_categorical logic."
|
|
495
|
-
)
|
|
496
|
-
|
|
967
|
+
raise RuntimeError(f"Configuration Error: Implemented methods failed to generate the required model feature: '{e}'")
|
|
968
|
+
|
|
497
969
|
return np.array(final_vector, dtype=np.float32)
|
|
498
970
|
|
|
499
|
-
|
|
500
|
-
def update_target_fields(window: sg.Window, results_dict: Dict[str, Any], map_model_to_gui: Optional[Dict[str,str]]):
|
|
501
|
-
"""
|
|
502
|
-
Updates the GUI's target fields with inference results.
|
|
503
|
-
|
|
504
|
-
Args:
|
|
505
|
-
window (sg.Window): The application's window object.
|
|
506
|
-
results_dict (dict): A dictionary where keys are target names (as expected by the GUI) and values are the predicted results to update.
|
|
507
|
-
map_model_to_gui (dict | None): Map `results_dict.keys()` from model target names to GUI target names, if gui names were customized.
|
|
508
|
-
"""
|
|
509
|
-
if map_model_to_gui is not None:
|
|
510
|
-
# Validation
|
|
511
|
-
if len(map_model_to_gui) != len(results_dict):
|
|
512
|
-
_LOGGER.error(f"Expected a mapping for {len(results_dict)} targets, but received {len(map_model_to_gui)} target map names.")
|
|
513
|
-
raise ValueError
|
|
514
|
-
|
|
515
|
-
# new dictionary with GUI keys and corresponding result values
|
|
516
|
-
display_dict = {
|
|
517
|
-
gui_key: results_dict[model_key]
|
|
518
|
-
for model_key, gui_key in map_model_to_gui.items()
|
|
519
|
-
}
|
|
520
|
-
else:
|
|
521
|
-
# If no map is provided, use given result keys
|
|
522
|
-
display_dict = results_dict
|
|
523
|
-
|
|
524
|
-
for key, result in display_dict.items():
|
|
525
|
-
# Format numbers to 2 decimal places, leave other types as-is
|
|
526
|
-
display_value = f"{result:.2f}" if isinstance(result, (int, float)) else result
|
|
527
|
-
window[key].update(display_value) # type: ignore
|
|
528
|
-
|
|
529
|
-
|
|
530
971
|
def info():
|
|
531
972
|
_script_info(__all__)
|
ml_tools/ensemble_learning.py
CHANGED
|
@@ -1026,7 +1026,8 @@ class InferenceHandler:
|
|
|
1026
1026
|
else: # Classification
|
|
1027
1027
|
label = model.predict(features)[0]
|
|
1028
1028
|
probabilities = model.predict_proba(features)[0]
|
|
1029
|
-
results[target_name] = {
|
|
1029
|
+
results[target_name] = {ModelSaveKeys.CLASSIFICATION_LABEL: label,
|
|
1030
|
+
ModelSaveKeys.CLASSIFICATION_PROBABILITIES: probabilities}
|
|
1030
1031
|
|
|
1031
1032
|
if self.verbose:
|
|
1032
1033
|
_LOGGER.info("✅ Inference process complete.")
|
ml_tools/keys.py
CHANGED
|
@@ -17,9 +17,12 @@ class LogKeys:
|
|
|
17
17
|
class ModelSaveKeys:
|
|
18
18
|
"""
|
|
19
19
|
Used internally for ensemble_learning module.
|
|
20
|
-
|
|
21
|
-
Keys used for serializing a trained model metadata.
|
|
22
20
|
"""
|
|
21
|
+
# Serializing a trained model metadata.
|
|
23
22
|
MODEL = "model"
|
|
24
23
|
FEATURES = "feature_names"
|
|
25
24
|
TARGET = "target_name"
|
|
25
|
+
|
|
26
|
+
# Classification keys
|
|
27
|
+
CLASSIFICATION_LABEL = "label"
|
|
28
|
+
CLASSIFICATION_PROBABILITIES = "probabilities"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|