Skip to content

Commit 2b1d17b

Browse files
0.14.43
emove_nan(self.X, self.y, stop_on_zero_return)
1 parent ab9940a commit 2b1d17b

9 files changed

Lines changed: 174 additions & 19 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
77

88
[project]
99
name = "spotpython"
10-
version = "0.14.42"
10+
version = "0.14.43"
1111
authors = [
1212
{ name="T. Bartz-Beielstein", email="tbb@bartzundbartz.de" }
1313
]

src/spotPython/hyperparameters/values.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,7 +1804,11 @@ def set_hyperparameter(fun_control, key, values):
18041804
>>> set_hyperparameter(fun_control, "step", [0.2, 5.0])
18051805
>>> set_hyperparameter(fun_control, "use_aggregation", [False, True])
18061806
>>> set_hyperparameter(fun_control, "leaf_model", ["LinearRegression", "Perceptron"])
1807+
>>> set_hyperparameter(fun_control, "leaf_model", "LinearRegression")
18071808
"""
1809+
# if values is only a string and not a list of strings, convert it to a list
1810+
if isinstance(values, str):
1811+
values = [values]
18081812
if isinstance(values, list):
18091813
if all(isinstance(v, int) for v in values):
18101814
_set_int_hyperparameter_values(fun_control, key, values[0], values[1])

src/spotPython/plot/xy.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
4+
5+
def plot_y_vs_X(X, y, nrows=5, ncols=2, figsize=(30, 20), ylabel="y", feature_names=None):
6+
"""
7+
Plots y versus each feature in X.
8+
9+
Args:
10+
X (ndarray):
11+
2D array of input features.
12+
y (ndarray):
13+
1D array of target values.
14+
nrows (int, optional):
15+
Number of rows in the subplot grid. Defaults to 5.
16+
ncols (int, optional):
17+
Number of columns in the subplot grid. Defaults to 2.
18+
figsize (tuple, optional):
19+
Size of the entire figure. Defaults to (30, 20).
20+
ylabel (str, optional):
21+
Label for the y-axis. Defaults to 'y'.
22+
feature_names (list of str, optional):
23+
List of feature names. Defaults to None. If None, generates feature names as x0, x1, etc.
24+
25+
Example:
26+
>>> from sklearn.datasets import load_diabetes
27+
>>> from spotPython.plot.xy import plot_y_vs_X
28+
>>> data = load_diabetes()
29+
>>> X, y = data.data, data.target
30+
>>> plot_y_vs_X(X, y, nrows=5, ncols=2, figsize=(20, 15))
31+
"""
32+
if feature_names is None:
33+
feature_names = [f"x{i}" for i in range(X.shape[1])]
34+
35+
fig, axs = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize)
36+
37+
for i, (ax, col) in enumerate(zip(axs.flat, feature_names)):
38+
x = X[:, i]
39+
pf = np.polyfit(x, y, 1)
40+
p = np.poly1d(pf)
41+
42+
ax.plot(x, y, "o")
43+
ax.plot(x, p(x), "r--")
44+
45+
ax.set_title(col + " " + ylabel)
46+
ax.set_xlabel(col)
47+
ax.set_ylabel(ylabel)
48+
49+
plt.tight_layout()
50+
plt.show()

src/spotPython/spot/spot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ def initialize_design(self, X_start=None) -> None:
932932
writer.add_hparams(config, {"spot_y": y_j})
933933
writer.flush()
934934
#
935-
self.X, self.y = remove_nan(self.X, self.y)
935+
self.X, self.y = remove_nan(self.X, self.y, stop_on_zero_return=True)
936936
logger.debug("In Spot() initialize_design(), final X val, after remove nan: self.X: %s", self.X)
937937
logger.debug("In Spot() initialize_design(), final y val, after remove nan: self.y: %s", self.y)
938938

@@ -1063,7 +1063,7 @@ def update_design(self) -> None:
10631063
)
10641064
# (S-18): Evaluating New Solutions:
10651065
y0 = self.fun(X=X_all, fun_control=self.fun_control)
1066-
X0, y0 = remove_nan(X0, y0)
1066+
X0, y0 = remove_nan(X0, y0, stop_on_zero_return=False)
10671067
# Append New Solutions:
10681068
self.X = np.append(self.X, X0, axis=0)
10691069
self.y = np.append(self.y, y0)

src/spotPython/utils/file.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import sys
77
import importlib
8+
from spotPython.hyperparameters.values import get_tuned_architecture
89

910
# from torch.utils.tensorboard import SummaryWriter
1011

@@ -178,3 +179,26 @@ def load_core_model_from_file(coremodel, dirname="userModel"):
178179
module = importlib.import_module(coremodel)
179180
core_model = getattr(module, coremodel)
180181
return core_model
182+
183+
184+
def get_experiment_from_PREFIX(PREFIX) -> tuple:
185+
"""
186+
Setup the experiment based on the PREFIX provided and return the relevant configuration
187+
and control objects.
188+
189+
Args:
190+
PREFIX (str): The prefix for the experiment filename.
191+
192+
Returns:
193+
tuple:
194+
A tuple containing config, spot_tuner, fun_control, design_control, surrogate_control,
195+
and optimizer_control.
196+
197+
Example:
198+
>>> config, _, _, _, _, _ = get_experiment_from_PREFIX("100")
199+
200+
"""
201+
experiment_name = get_experiment_filename(PREFIX)
202+
spot_tuner, fun_control, design_control, surrogate_control, optimizer_control = load_experiment(experiment_name)
203+
config = get_tuned_architecture(spot_tuner, fun_control)
204+
return config, spot_tuner, fun_control, design_control, surrogate_control, optimizer_control

src/spotPython/utils/repair.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,23 @@ def repair_non_numeric(X: np.ndarray, var_type: List[str]) -> np.ndarray:
2727
return X
2828

2929

30-
def remove_nan(X: np.ndarray, y: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
31-
"""
32-
Remove rows from X and y where y contains NaN values and issue a warning
33-
if the dimension of the returned y array is smaller than the dimension of the original y array.
34-
Issues a ValueError if the dimension of the returned y array is less than 2.
30+
def remove_nan(X: np.ndarray, y: np.ndarray, stop_on_zero_return: bool = False) -> Tuple[np.ndarray, np.ndarray]:
31+
"""Remove rows from X and y where y contains NaN values and issue a warning
32+
if the dimension of the returned y array is smaller than the dimension of the original y array.
33+
Issues a ValueError if the dimension of the returned y array is less than 2.
3534
3635
Args:
37-
X (numpy.ndarray): X array
38-
y (numpy.ndarray): y array
36+
X (numpy.ndarray):
37+
X array
38+
y (numpy.ndarray):
39+
y array
40+
stop_on_zero_return (bool):
41+
whether to stop if the returned dimension is less than 1.
42+
Default is False.
3943
4044
Returns:
4145
Tuple[numpy.ndarray, np.ndarray]:
42-
X and y arrays with rows containing NaN values in y removed.
46+
X and y arrays with rows containing NaN values in y removed.
4347
4448
Examples:
4549
>>> import numpy as np
@@ -70,7 +74,7 @@ def remove_nan(X: np.ndarray, y: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
7074
)
7175
warnings.warn("\n!!! Check whether to continue with the reduced dimension is useful.")
7276
# throw an error if the returned dimension is smaller than one
73-
if returned_dim < 1:
77+
if returned_dim < 1 and stop_on_zero_return:
7478
raise ValueError("!!!! The dimension of the returned y array is less than 1. Check the input data.")
7579

7680
return X_cleaned, y_cleaned
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from spotPython.utils.file import get_experiment_from_PREFIX
2+
import pytest
3+
from unittest.mock import patch
4+
5+
6+
def test_get_experiment_from_PREFIX_invalid_prefix():
7+
PREFIX = "invalid"
8+
9+
with patch(
10+
"spotPython.utils.file.get_experiment_filename", return_value=None
11+
) as mock_get_experiment_filename, patch(
12+
"spotPython.utils.file.load_experiment", side_effect=FileNotFoundError("Experiment not found")
13+
) as mock_load_experiment:
14+
with pytest.raises(FileNotFoundError, match="Experiment not found"):
15+
get_experiment_from_PREFIX(PREFIX)
16+
17+
# Ensure the filename function was called
18+
mock_get_experiment_filename.assert_called_once_with(PREFIX)
19+
# Ensure the load experiment function was called
20+
mock_load_experiment.assert_called_once()

test/test_remove_nan.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import numpy as np
22
from spotPython.utils.repair import remove_nan
33
import pytest
4+
import warnings
5+
6+
7+
def test_remove_nan_dimension_error():
8+
# Case that should raise ValueError
9+
X = np.array([[1, 2]])
10+
y = np.array([np.nan])
11+
with pytest.raises(ValueError):
12+
X_cleaned, y_cleaned = remove_nan(X, y, stop_on_zero_return=True)
413

514

615
def test_remove_nan_no_nan():
@@ -30,9 +39,44 @@ def test_remove_nan_dimension_warning():
3039
assert y_cleaned.shape[0] < y.shape[0], "Expected dimension reduction did not trigger a warning."
3140

3241

33-
def test_remove_nan_dimension_error():
34-
# Case that should raise ValueError
35-
X = np.array([[1, 2]])
36-
y = np.array([np.nan])
37-
with pytest.raises(ValueError):
42+
def test_remove_nan_basic():
43+
X = np.array([[1, 2], [3, 4], [5, 6]])
44+
y = np.array([1, np.nan, 2])
45+
X_cleaned, y_cleaned = remove_nan(X, y)
46+
np.testing.assert_array_equal(X_cleaned, np.array([[1, 2], [5, 6]]))
47+
np.testing.assert_array_equal(y_cleaned, np.array([1, 2]))
48+
49+
50+
def test_remove_nan_warning():
51+
X = np.array([[1, 2], [3, 4], [5, 6]])
52+
y = np.array([1, np.nan, 2])
53+
with warnings.catch_warnings(record=True) as w:
54+
warnings.simplefilter("always")
3855
X_cleaned, y_cleaned = remove_nan(X, y)
56+
assert len(w) == 2
57+
assert issubclass(w[-1].category, UserWarning)
58+
assert "smaller than the original dimension" in str(w[0].message)
59+
assert "Check whether to continue with the reduced dimension is useful" in str(w[1].message)
60+
61+
62+
def test_remove_nan_value_error():
63+
X = np.array([[1, 2], [3, 4], [5, 6]])
64+
y = np.array([np.nan, np.nan, np.nan])
65+
with pytest.raises(ValueError):
66+
remove_nan(X, y, stop_on_zero_return=True)
67+
68+
69+
def test_no_nan():
70+
X = np.array([[1, 2], [3, 4], [5, 6]])
71+
y = np.array([1, 2, 3])
72+
X_cleaned, y_cleaned = remove_nan(X, y)
73+
np.testing.assert_array_equal(X_cleaned, X)
74+
np.testing.assert_array_equal(y_cleaned, y)
75+
76+
77+
def test_remove_nan_empty_X():
78+
X = np.array([[], [], []])
79+
y = np.array([1, np.nan, 2])
80+
X_cleaned, y_cleaned = remove_nan(X, y)
81+
np.testing.assert_array_equal(X_cleaned, np.array([[], []]))
82+
np.testing.assert_array_equal(y_cleaned, np.array([1, 2]))

test/test_set_hyperparameter.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,22 @@ def test_set_hyperparameter_boolean():
2929

3030
def test_set_hyperparameter_factor():
3131
fun_control = {
32-
"core_model_hyper_dict": {"leaf_model": {"type": "factor", "default": "LinearRegression", "upper": 1}}
32+
"core_model_hyper_dict": {"leaf_model": {"type": "factor", "default": "LinearRegression", "upper": 2}}
3333
}
3434
set_hyperparameter(fun_control, "leaf_model", ["LinearRegression", "Perceptron"])
3535
assert fun_control["core_model_hyper_dict"]["leaf_model"]["levels"] == ["LinearRegression", "Perceptron"]
3636
assert fun_control["core_model_hyper_dict"]["leaf_model"]["upper"] == 1
3737

3838

39+
def test_set_hyperparameter_single_string():
40+
fun_control = {
41+
"core_model_hyper_dict": {"leaf_model": {"type": "factor", "default": "LinearRegression", "upper": 0}}
42+
}
43+
set_hyperparameter(fun_control, "leaf_model", "LinearRegression")
44+
assert fun_control["core_model_hyper_dict"]["leaf_model"]["levels"] == ["LinearRegression"]
45+
assert fun_control["core_model_hyper_dict"]["leaf_model"]["upper"] == 0
46+
47+
3948
def test_set_hyperparameter_invalid_type():
4049
fun_control = {"core_model_hyper_dict": {"n_estimators": {"type": "int", "default": 10, "lower": 2, "upper": 1000}}}
4150
with pytest.raises(ValueError):
@@ -45,4 +54,4 @@ def test_set_hyperparameter_invalid_type():
4554
def test_set_hyperparameter_invalid_values_type():
4655
fun_control = {"core_model_hyper_dict": {"n_estimators": {"type": "int", "default": 10, "lower": 2, "upper": 1000}}}
4756
with pytest.raises(TypeError):
48-
set_hyperparameter(fun_control, "n_estimators", "2, 5")
57+
set_hyperparameter(fun_control, "n_estimators", 2, 5)

0 commit comments

Comments
 (0)