Skip to content

Commit 120ed78

Browse files
0.23.4
convert_keys() updated
1 parent 1862cea commit 120ed78

6 files changed

Lines changed: 260 additions & 108 deletions

File tree

notebooks/00_spotPython_tests.ipynb

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7827,26 +7827,9 @@
78277827
},
78287828
{
78297829
"cell_type": "code",
7830-
"execution_count": 4,
7830+
"execution_count": null,
78317831
"metadata": {},
7832-
"outputs": [
7833-
{
7834-
"name": "stderr",
7835-
"output_type": "stream",
7836-
"text": [
7837-
"Seed set to 123\n"
7838-
]
7839-
},
7840-
{
7841-
"name": "stdout",
7842-
"output_type": "stream",
7843-
"text": [
7844-
"Experiment saved to test_get_spot_attributes_as_df_exp.pkl\n",
7845-
"Result file test_get_spot_attributes_as_df_res.pkl exists. Loading the result.\n",
7846-
"Loaded experiment from test_get_spot_attributes_as_df_res.pkl\n"
7847-
]
7848-
}
7849-
],
7832+
"outputs": [],
78507833
"source": [
78517834
"import pytest\n",
78527835
"import numpy as np\n",
@@ -7942,6 +7925,56 @@
79427925
"assert not lower_row.empty and lower_row['Attribute Value'].values[0] == [-1]"
79437926
]
79447927
},
7928+
{
7929+
"cell_type": "markdown",
7930+
"metadata": {},
7931+
"source": [
7932+
"## iterate_dic_values()"
7933+
]
7934+
},
7935+
{
7936+
"cell_type": "code",
7937+
"execution_count": null,
7938+
"metadata": {},
7939+
"outputs": [],
7940+
"source": [
7941+
"import numpy as np\n",
7942+
"from spotpython.hyperparameters.values import iterate_dict_values\n",
7943+
"var_dict = {'a': np.array([1, 3, 5]), 'b': np.array([2, 4, 6])}\n",
7944+
"print(var_dict)\n",
7945+
"list(iterate_dict_values(var_dict))\n"
7946+
]
7947+
},
7948+
{
7949+
"cell_type": "markdown",
7950+
"metadata": {},
7951+
"source": [
7952+
"## convert_keys()"
7953+
]
7954+
},
7955+
{
7956+
"cell_type": "code",
7957+
"execution_count": 6,
7958+
"metadata": {},
7959+
"outputs": [
7960+
{
7961+
"data": {
7962+
"text/plain": [
7963+
"{'a': 1, 'b': 2.1, 'c': 3}"
7964+
]
7965+
},
7966+
"execution_count": 6,
7967+
"metadata": {},
7968+
"output_type": "execute_result"
7969+
}
7970+
],
7971+
"source": [
7972+
"from spotpython.hyperparameters.values import convert_keys\n",
7973+
"d = {'a': 1, 'b': 2.1, 'c': 3}\n",
7974+
"var_type = [\"int\", \"num\", \"int\"]\n",
7975+
"convert_keys(d, var_type)\n"
7976+
]
7977+
},
79457978
{
79467979
"cell_type": "code",
79477980
"execution_count": null,

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.23.3"
10+
version = "0.23.4"
1111
authors = [
1212
{ name="T. Bartz-Beielstein", email="tbb@bartzundbartz.de" }
1313
]

src/spotpython/hyperparameters/values.py

Lines changed: 129 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,135 @@ def assign_values(X: np.array, var_list: list) -> dict:
6565
return result
6666

6767

68+
def iterate_dict_values(var_dict: Dict[str, np.ndarray]) -> Generator[Dict[str, Union[int, float]], None, None]:
69+
"""Iterate over the values of a dictionary of variables.
70+
This function takes a dictionary of variables as input arguments and returns a generator that
71+
yields dictionaries with the values from the arrays in the input dictionary.
72+
73+
Args:
74+
var_dict (dict): A dictionary where keys are variable names and values are numpy arrays.
75+
76+
Returns:
77+
Generator[dict]:
78+
A generator that yields dictionaries with the values from the arrays in the input dictionary.
79+
80+
Raises:
81+
ValueError: If the arrays in the dictionary do not have the same length.
82+
83+
Examples:
84+
>>> import numpy as np
85+
>>> from spotpython.hyperparameters.values import iterate_dict_values
86+
>>> var_dict = {'a': np.array([1, 3, 5]), 'b': np.array([2, 4, 6])}
87+
>>> print(var_dict)
88+
{'a': array([1, 3, 5]), 'b': array([2, 4, 6])}
89+
>>> list(iterate_dict_values(var_dict))
90+
[{'a': np.int64(1), 'b': np.int64(2)},
91+
{'a': np.int64(3), 'b': np.int64(4)},
92+
{'a': np.int64(5), 'b': np.int64(6)}]
93+
"""
94+
# Check if the dictionary is empty
95+
if not var_dict:
96+
return
97+
98+
# Get the length of the first array
99+
first_length = None
100+
for value in var_dict.values():
101+
if first_length is None:
102+
first_length = len(value)
103+
elif len(value) != first_length:
104+
raise ValueError("All arrays must have the same length.")
105+
106+
# Generate the output dictionaries
107+
for i in range(first_length):
108+
yield {key: value[i] for key, value in var_dict.items()}
109+
110+
111+
def convert_keys(d: Dict[str, Union[int, float, str]], var_type: List[str]) -> Dict[str, Union[int, float]]:
112+
"""Convert values in a dictionary to integers or floats based on a list of variable types.
113+
This function processes a dictionary 'd' based on the list of variable types 'var_type'.
114+
It handles the conversion of strings or other compatible types to 'int' or 'float' as specified.
115+
Specifically:
116+
1. If `var_type[i]` is anything other than `"num"` or `"float"`, the value should be converted to `int()`.
117+
If the conversion fails (i.e., if the value is not an integer value or representation), an error is raised.
118+
2. If `var_type[i]` is `"float"`, the value should be converted to `float()`.
119+
3. If `var_type[i]` is `"num"`, the function should decide whether to convert the value to `int()` or `float()`
120+
based on whether it represents an integer or a float.
121+
122+
Args:
123+
d (dict): The input dictionary with values to convert.
124+
var_type (list): A list of variable types where:
125+
- Not "num" or "float": the value is converted to int(). If conversion to int() fails, an error is raised.
126+
- "float": convert the value to a float.
127+
- "num": the value is converted to int() if it represents an integer, otherwise to float().
128+
129+
Returns:
130+
dict: A modified dictionary with values converted based on 'var_type' settings.
131+
132+
Raises:
133+
ValueError: If the conversion to an integer is not possible when required.
134+
135+
Examples:
136+
>>> from spotpython.hyperparameters.values import convert_keys
137+
d = {'a': 1, 'b': 2.1, 'c': 3}
138+
var_type = ["int", "num", "int"]
139+
convert_keys(d, var_type)
140+
{'a': 1, 'b': 2.1, 'c': 3}
141+
"""
142+
keys = list(d.keys())
143+
144+
for i in range(len(keys)):
145+
try:
146+
if var_type[i] not in ["num", "float"]:
147+
value = float(d[keys[i]])
148+
if value.is_integer():
149+
d[keys[i]] = int(value)
150+
else:
151+
raise ValueError(f"Invalid value for conversion at {keys[i]}: {d[keys[i]]} (not an integer)")
152+
elif var_type[i] == "float":
153+
d[keys[i]] = float(d[keys[i]])
154+
elif var_type[i] == "num":
155+
value = float(d[keys[i]])
156+
d[keys[i]] = int(value) if value.is_integer() else value
157+
except (ValueError, TypeError) as e:
158+
raise ValueError(f"Invalid value for conversion at {keys[i]}: {d[keys[i]]}")
159+
160+
return d
161+
162+
163+
def get_one_config_from_X(X, fun_control=None):
164+
"""Get one config from X.
165+
166+
Args:
167+
X (np.array):
168+
The array with the hyper parameter values.
169+
fun_control (dict):
170+
The function control dictionary.
171+
172+
Returns:
173+
(dict):
174+
The config dictionary.
175+
176+
Examples:
177+
>>> from river.tree import HoeffdingAdaptiveTreeRegressor
178+
from spotriver.data.river_hyper_dict import RiverHyperDict
179+
fun_control = {}
180+
add_core_model_to_fun_control(core_model=HoeffdingAdaptiveTreeRegressor,
181+
fun_control=func_control,
182+
hyper_dict=RiverHyperDict,
183+
filename=None)
184+
X = np.array([0, 0, 0, 0, 0])
185+
get_one_config_from_X(X, fun_control)
186+
{'leaf_prediction': 'mean',
187+
'leaf_model': 'NBAdaptive',
188+
'splitter': 'HoeffdingAdaptiveTreeSplitter',
189+
'binary_split': 'info_gain',
190+
'stop_mem_management': False}
191+
"""
192+
var_dict = assign_values(X, fun_control["var_name"])
193+
config = return_conf_list_from_var_dict(var_dict, fun_control)[0]
194+
return config
195+
196+
68197
def get_tuned_architecture(spot_tuner, force_minX=False) -> dict:
69198
"""
70199
Returns the tuned architecture. If the spot tuner has noise,
@@ -163,60 +292,6 @@ def return_conf_list_from_var_dict(
163292
return conf_list
164293

165294

166-
def iterate_dict_values(var_dict: Dict[str, np.ndarray]) -> Generator[Dict[str, Union[int, float]], None, None]:
167-
"""Iterate over the values of a dictionary of variables.
168-
This function takes a dictionary of variables as input arguments and returns a generator that
169-
yields dictionaries with the values from the arrays in the input dictionary.
170-
171-
Args:
172-
var_dict (dict): A dictionary where keys are variable names and values are numpy arrays.
173-
174-
Returns:
175-
Generator[dict]:
176-
A generator that yields dictionaries with the values from the arrays in the input dictionary.
177-
178-
Examples:
179-
>>> import numpy as np
180-
>>> from spotpython.hyperparameters.values import iterate_dict_values
181-
>>> var_dict = {'a': np.array([1, 3, 5]), 'b': np.array([2, 4, 6])}
182-
>>> list(iterate_dict_values(var_dict))
183-
[{'a': 1, 'b': 2}, {'a': 3, 'b': 4}, {'a': 5, 'b': 6}]
184-
"""
185-
n = len(next(iter(var_dict.values())))
186-
for i in range(n):
187-
yield {key: value[i] for key, value in var_dict.items()}
188-
189-
190-
def convert_keys(d: Dict[str, Union[int, float, str]], var_type: List[str]) -> Dict[str, Union[int, float]]:
191-
"""Convert values in a dictionary to integers based on a list of variable types.
192-
This function takes a dictionary `d` and a list of variable types `var_type` as arguments.
193-
For each key in the dictionary,
194-
if the corresponding entry in `var_type` is not equal to `"num"`,
195-
the value associated with that key is converted to an integer.
196-
197-
Args:
198-
d (dict): The input dictionary.
199-
var_type (list):
200-
A list of variable types. If the entry is not `"num"` the corresponding
201-
value will be converted to the type `"int"`.
202-
203-
Returns:
204-
dict: The modified dictionary with values converted to integers based on `var_type`.
205-
206-
Examples:
207-
>>> from spotpython.hyperparameters.values import convert_keys
208-
>>> d = {'a': '1.1', 'b': '2', 'c': '3.1'}
209-
>>> var_type = ["int", "num", "int"]
210-
>>> convert_keys(d, var_type)
211-
{'a': 1, 'b': '2', 'c': 3}
212-
"""
213-
keys = list(d.keys())
214-
for i in range(len(keys)):
215-
if var_type[i] not in ["num", "float"]:
216-
d[keys[i]] = int(d[keys[i]])
217-
return d
218-
219-
220295
def get_dict_with_levels_and_types(fun_control: Dict[str, Any], v: Dict[str, Any], default=False) -> Dict[str, Any]:
221296
"""Get dictionary with levels and types.
222297
The function maps the numerical output of the hyperparameter optimization to the corresponding levels
@@ -877,40 +952,6 @@ def get_one_core_model_from_X(
877952
return core_model
878953

879954

880-
def get_one_config_from_X(X, fun_control=None):
881-
"""Get one config from X.
882-
883-
Args:
884-
X (np.array):
885-
The array with the hyper parameter values.
886-
fun_control (dict):
887-
The function control dictionary.
888-
889-
Returns:
890-
(dict):
891-
The config dictionary.
892-
893-
Examples:
894-
>>> from river.tree import HoeffdingAdaptiveTreeRegressor
895-
from spotriver.data.river_hyper_dict import RiverHyperDict
896-
fun_control = {}
897-
add_core_model_to_fun_control(core_model=HoeffdingAdaptiveTreeRegressor,
898-
fun_control=func_control,
899-
hyper_dict=RiverHyperDict,
900-
filename=None)
901-
X = np.array([0, 0, 0, 0, 0])
902-
get_one_config_from_X(X, fun_control)
903-
{'leaf_prediction': 'mean',
904-
'leaf_model': 'NBAdaptive',
905-
'splitter': 'HoeffdingAdaptiveTreeSplitter',
906-
'binary_split': 'info_gain',
907-
'stop_mem_management': False}
908-
"""
909-
var_dict = assign_values(X, fun_control["var_name"])
910-
config = return_conf_list_from_var_dict(var_dict, fun_control)[0]
911-
return config
912-
913-
914955
def get_one_sklearn_model_from_X(X, fun_control=None):
915956
"""Get one sklearn model from X.
916957

test/test_convert_keys.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
from spotpython.hyperparameters.values import convert_keys
3+
4+
def test_convert_keys():
5+
# Test case 1: Basic conversion to int and float should succeed
6+
d = {'a': '1', 'b': '2.0', 'c': '3.5'}
7+
var_type = ["int", "num", "float"]
8+
result = convert_keys(d, var_type)
9+
expected = {'a': 1, 'b': 2, 'c': 3.5}
10+
assert result == expected
11+
12+
# Test case 2: Conversion to int should raise an error for non-integer strings
13+
d = {'a': '1.5', 'b': '2', 'c': '3'}
14+
var_type = ["int", "int", "int"]
15+
with pytest.raises(ValueError, match="Invalid value for conversion at a: 1.5"):
16+
convert_keys(d, var_type)
17+
18+
# Test case 3: Conversion with all "num" type should succeed
19+
d = {'a': '1', 'b': '2.2', 'c': '3'}
20+
var_type = ["num", "num", "num"]
21+
result = convert_keys(d, var_type)
22+
expected = {'a': 1, 'b': 2.2, 'c': 3}
23+
assert result == expected
24+
25+
# Test case 4: Check for correct float conversion with "float" type
26+
d = {'a': '1.0', 'b': '2.5', 'c': '3.1'}
27+
var_type = ["float", "float", "float"]
28+
result = convert_keys(d, var_type)
29+
expected = {'a': 1.0, 'b': 2.5, 'c': 3.1}
30+
assert result == expected
31+
32+
# Test case 5: Handling strings that cannot be converted to numbers
33+
d = {'a': 'hello', 'b': '2', 'c': '3'}
34+
var_type = ["int", "float", "num"]
35+
with pytest.raises(ValueError, match="Invalid value for conversion at a: hello"):
36+
convert_keys(d, var_type)
37+
38+
if __name__ == "__main__":
39+
pytest.main()

0 commit comments

Comments
 (0)