robert 2.1.0__tar.gz → 2.1.1__tar.gz
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.
- {robert-2.1.0 → robert-2.1.1}/PKG-INFO +4 -2
- robert-2.1.1/robert/__init__.py +9 -0
- robert-2.1.1/robert/api.py +645 -0
- {robert-2.1.0 → robert-2.1.1}/robert/argument_parser.py +9 -3
- robert-2.1.1/robert/gui_easyrob/easyrob.py +32 -0
- robert-2.1.1/robert/gui_easyrob/easyrob_launcher.py +68 -0
- robert-2.1.1/robert/gui_easyrob/main/__init__.py +9 -0
- robert-2.1.1/robert/gui_easyrob/main/window.py +3534 -0
- robert-2.1.1/robert/gui_easyrob/tabs/__init__.py +21 -0
- robert-2.1.1/robert/gui_easyrob/tabs/advanced_options.py +334 -0
- robert-2.1.1/robert/gui_easyrob/tabs/aqme.py +1105 -0
- robert-2.1.1/robert/gui_easyrob/tabs/images.py +271 -0
- robert-2.1.1/robert/gui_easyrob/tabs/molssi.py +338 -0
- robert-2.1.1/robert/gui_easyrob/tabs/predictions.py +411 -0
- robert-2.1.1/robert/gui_easyrob/tabs/results.py +477 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/chemdraw.md +115 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/csv.md +212 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/descriptors.md +66 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/overview.md +527 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/predictions.md +68 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_10.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_11.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_12.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_13.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_2.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_3.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_4.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_5.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_6.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_7.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_8.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_9.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_2.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_3.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_4.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_5.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_2.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_3.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_4.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_5.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_6.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_7.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_8.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_9.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_2.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_3.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_4.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_5.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_6.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_2.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_3.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_4.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_5.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_6.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_7.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_8.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_2.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_3.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_4.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_5.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_5_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_5_2.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_5_3.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_1_1.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_2.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_3.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_4.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_5.png +0 -0
- robert-2.1.1/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_6.png +0 -0
- robert-2.1.1/robert/gui_easyrob/utils/__init__.py +0 -0
- robert-2.1.1/robert/gui_easyrob/utils/aqme_utils.py +163 -0
- robert-2.1.1/robert/gui_easyrob/utils/molssi_utils.py +424 -0
- robert-2.1.1/robert/gui_easyrob/utils/predictions_utils.py +1074 -0
- robert-2.1.1/robert/gui_easyrob/utils/utils_gui.py +392 -0
- robert-2.1.1/robert/gui_easyrob/version.py +63 -0
- robert-2.1.1/robert/icons/documentation_icon.png +0 -0
- robert-2.1.1/robert/icons/youtube_icon.ico +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/predict_utils.py +43 -11
- {robert-2.1.0 → robert-2.1.1}/robert/report.py +1 -0
- {robert-2.1.0 → robert-2.1.1}/robert/utils.py +152 -20
- {robert-2.1.0 → robert-2.1.1}/robert.egg-info/PKG-INFO +4 -2
- robert-2.1.1/robert.egg-info/SOURCES.txt +162 -0
- robert-2.1.1/robert.egg-info/entry_points.txt +2 -0
- {robert-2.1.0 → robert-2.1.1}/robert.egg-info/requires.txt +2 -0
- {robert-2.1.0 → robert-2.1.1}/setup.py +26 -7
- robert-2.1.1/tests/test_1curate.py +444 -0
- robert-2.1.1/tests/test_2generate.py +449 -0
- robert-2.1.1/tests/test_3verify.py +134 -0
- robert-2.1.1/tests/test_4predict.py +180 -0
- robert-2.1.1/tests/test_5aqme_n_full.py +478 -0
- robert-2.1.1/tests/test_7api.py +221 -0
- robert-2.1.1/tests/test_easyrob.py +2085 -0
- robert-2.1.0/robert.egg-info/SOURCES.txt +0 -77
- robert-2.1.0/tests/test_1curate.py +0 -371
- robert-2.1.0/tests/test_2generate.py +0 -291
- robert-2.1.0/tests/test_3verify.py +0 -116
- robert-2.1.0/tests/test_4predict.py +0 -171
- robert-2.1.0/tests/test_5aqme_n_full.py +0 -387
- {robert-2.1.0 → robert-2.1.1}/LICENSE +0 -0
- {robert-2.1.0 → robert-2.1.1}/README.md +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/__main__.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/aqme.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/curate.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/evaluate.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/generate.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/generate_utils.py +0 -0
- {robert-2.1.0/robert → robert-2.1.1/robert/gui_easyrob}/__init__.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/icons/Robert_icon.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/icons/Robert_logo.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/icons/Robert_logo_transparent.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/icons/info_icon.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/icons/pdf_icon.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/icons/play_icon.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/icons/stop_icon.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/model_params/ADAB_params.yaml +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/model_params/GB_params.yaml +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/model_params/GP_params.yaml +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/model_params/MVL_params.yaml +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/model_params/NN_params.yaml +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/model_params/RF_params.yaml +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/model_params/VR_params.yaml +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/predict.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/Robert_icon.ico +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/Robert_logo.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/Robert_logo1.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/abbrev.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/adv_anal.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/features.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/generate.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/outliers.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/pred.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/repro.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_0.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_1.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_10.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_2.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_3.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_4.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_5.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_6.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_7.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_8.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_9.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_1_0.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_1_1.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_2_0.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_2_1.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_2_2.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_3_0.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_3_1.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_3_2.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/score_w_3_3.jpg +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/transpa.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report/y_distrib.png +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/report_utils.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/robert.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert/verify.py +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert.egg-info/dependency_links.txt +0 -0
- {robert-2.1.0 → robert-2.1.1}/robert.egg-info/top_level.txt +0 -0
- {robert-2.1.0 → robert-2.1.1}/setup.cfg +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robert
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: Refiner and Optimizer of a Bunch of Existing Regression Tools
|
|
5
5
|
Home-page: https://github.com/jvalegre/robert
|
|
6
|
-
Download-URL: https://github.com/jvalegre/robert/archive/refs/tags/2.1.
|
|
6
|
+
Download-URL: https://github.com/jvalegre/robert/archive/refs/tags/2.1.1.tar.gz
|
|
7
7
|
Author: Juan V. Alegre Requena, David Dalmau
|
|
8
8
|
Author-email: jv.alegre@csic.es
|
|
9
9
|
License: MIT
|
|
@@ -33,6 +33,8 @@ Requires-Dist: shap==0.49.1
|
|
|
33
33
|
Requires-Dist: weasyprint==63.1
|
|
34
34
|
Requires-Dist: PySide6==6.9.2
|
|
35
35
|
Requires-Dist: PyMuPDF
|
|
36
|
+
Requires-Dist: pdfplumber
|
|
37
|
+
Requires-Dist: openpyxl
|
|
36
38
|
Requires-Dist: ansi2html
|
|
37
39
|
Requires-Dist: rdkit==2025.9.1
|
|
38
40
|
Requires-Dist: psutil==7.0.0
|
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sklearn-style facade over the ROBERT CLI pipeline.
|
|
3
|
+
|
|
4
|
+
``RobertModel`` runs CURATE through PREDICT (optional REPORT). Point
|
|
5
|
+
predictions, uncertainty columns, and matplotlib handling are documented in
|
|
6
|
+
``docs/API/robert.api.rst``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import glob
|
|
12
|
+
import os
|
|
13
|
+
import tempfile
|
|
14
|
+
import uuid
|
|
15
|
+
import warnings
|
|
16
|
+
from contextlib import contextmanager
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Literal, Optional, Tuple, Union
|
|
19
|
+
|
|
20
|
+
import matplotlib
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
22
|
+
import numpy as np
|
|
23
|
+
import pandas as pd
|
|
24
|
+
from sklearn.base import BaseEstimator
|
|
25
|
+
from sklearn.metrics import accuracy_score, r2_score
|
|
26
|
+
|
|
27
|
+
from robert.argument_parser import options_add, var_dict
|
|
28
|
+
from robert.curate import curate
|
|
29
|
+
from robert.generate import generate
|
|
30
|
+
from robert.predict import predict as predict_module
|
|
31
|
+
from robert.report import report as report_module
|
|
32
|
+
from robert.utils import load_params
|
|
33
|
+
from robert.verify import verify
|
|
34
|
+
|
|
35
|
+
_NAME_COL = "__robert_name__"
|
|
36
|
+
_DEFAULT_Y = "__robert_y__"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _filter_robert_kwargs(kwargs: dict) -> dict:
|
|
40
|
+
"""Keep only keys accepted by ``set_options`` / ``load_variables``."""
|
|
41
|
+
return {k: v for k, v in kwargs.items() if k in var_dict}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@contextmanager
|
|
45
|
+
def _chdir(path: Path):
|
|
46
|
+
prev = os.getcwd()
|
|
47
|
+
os.chdir(path)
|
|
48
|
+
try:
|
|
49
|
+
yield
|
|
50
|
+
finally:
|
|
51
|
+
os.chdir(prev)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@contextmanager
|
|
55
|
+
def _noninteractive_mpl():
|
|
56
|
+
"""Use Agg during ROBERT steps; restore the previous backend afterward."""
|
|
57
|
+
prev = matplotlib.get_backend()
|
|
58
|
+
was_interactive = matplotlib.is_interactive()
|
|
59
|
+
matplotlib.use("Agg", force=True)
|
|
60
|
+
plt.ioff()
|
|
61
|
+
try:
|
|
62
|
+
yield
|
|
63
|
+
finally:
|
|
64
|
+
matplotlib.use(prev, force=True)
|
|
65
|
+
if was_interactive:
|
|
66
|
+
plt.ion()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _ParamsAdapter:
|
|
70
|
+
"""Minimal ``self`` for :func:`robert.utils.load_params` / ``model_adjust_params``."""
|
|
71
|
+
|
|
72
|
+
__slots__ = ("args",)
|
|
73
|
+
|
|
74
|
+
def __init__(self, seed: int, type_: str):
|
|
75
|
+
self.args = options_add()
|
|
76
|
+
self.args.seed = seed
|
|
77
|
+
self.args.type = type_
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _suffix_title(filter_mode: str) -> str:
|
|
81
|
+
if filter_mode == "pfi":
|
|
82
|
+
return "PFI"
|
|
83
|
+
if filter_mode == "no_pfi":
|
|
84
|
+
return "No_PFI"
|
|
85
|
+
raise ValueError("filter_mode must be 'pfi' or 'no_pfi'")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _find_params_csv(best_subdir: Path) -> Path:
|
|
89
|
+
if not best_subdir.is_dir():
|
|
90
|
+
raise FileNotFoundError(str(best_subdir))
|
|
91
|
+
csvs = sorted(p for p in best_subdir.glob("*.csv") if not p.name.endswith("_db.csv"))
|
|
92
|
+
if len(csvs) != 1:
|
|
93
|
+
raise RuntimeError(
|
|
94
|
+
"Expected exactly one parameter CSV in "
|
|
95
|
+
f"{best_subdir}, found: {[p.name for p in csvs]}"
|
|
96
|
+
)
|
|
97
|
+
return csvs[0]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _resolve_prediction_id_column(
|
|
101
|
+
result_df: pd.DataFrame, names_key: str, model_names: str
|
|
102
|
+
) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Column in the PREDICT external CSV used to align rows to the API input order.
|
|
105
|
+
|
|
106
|
+
Prefer the API names column, then the name stored in GENERATE params, then a
|
|
107
|
+
single case-insensitive match. Avoid assuming ``result_df.columns[0]`` is the id
|
|
108
|
+
column (column order may change).
|
|
109
|
+
"""
|
|
110
|
+
cols = list(result_df.columns)
|
|
111
|
+
if names_key in result_df.columns:
|
|
112
|
+
return names_key
|
|
113
|
+
if model_names and model_names in result_df.columns:
|
|
114
|
+
return model_names
|
|
115
|
+
candidates = [c for c in (names_key, model_names) if c]
|
|
116
|
+
for cand in candidates:
|
|
117
|
+
cf = cand.casefold()
|
|
118
|
+
matches = [c for c in cols if str(c).casefold() == cf]
|
|
119
|
+
if len(matches) == 1:
|
|
120
|
+
return str(matches[0])
|
|
121
|
+
if len(matches) > 1:
|
|
122
|
+
raise RuntimeError(
|
|
123
|
+
f"Ambiguous id column for predictions: {matches!r} all match {cand!r} "
|
|
124
|
+
f"(case-insensitive). Columns in CSV: {cols!r}"
|
|
125
|
+
)
|
|
126
|
+
raise RuntimeError(
|
|
127
|
+
f"Could not find row id column for predictions (tried {names_key!r} and "
|
|
128
|
+
f"{model_names!r}). Columns in CSV: {cols!r}"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _resolve_predict_csv(workdir: Path, pred_stem: str, model_code: str, suffix: str) -> str:
|
|
133
|
+
"""Path to PREDICT output CSV for the external set (exact file, else sorted glob)."""
|
|
134
|
+
csv_dir = workdir / "PREDICT" / "csv_test"
|
|
135
|
+
exact = csv_dir / f"{pred_stem}_{model_code}_{suffix}.csv"
|
|
136
|
+
if exact.is_file():
|
|
137
|
+
return str(exact)
|
|
138
|
+
pattern = str(csv_dir / f"{pred_stem}_{model_code}_{suffix}.csv")
|
|
139
|
+
matches = sorted(glob.glob(pattern))
|
|
140
|
+
if not matches:
|
|
141
|
+
alt_pat = str(csv_dir / f"*{pred_stem}*{suffix}.csv")
|
|
142
|
+
matches = sorted(glob.glob(alt_pat))
|
|
143
|
+
if not matches:
|
|
144
|
+
raise RuntimeError(f"No prediction CSV found matching {pattern!r}")
|
|
145
|
+
if len(matches) > 1:
|
|
146
|
+
warnings.warn(
|
|
147
|
+
"Multiple prediction CSVs matched; using the first after lexicographic sort: "
|
|
148
|
+
f"{matches[0]!r}",
|
|
149
|
+
UserWarning,
|
|
150
|
+
stacklevel=3,
|
|
151
|
+
)
|
|
152
|
+
return matches[0]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class RobertModel(BaseEstimator):
|
|
156
|
+
"""
|
|
157
|
+
High-level ROBERT workflow with ``fit``, ``predict``, and ``score``.
|
|
158
|
+
|
|
159
|
+
Subclasses :class:`~sklearn.base.BaseEstimator` for ``get_params`` and
|
|
160
|
+
``set_params``. Preprocessing (CURATE encoding, ROBERT-internal
|
|
161
|
+
``StandardScaler`` on the design matrix) runs inside ROBERT; do not stack a
|
|
162
|
+
separate ``StandardScaler`` on the same raw descriptors. Refitting with the same
|
|
163
|
+
``workdir`` leaves prior outputs on disk. ``fit`` and ``predict`` change the
|
|
164
|
+
process working directory and are not safe to run concurrently on multiple
|
|
165
|
+
instances in one process.
|
|
166
|
+
|
|
167
|
+
:param problem_type: ``"reg"`` or ``"clas"``.
|
|
168
|
+
:param filter_mode: ``"pfi"`` or ``"no_pfi"``; which ``GENERATE/Best_model``
|
|
169
|
+
variant ``predict`` loads.
|
|
170
|
+
:param workdir: Directory for ROBERT outputs, or ``None`` for a managed temp dir.
|
|
171
|
+
:param names: Column in ``X`` used as row identifiers (CURATE ``names``), or ``None``.
|
|
172
|
+
:param report: If ``True``, run REPORT after PREDICT during ``fit``.
|
|
173
|
+
:param seed: Random seed forwarded to ROBERT, or ``None`` for package default.
|
|
174
|
+
:param y_column: If ``fit(X)`` is called with ``y is None``, name of the target column
|
|
175
|
+
in ``X`` (DataFrame only).
|
|
176
|
+
:param kwargs: Additional ROBERT options (keys in ``robert.argument_parser.var_dict``),
|
|
177
|
+
e.g. ``model``, ``n_iter``. Regression uncertainty tuning includes
|
|
178
|
+
``conformal_enable``, ``conformal_calib_frac``, and ``conformal_coverage``.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
problem_type: Literal["reg", "clas"] = "reg",
|
|
184
|
+
filter_mode: Literal["pfi", "no_pfi"] = "pfi",
|
|
185
|
+
workdir: Optional[Union[str, Path]] = None,
|
|
186
|
+
names: Optional[str] = None,
|
|
187
|
+
report: bool = False,
|
|
188
|
+
seed: Optional[int] = None,
|
|
189
|
+
y_column: Optional[str] = None,
|
|
190
|
+
**kwargs: Any,
|
|
191
|
+
) -> None:
|
|
192
|
+
super().__init__()
|
|
193
|
+
kwargs = dict(kwargs)
|
|
194
|
+
if "type" in kwargs:
|
|
195
|
+
warnings.warn(
|
|
196
|
+
"Passing 'type' to RobertModel is deprecated; use 'problem_type' instead.",
|
|
197
|
+
DeprecationWarning,
|
|
198
|
+
stacklevel=2,
|
|
199
|
+
)
|
|
200
|
+
if problem_type != "reg":
|
|
201
|
+
raise ValueError("Pass only one of 'problem_type' or deprecated 'type'.")
|
|
202
|
+
problem_type = kwargs.pop("type") # type: ignore[assignment]
|
|
203
|
+
if "filter" in kwargs:
|
|
204
|
+
warnings.warn(
|
|
205
|
+
"Passing 'filter' to RobertModel is deprecated; use 'filter_mode' instead.",
|
|
206
|
+
DeprecationWarning,
|
|
207
|
+
stacklevel=2,
|
|
208
|
+
)
|
|
209
|
+
if filter_mode != "pfi":
|
|
210
|
+
raise ValueError("Pass only one of 'filter_mode' or deprecated 'filter'.")
|
|
211
|
+
filter_mode = kwargs.pop("filter") # type: ignore[assignment]
|
|
212
|
+
|
|
213
|
+
self.problem_type = problem_type
|
|
214
|
+
self.filter_mode = filter_mode
|
|
215
|
+
self._user_names_col = names
|
|
216
|
+
self.run_report = report
|
|
217
|
+
self.y_column_init = y_column
|
|
218
|
+
self._workdir_raw: Optional[Union[str, Path]] = workdir
|
|
219
|
+
self._seed_explicit = seed
|
|
220
|
+
|
|
221
|
+
raw_kw = _filter_robert_kwargs(kwargs)
|
|
222
|
+
if seed is not None:
|
|
223
|
+
raw_kw["seed"] = seed
|
|
224
|
+
raw_kw["type"] = problem_type
|
|
225
|
+
self._rob_kwargs = raw_kw
|
|
226
|
+
|
|
227
|
+
self._managed_temp = workdir is None
|
|
228
|
+
self.workdir_: Optional[Path] = (
|
|
229
|
+
Path(workdir).resolve() if workdir is not None else None
|
|
230
|
+
)
|
|
231
|
+
self._tempdir_obj: Optional[tempfile.TemporaryDirectory[str]] = None
|
|
232
|
+
|
|
233
|
+
self.is_fitted_ = False
|
|
234
|
+
self.y_col_: str = ""
|
|
235
|
+
self.X_columns_: list[str] = []
|
|
236
|
+
self.model_data_: dict[str, Any] = {}
|
|
237
|
+
self.names_col_: str = ""
|
|
238
|
+
self.n_features_in_: int = 0
|
|
239
|
+
self.feature_names_in_: Optional[np.ndarray] = None
|
|
240
|
+
self._fit_input_was_dataframe = False
|
|
241
|
+
|
|
242
|
+
def get_params(self, deep: bool = True) -> dict[str, Any]:
|
|
243
|
+
"""Estimator and ROBERT option names from ``__init__`` (no nested estimators)."""
|
|
244
|
+
_ = deep
|
|
245
|
+
out: dict[str, Any] = {
|
|
246
|
+
"problem_type": self.problem_type,
|
|
247
|
+
"filter_mode": self.filter_mode,
|
|
248
|
+
"workdir": self._workdir_raw,
|
|
249
|
+
"names": self._user_names_col,
|
|
250
|
+
"report": self.run_report,
|
|
251
|
+
"seed": self._seed_explicit,
|
|
252
|
+
"y_column": self.y_column_init,
|
|
253
|
+
}
|
|
254
|
+
for k, v in self._rob_kwargs.items():
|
|
255
|
+
if k == "type":
|
|
256
|
+
continue
|
|
257
|
+
out[k] = v
|
|
258
|
+
return out
|
|
259
|
+
|
|
260
|
+
def set_params(self, **params: Any) -> "RobertModel":
|
|
261
|
+
"""Update ``RobertModel`` fields and ROBERT keys accepted by ``var_dict``."""
|
|
262
|
+
if not params:
|
|
263
|
+
return self
|
|
264
|
+
special = {
|
|
265
|
+
"problem_type",
|
|
266
|
+
"filter_mode",
|
|
267
|
+
"workdir",
|
|
268
|
+
"names",
|
|
269
|
+
"report",
|
|
270
|
+
"seed",
|
|
271
|
+
"y_column",
|
|
272
|
+
}
|
|
273
|
+
for key in list(params):
|
|
274
|
+
if key not in special and key not in var_dict:
|
|
275
|
+
raise ValueError(
|
|
276
|
+
f"RobertModel got invalid parameter {key!r}. "
|
|
277
|
+
"Use problem_type, filter_mode, workdir, names, report, seed, y_column, "
|
|
278
|
+
"or a key from robert.argument_parser.var_dict."
|
|
279
|
+
)
|
|
280
|
+
for key in list(params):
|
|
281
|
+
if key not in special:
|
|
282
|
+
continue
|
|
283
|
+
val = params.pop(key)
|
|
284
|
+
if key == "problem_type":
|
|
285
|
+
self.problem_type = val
|
|
286
|
+
self._rob_kwargs["type"] = val
|
|
287
|
+
elif key == "filter_mode":
|
|
288
|
+
self.filter_mode = val
|
|
289
|
+
elif key == "workdir":
|
|
290
|
+
self._workdir_raw = val
|
|
291
|
+
self.workdir_ = Path(val).resolve() if val is not None else None
|
|
292
|
+
self._managed_temp = val is None
|
|
293
|
+
self._tempdir_obj = None
|
|
294
|
+
elif key == "names":
|
|
295
|
+
self._user_names_col = val
|
|
296
|
+
elif key == "report":
|
|
297
|
+
self.run_report = val
|
|
298
|
+
elif key == "seed":
|
|
299
|
+
self._seed_explicit = val
|
|
300
|
+
if val is not None:
|
|
301
|
+
self._rob_kwargs["seed"] = val
|
|
302
|
+
else:
|
|
303
|
+
self._rob_kwargs.pop("seed", None)
|
|
304
|
+
elif key == "y_column":
|
|
305
|
+
self.y_column_init = val
|
|
306
|
+
for k, v in params.items():
|
|
307
|
+
self._rob_kwargs[k] = v
|
|
308
|
+
if k == "type":
|
|
309
|
+
self.problem_type = v # noqa: PLW2901
|
|
310
|
+
return self
|
|
311
|
+
|
|
312
|
+
def __enter__(self) -> "RobertModel":
|
|
313
|
+
self._ensure_workdir()
|
|
314
|
+
return self
|
|
315
|
+
|
|
316
|
+
def __exit__(self, *exc: Any) -> Literal[False]:
|
|
317
|
+
self.cleanup()
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
def cleanup(self) -> None:
|
|
321
|
+
"""Remove managed temporary workdir, if any. Safe to call multiple times."""
|
|
322
|
+
if self._tempdir_obj is not None:
|
|
323
|
+
self._tempdir_obj.cleanup()
|
|
324
|
+
self._tempdir_obj = None
|
|
325
|
+
self.workdir_ = None
|
|
326
|
+
self._managed_temp = self._workdir_raw is None
|
|
327
|
+
|
|
328
|
+
def _ensure_workdir(self) -> Path:
|
|
329
|
+
if self.workdir_ is not None:
|
|
330
|
+
self.workdir_.mkdir(parents=True, exist_ok=True)
|
|
331
|
+
return self.workdir_
|
|
332
|
+
if self._tempdir_obj is None:
|
|
333
|
+
self._tempdir_obj = tempfile.TemporaryDirectory(prefix="robert_api_")
|
|
334
|
+
self.workdir_ = Path(self._tempdir_obj.name).resolve()
|
|
335
|
+
self._managed_temp = True
|
|
336
|
+
assert self.workdir_ is not None
|
|
337
|
+
self.workdir_.mkdir(parents=True, exist_ok=True)
|
|
338
|
+
return self.workdir_
|
|
339
|
+
|
|
340
|
+
def _coerce_xy(
|
|
341
|
+
self,
|
|
342
|
+
X: Union[pd.DataFrame, np.ndarray],
|
|
343
|
+
y: Optional[Union[pd.Series, np.ndarray, list]],
|
|
344
|
+
names: Optional[str],
|
|
345
|
+
) -> Tuple[pd.DataFrame, pd.Series, str]:
|
|
346
|
+
if isinstance(X, np.ndarray):
|
|
347
|
+
if X.ndim != 2:
|
|
348
|
+
raise ValueError("X must be 2-D")
|
|
349
|
+
X_df = pd.DataFrame(X, columns=[f"x{i}" for i in range(X.shape[1])])
|
|
350
|
+
elif isinstance(X, pd.DataFrame):
|
|
351
|
+
X_df = X.copy()
|
|
352
|
+
else:
|
|
353
|
+
raise TypeError("X must be a pandas DataFrame or numpy.ndarray")
|
|
354
|
+
|
|
355
|
+
names_col = names or self._user_names_col
|
|
356
|
+
if names_col is not None and names_col not in X_df.columns:
|
|
357
|
+
raise ValueError(f"names column {names_col!r} not found in X")
|
|
358
|
+
|
|
359
|
+
if y is None:
|
|
360
|
+
ykey = self.y_column_init
|
|
361
|
+
if ykey is None or ykey not in X_df.columns:
|
|
362
|
+
raise ValueError(
|
|
363
|
+
"y is None but no valid y_column in X; pass y or set y_column="
|
|
364
|
+
)
|
|
365
|
+
y_series = X_df[ykey].copy()
|
|
366
|
+
X_df = X_df.drop(columns=[ykey])
|
|
367
|
+
else:
|
|
368
|
+
y_series = pd.Series(np.asarray(y).ravel(), index=X_df.index)
|
|
369
|
+
if isinstance(y, pd.Series) and y.name is not None and str(y.name).strip():
|
|
370
|
+
y_col_guess = str(y.name)
|
|
371
|
+
else:
|
|
372
|
+
y_col_guess = self.y_column_init or _DEFAULT_Y
|
|
373
|
+
y_series.name = y_col_guess
|
|
374
|
+
|
|
375
|
+
if names_col is None:
|
|
376
|
+
X_df = X_df.copy()
|
|
377
|
+
X_df.insert(0, _NAME_COL, X_df.index.astype(str))
|
|
378
|
+
names_col = _NAME_COL
|
|
379
|
+
|
|
380
|
+
y_col = str(y_series.name) if y_series.name is not None else _DEFAULT_Y
|
|
381
|
+
y_series.name = y_col
|
|
382
|
+
return X_df, y_series, names_col
|
|
383
|
+
|
|
384
|
+
def _build_train_frame(self, X_df: pd.DataFrame, y_series: pd.Series) -> pd.DataFrame:
|
|
385
|
+
out = X_df.copy()
|
|
386
|
+
out[y_series.name] = y_series.values
|
|
387
|
+
return out
|
|
388
|
+
|
|
389
|
+
def _read_model_snapshot(self, workdir: Path) -> dict[str, Any]:
|
|
390
|
+
sub = "PFI" if self.filter_mode == "pfi" else "No_PFI"
|
|
391
|
+
folder = workdir / "GENERATE" / "Best_model" / sub
|
|
392
|
+
params_path = _find_params_csv(folder)
|
|
393
|
+
seed = int(self._rob_kwargs.get("seed", var_dict["seed"]))
|
|
394
|
+
adapter = _ParamsAdapter(seed, self.problem_type)
|
|
395
|
+
return load_params(adapter, str(params_path))
|
|
396
|
+
|
|
397
|
+
def fit(
|
|
398
|
+
self,
|
|
399
|
+
X: Union[pd.DataFrame, np.ndarray],
|
|
400
|
+
y: Optional[Union[pd.Series, np.ndarray, list]] = None,
|
|
401
|
+
names: Optional[str] = None,
|
|
402
|
+
**fit_params: Any,
|
|
403
|
+
) -> "RobertModel":
|
|
404
|
+
_ = fit_params
|
|
405
|
+
self._fit_input_was_dataframe = isinstance(X, pd.DataFrame)
|
|
406
|
+
workdir = self._ensure_workdir()
|
|
407
|
+
X_df, y_series, names_col = self._coerce_xy(X, y, names)
|
|
408
|
+
train_csv = workdir / "_robert_train.csv"
|
|
409
|
+
train_rel = train_csv.name
|
|
410
|
+
y_col = str(y_series.name)
|
|
411
|
+
|
|
412
|
+
train_frame = self._build_train_frame(X_df, y_series)
|
|
413
|
+
train_frame.to_csv(train_csv, index=False)
|
|
414
|
+
|
|
415
|
+
base = dict(self._rob_kwargs)
|
|
416
|
+
base["command_line"] = False
|
|
417
|
+
base["csv_test"] = ""
|
|
418
|
+
|
|
419
|
+
with _noninteractive_mpl(), _chdir(workdir):
|
|
420
|
+
curate(
|
|
421
|
+
csv_name=train_rel,
|
|
422
|
+
y=y_col,
|
|
423
|
+
names=names_col,
|
|
424
|
+
**base,
|
|
425
|
+
)
|
|
426
|
+
generate(
|
|
427
|
+
csv_name="",
|
|
428
|
+
y="",
|
|
429
|
+
discard=[],
|
|
430
|
+
**base,
|
|
431
|
+
)
|
|
432
|
+
verify(**base)
|
|
433
|
+
predict_module(**base)
|
|
434
|
+
if self.run_report:
|
|
435
|
+
report_module(**base)
|
|
436
|
+
|
|
437
|
+
best_sub = workdir / "GENERATE" / "Best_model" / (
|
|
438
|
+
"PFI" if self.filter_mode == "pfi" else "No_PFI"
|
|
439
|
+
)
|
|
440
|
+
if self.filter_mode == "pfi" and not best_sub.is_dir():
|
|
441
|
+
raise RuntimeError(
|
|
442
|
+
"PFI model folder missing after fit. "
|
|
443
|
+
"Try filter_mode='no_pfi' or enable pfi_filter in ROBERT options."
|
|
444
|
+
)
|
|
445
|
+
if self.filter_mode == "no_pfi" and not best_sub.is_dir():
|
|
446
|
+
raise RuntimeError("No_PFI model folder missing after fit.")
|
|
447
|
+
|
|
448
|
+
model_data = self._read_model_snapshot(workdir)
|
|
449
|
+
self.model_data_ = model_data
|
|
450
|
+
self.y_col_ = str(model_data["y"])
|
|
451
|
+
descs = list(model_data["X_descriptors"])
|
|
452
|
+
self.X_columns_ = descs
|
|
453
|
+
self.n_features_in_ = len(descs)
|
|
454
|
+
if self._fit_input_was_dataframe:
|
|
455
|
+
self.feature_names_in_ = np.asarray(descs, dtype=object)
|
|
456
|
+
else:
|
|
457
|
+
self.feature_names_in_ = None
|
|
458
|
+
model_names = str(model_data.get("names") or "")
|
|
459
|
+
if model_names != names_col:
|
|
460
|
+
if model_names and names_col and model_names.casefold() == names_col.casefold():
|
|
461
|
+
warnings.warn(
|
|
462
|
+
f"Names column casing normalized to saved model column {model_names!r} "
|
|
463
|
+
f"(was {names_col!r}).",
|
|
464
|
+
UserWarning,
|
|
465
|
+
stacklevel=2,
|
|
466
|
+
)
|
|
467
|
+
names_col = model_names
|
|
468
|
+
else:
|
|
469
|
+
raise RuntimeError(
|
|
470
|
+
"Names column mismatch between training data "
|
|
471
|
+
f"({names_col!r}) and GENERATE params ({model_names!r}). "
|
|
472
|
+
"Refit with a clean workdir or align the names column with the model."
|
|
473
|
+
)
|
|
474
|
+
self.names_col_ = names_col
|
|
475
|
+
self.is_fitted_ = True
|
|
476
|
+
return self
|
|
477
|
+
|
|
478
|
+
def best_model_info(self) -> dict[str, Any]:
|
|
479
|
+
if not self.is_fitted_:
|
|
480
|
+
raise RuntimeError("Call fit before best_model_info.")
|
|
481
|
+
md = self.model_data_
|
|
482
|
+
return {
|
|
483
|
+
"model": md["model"],
|
|
484
|
+
"params": md["params"],
|
|
485
|
+
"descriptors": md["X_descriptors"],
|
|
486
|
+
"filter_mode": self.filter_mode,
|
|
487
|
+
"y": md["y"],
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
def predict(
|
|
491
|
+
self,
|
|
492
|
+
X: Union[pd.DataFrame, np.ndarray],
|
|
493
|
+
return_std: bool = False,
|
|
494
|
+
return_uncertainty: Literal[False, "cv_sd", "conformal", "both"] = False,
|
|
495
|
+
) -> Union[
|
|
496
|
+
np.ndarray,
|
|
497
|
+
Tuple[np.ndarray, np.ndarray],
|
|
498
|
+
Tuple[np.ndarray, np.ndarray, np.ndarray],
|
|
499
|
+
]:
|
|
500
|
+
if not self.is_fitted_:
|
|
501
|
+
raise RuntimeError("Call fit before predict.")
|
|
502
|
+
workdir = self.workdir_
|
|
503
|
+
assert workdir is not None
|
|
504
|
+
|
|
505
|
+
if return_uncertainty is not False:
|
|
506
|
+
umode: Literal[False, "cv_sd", "conformal", "both"] = return_uncertainty
|
|
507
|
+
if return_std:
|
|
508
|
+
warnings.warn(
|
|
509
|
+
"return_uncertainty is set; return_std is ignored.",
|
|
510
|
+
UserWarning,
|
|
511
|
+
stacklevel=2,
|
|
512
|
+
)
|
|
513
|
+
elif return_std:
|
|
514
|
+
umode = "cv_sd"
|
|
515
|
+
else:
|
|
516
|
+
umode = False
|
|
517
|
+
|
|
518
|
+
descriptors = list(self.model_data_["X_descriptors"])
|
|
519
|
+
y_target = str(self.model_data_["y"])
|
|
520
|
+
names_key = self.names_col_
|
|
521
|
+
|
|
522
|
+
if isinstance(X, np.ndarray):
|
|
523
|
+
if X.ndim != 2:
|
|
524
|
+
raise ValueError("X must be 2-D")
|
|
525
|
+
if X.shape[1] != len(descriptors):
|
|
526
|
+
raise ValueError(
|
|
527
|
+
f"X has {X.shape[1]} columns but model expects "
|
|
528
|
+
f"{len(descriptors)} descriptors"
|
|
529
|
+
)
|
|
530
|
+
X_df = pd.DataFrame(X, columns=descriptors, copy=False)
|
|
531
|
+
else:
|
|
532
|
+
X_df = X.copy()
|
|
533
|
+
missing = [c for c in descriptors if c not in X_df.columns]
|
|
534
|
+
if missing:
|
|
535
|
+
tail = "..." if len(missing) > 10 else ""
|
|
536
|
+
raise ValueError(
|
|
537
|
+
f"Missing descriptor columns: {missing[:10]!r}{tail}"
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
pred_id = uuid.uuid4().hex[:12]
|
|
541
|
+
pred_name = f"_robert_predict_{pred_id}.csv"
|
|
542
|
+
pred_path = workdir / pred_name
|
|
543
|
+
|
|
544
|
+
if names_key and names_key in X_df.columns:
|
|
545
|
+
X_out = X_df[[names_key] + descriptors].copy()
|
|
546
|
+
else:
|
|
547
|
+
X_out = X_df[descriptors].copy()
|
|
548
|
+
X_out.insert(0, names_key or _NAME_COL, X_out.index.astype(str))
|
|
549
|
+
|
|
550
|
+
order_keys = X_out.iloc[:, 0].astype(str)
|
|
551
|
+
if order_keys.duplicated().any():
|
|
552
|
+
raise ValueError(
|
|
553
|
+
"Duplicate values in the names column; cannot align predictions to rows."
|
|
554
|
+
)
|
|
555
|
+
X_out.to_csv(pred_path, index=False)
|
|
556
|
+
|
|
557
|
+
base = dict(self._rob_kwargs)
|
|
558
|
+
base["command_line"] = False
|
|
559
|
+
base["csv_test"] = pred_name
|
|
560
|
+
base["params_dir"] = "GENERATE/Best_model"
|
|
561
|
+
base["names"] = self.names_col_
|
|
562
|
+
|
|
563
|
+
with _noninteractive_mpl(), _chdir(workdir):
|
|
564
|
+
predict_module(**base)
|
|
565
|
+
|
|
566
|
+
suffix = _suffix_title(self.filter_mode)
|
|
567
|
+
model_code = str(self.model_data_["model"])
|
|
568
|
+
pred_stem = Path(pred_name).stem
|
|
569
|
+
csv_path = _resolve_predict_csv(workdir, pred_stem, model_code, suffix)
|
|
570
|
+
|
|
571
|
+
result_df = pd.read_csv(csv_path, encoding="utf-8")
|
|
572
|
+
pred_col = f"{y_target}_pred"
|
|
573
|
+
sd_col = f"{y_target}_pred_sd"
|
|
574
|
+
hw_col = f"{y_target}_pred_conformal_hw"
|
|
575
|
+
if pred_col not in result_df.columns:
|
|
576
|
+
raise RuntimeError(f"Column {pred_col!r} missing in {csv_path}")
|
|
577
|
+
|
|
578
|
+
model_names = str(self.model_data_.get("names") or "")
|
|
579
|
+
name_col_result = _resolve_prediction_id_column(
|
|
580
|
+
result_df, names_key, model_names
|
|
581
|
+
)
|
|
582
|
+
aligned = result_df.set_index(
|
|
583
|
+
result_df[name_col_result].astype(str), drop=False
|
|
584
|
+
)
|
|
585
|
+
try:
|
|
586
|
+
ordered = aligned.reindex(order_keys.values)
|
|
587
|
+
except ValueError as err:
|
|
588
|
+
raise RuntimeError(
|
|
589
|
+
"Could not align predictions to input row names; duplicate names?"
|
|
590
|
+
) from err
|
|
591
|
+
if ordered[pred_col].isna().any():
|
|
592
|
+
missing_n = int(ordered[pred_col].isna().sum())
|
|
593
|
+
raise RuntimeError(
|
|
594
|
+
f"Prediction CSV row count or names mismatch ({missing_n} missing rows)."
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
y_pred = ordered[pred_col].to_numpy()
|
|
598
|
+
try:
|
|
599
|
+
os.remove(pred_path)
|
|
600
|
+
except OSError:
|
|
601
|
+
pass
|
|
602
|
+
|
|
603
|
+
if umode is False:
|
|
604
|
+
return y_pred
|
|
605
|
+
|
|
606
|
+
if umode in ("cv_sd", "both"):
|
|
607
|
+
if sd_col not in result_df.columns:
|
|
608
|
+
raise RuntimeError(f"Column {sd_col!r} missing in {csv_path}")
|
|
609
|
+
y_sd = ordered[sd_col].to_numpy(dtype=float)
|
|
610
|
+
if umode in ("conformal", "both"):
|
|
611
|
+
if self.problem_type != "reg":
|
|
612
|
+
raise ValueError(
|
|
613
|
+
"return_uncertainty='conformal' or 'both' is only supported "
|
|
614
|
+
"for problem_type='reg'."
|
|
615
|
+
)
|
|
616
|
+
if hw_col not in result_df.columns:
|
|
617
|
+
raise RuntimeError(f"Column {hw_col!r} missing in {csv_path}")
|
|
618
|
+
y_hw = ordered[hw_col].to_numpy(dtype=float)
|
|
619
|
+
if np.all(np.isnan(y_hw)):
|
|
620
|
+
raise RuntimeError(
|
|
621
|
+
f"Column {hw_col!r} has no finite values; disable conformal "
|
|
622
|
+
"or use a larger training set."
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
if umode == "cv_sd":
|
|
626
|
+
return y_pred, y_sd
|
|
627
|
+
if umode == "conformal":
|
|
628
|
+
return y_pred, y_hw
|
|
629
|
+
return y_pred, y_sd, y_hw
|
|
630
|
+
|
|
631
|
+
def score(
|
|
632
|
+
self,
|
|
633
|
+
X: Union[pd.DataFrame, np.ndarray],
|
|
634
|
+
y: Union[pd.Series, np.ndarray, list],
|
|
635
|
+
) -> float:
|
|
636
|
+
y_true = np.asarray(y).ravel()
|
|
637
|
+
y_hat = np.asarray(self.predict(X, return_std=False)).ravel()
|
|
638
|
+
if self.problem_type == "reg":
|
|
639
|
+
return float(r2_score(y_true, y_hat))
|
|
640
|
+
return float(
|
|
641
|
+
accuracy_score(
|
|
642
|
+
np.round(y_true).astype(int),
|
|
643
|
+
np.round(y_hat).astype(int),
|
|
644
|
+
)
|
|
645
|
+
)
|