From 29b42ca0518ecf0596837e8edc8602c1ae1a93ba Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Tue, 11 Jun 2024 10:04:04 +0200 Subject: [PATCH] Move add_parameter_noise to simulation package --- baybe/simulation/core.py | 2 +- baybe/simulation/utils.py | 61 +++++++++++++++++++++++++++++++++++++++ baybe/utils/dataframe.py | 60 ++------------------------------------ tests/conftest.py | 3 +- 4 files changed, 67 insertions(+), 59 deletions(-) create mode 100644 baybe/simulation/utils.py diff --git a/baybe/simulation/core.py b/baybe/simulation/core.py index 673ff8caf9..41810a57b4 100644 --- a/baybe/simulation/core.py +++ b/baybe/simulation/core.py @@ -15,8 +15,8 @@ from baybe.campaign import Campaign from baybe.exceptions import NotEnoughPointsLeftError, NothingToSimulateError from baybe.simulation.lookup import look_up_targets +from baybe.simulation.utils import add_parameter_noise from baybe.targets.enum import TargetMode -from baybe.utils.dataframe import add_parameter_noise from baybe.utils.numerical import DTypeFloatNumpy, closer_element, closest_element from baybe.utils.random import temporary_seed diff --git a/baybe/simulation/utils.py b/baybe/simulation/utils.py new file mode 100644 index 0000000000..a68da0a4ad --- /dev/null +++ b/baybe/simulation/utils.py @@ -0,0 +1,61 @@ +"""Simulation utilities.""" + +from __future__ import annotations + +from collections.abc import Iterable +from typing import TYPE_CHECKING, Literal + +import numpy as np +import pandas as pd + +if TYPE_CHECKING: + from baybe.parameters import Parameter + + +def add_parameter_noise( + data: pd.DataFrame, + parameters: Iterable[Parameter], + noise_type: Literal["absolute", "relative_percent"] = "absolute", + noise_level: float = 1.0, +) -> None: + """Apply uniform noise to the parameter values of a recommendation frame. + + The noise can be additive or multiplicative. + This can be used to simulate experimental noise or imperfect user input containing + numerical parameter values that differ from the recommendations. Note that the + dataframe is modified in-place, and that no new dataframe is returned. + + Args: + data: Output of the ``recommend`` function of a ``Campaign`` object, see + :func:`baybe.campaign.Campaign.recommend`. + parameters: The parameters for which the values shall be corrupted. + noise_type: Defines whether the noise should be additive or multiplicative. + noise_level: Level/magnitude of the noise. Must be provided as numerical value + for noise type ``absolute`` and as percentage for noise type + ``relative_percent``. + + Raises: + ValueError: If ``noise_type`` is neither ``absolute`` nor + ``relative_percent``. + """ + # Validate input + if noise_type not in ("relative_percent", "absolute"): + raise ValueError( + f"Parameter 'noise_type' was {noise_type} but must be either " + "'absolute' or 'relative_percent'." + ) + + for param in (p for p in parameters if p.is_numeric): + # Add selected noise type + if noise_type == "relative_percent": + data[param.name] *= np.random.uniform( + 1.0 - noise_level / 100.0, 1.0 + noise_level / 100.0, len(data) + ) + elif noise_type == "absolute": + data[param.name] += np.random.uniform(-noise_level, noise_level, len(data)) + + # Respect continuous intervals + if param.is_continuous: + data[param.name] = data[param.name].clip( + param.bounds.lower, param.bounds.upper + ) diff --git a/baybe/utils/dataframe.py b/baybe/utils/dataframe.py index 4e40212f6e..38014fe7f0 100644 --- a/baybe/utils/dataframe.py +++ b/baybe/utils/dataframe.py @@ -3,14 +3,9 @@ from __future__ import annotations import logging -from collections.abc import Iterable, Iterator, Sequence -from typing import ( - TYPE_CHECKING, - Literal, - overload, -) - -import numpy as np +from collections.abc import Iterator, Sequence +from typing import TYPE_CHECKING, overload + import pandas as pd from baybe.utils.numerical import DTypeFloatNumpy @@ -63,55 +58,6 @@ def to_tensor(*dfs: pd.DataFrame) -> Tensor | Iterator[Tensor]: return out -def add_parameter_noise( - data: pd.DataFrame, - parameters: Iterable[Parameter], - noise_type: Literal["absolute", "relative_percent"] = "absolute", - noise_level: float = 1.0, -) -> None: - """Apply uniform noise to the parameter values of a recommendation frame. - - The noise can be additive or multiplicative. - This can be used to simulate experimental noise or imperfect user input containing - numerical parameter values that differ from the recommendations. Note that the - dataframe is modified in-place, and that no new dataframe is returned. - - Args: - data: Output of the ``recommend`` function of a ``Campaign`` object, see - :func:`baybe.campaign.Campaign.recommend`. - parameters: The parameters for which the values shall be corrupted. - noise_type: Defines whether the noise should be additive or multiplicative. - noise_level: Level/magnitude of the noise. Must be provided as numerical value - for noise type ``absolute`` and as percentage for noise type - ``relative_percent``. - - Raises: - ValueError: If ``noise_type`` is neither ``absolute`` nor - ``relative_percent``. - """ - # Validate input - if noise_type not in ("relative_percent", "absolute"): - raise ValueError( - f"Parameter 'noise_type' was {noise_type} but must be either " - "'absolute' or 'relative_percent'." - ) - - for param in (p for p in parameters if p.is_numeric): - # Add selected noise type - if noise_type == "relative_percent": - data[param.name] *= np.random.uniform( - 1.0 - noise_level / 100.0, 1.0 + noise_level / 100.0, len(data) - ) - elif noise_type == "absolute": - data[param.name] += np.random.uniform(-noise_level, noise_level, len(data)) - - # Respect continuous intervals - if param.is_continuous: - data[param.name] = data[param.name].clip( - param.bounds.lower, param.bounds.upper - ) - - def df_drop_single_value_columns( df: pd.DataFrame, lst_exclude: list = None ) -> pd.DataFrame: diff --git a/tests/conftest.py b/tests/conftest.py index be7539c136..5e598b840b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -52,6 +52,7 @@ ) from baybe.recommenders.pure.nonpredictive.sampling import RandomRecommender from baybe.searchspace import SearchSpace +from baybe.simulation.utils import add_parameter_noise from baybe.surrogates import _ONNX_INSTALLED, GaussianProcessSurrogate from baybe.targets import NumericalTarget from baybe.telemetry import ( @@ -61,7 +62,7 @@ ) from baybe.utils.basic import hilberts_factory from baybe.utils.boolean import strtobool -from baybe.utils.dataframe import add_fake_results, add_parameter_noise +from baybe.utils.dataframe import add_fake_results try: import baybe.utils.chemistry # noqa: F401 # Tests if chem deps are available