From a6bb56accc24d9c4a38f670273e3f21bff004fd5 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Mon, 12 Feb 2024 14:20:31 +0100 Subject: [PATCH 01/20] Add util for plotting examples --- baybe/utils/plotting.py | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 baybe/utils/plotting.py diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py new file mode 100644 index 0000000000..5f34d4ce56 --- /dev/null +++ b/baybe/utils/plotting.py @@ -0,0 +1,63 @@ +"""Plotting utilities.""" + +import json +import os +import sys +import warnings +from pathlib import Path + +import matplotlib.pyplot as plt +import pandas as pd +import seaborn as sns + + +def create_plots(data: pd.DataFrame, name: str, **kwargs) -> None: + """Create plots from a given data frame and save them as a svg file. + + The plots will be saved in the location in which the file that calls this function + is being located. This method is intended to be used for plotting the results of the + examples, but can also be used for other plots. + + If the ``SMOKE_TEST`` variable is set, no plots are being created and this method + immediately returns. + + Using the ``BAYBE_MULTIVERSION_PLOTS`` environment variable, it is possible to + create plots for the light and dark version of the documentation. If this variable + is not set, a single file named `{name}_check.svg` is created. + + Note that it is necessary to provide keyword arguments for setting up the exact + plot. + + Args: + data: The data frame containing the data to be plotted. + name: The name of the plot that should be created. + **kwargs: Keyword arguments. Used for specifying the plot. + """ + # Check whether we immediately return due to just running a SMOKE_TEST + if "SMOKE_TEST" in os.environ: + return + + # File containing all the themes + themes = json.load(open(Path("plotting_themes.json"))) + # Environment variables for checking whether we want to have multiversion plots + BAYBE_MULTIVERSION_PLOTS = "BAYBE_MULTIVERSION_PLOTS" in os.environ + + # Choose either all available or just the `check` theme. + chosen_themes = themes if BAYBE_MULTIVERSION_PLOTS else ["check"] + for theme_name in chosen_themes: + # Extract and set the current theme + theme = themes[theme_name] + font_scale, rc_params = theme["font_scale"], theme["rc_params"] + sns.set_theme(style="ticks", font_scale=font_scale, rc=rc_params) + # Only if kwargs are being provided, a plot is actually created + if kwargs: + sns.lineplot(data=data, **kwargs) + path = Path(sys.path[0], f"{name}_{theme_name}.svg") + plt.savefig( + path, + format="svg", + transparent=True, + ) + plt.clf() + else: + warnings.warn("No keyword arguments were provided when plotting.") From 2c7da0d34ecbbabbe9c4019828d837fdd4a2c597 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 2 Feb 2024 17:50:41 +0100 Subject: [PATCH 02/20] Add settings for plotting in examples --- plotting_themes.json | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 plotting_themes.json diff --git a/plotting_themes.json b/plotting_themes.json new file mode 100644 index 0000000000..8162729967 --- /dev/null +++ b/plotting_themes.json @@ -0,0 +1,44 @@ +{ + "dark": { + "font_scale": 1.75, + "rc_params": { + "axes.edgecolor": "white", + "axes.labelcolor": "white", + "xtick.color": "white", + "ytick.color": "white", + "legend.framealpha": 0.3, + "text.color": "white", + "figure.figsize": [ + 24, + 8 + ], + "lines.marker": "o", + "lines.markersize": 10 + } + }, + "light": { + "font_scale": 1.75, + "rc_params": { + "figure.figsize": [ + 24, + 8 + ], + "legend.labelcolor": "black", + "text.color": "black", + "lines.marker": "o", + "lines.markersize": 10 + } + }, + "check": { + "font_scale": 1.75, + "rc_params": { + "figure.figsize": [ + 24, + 8 + ], + "legend.labelcolor": "black", + "lines.marker": "o", + "lines.markersize": 10 + } + } +} \ No newline at end of file From bbec5e6bcf9c09c7092d28ed4c18ecbe54286f8c Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 2 Feb 2024 17:52:03 +0100 Subject: [PATCH 03/20] Add SMOKE_TEST check --- examples/Backtesting/botorch_analytical.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/Backtesting/botorch_analytical.py b/examples/Backtesting/botorch_analytical.py index 645837d359..e9e96c59c0 100644 --- a/examples/Backtesting/botorch_analytical.py +++ b/examples/Backtesting/botorch_analytical.py @@ -13,6 +13,8 @@ ### Necessary imports for this example +import os + import matplotlib.pyplot as plt import numpy as np import seaborn as sns @@ -33,8 +35,11 @@ # For the full simulation, we need to define some additional parameters. # These are the number of Monte Carlo runs and the number of experiments to be conducted per run. -N_MC_ITERATIONS = 2 -N_DOE_ITERATIONS = 2 +SMOKE_TEST = "SMOKE_TEST" in os.environ + +N_MC_ITERATIONS = 2 if SMOKE_TEST else 30 +N_DOE_ITERATIONS = 2 if SMOKE_TEST else 15 +BATCH_SIZE = 1 if SMOKE_TEST else 3 ### Defining the test function @@ -119,7 +124,7 @@ results = simulate_scenarios( scenarios, WRAPPED_FUNCTION, - batch_size=3, + batch_size=BATCH_SIZE, n_doe_iterations=N_DOE_ITERATIONS, n_mc_iterations=N_MC_ITERATIONS, ) From 62ba0e940339ef552e6144bd0441bbcc158acbbe Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 2 Feb 2024 17:52:03 +0100 Subject: [PATCH 04/20] Use create_plots utility for creating plots --- examples/Backtesting/botorch_analytical.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/Backtesting/botorch_analytical.py b/examples/Backtesting/botorch_analytical.py index e9e96c59c0..134e7093d2 100644 --- a/examples/Backtesting/botorch_analytical.py +++ b/examples/Backtesting/botorch_analytical.py @@ -15,9 +15,7 @@ import os -import matplotlib.pyplot as plt import numpy as np -import seaborn as sns from botorch.test_functions import Rastrigin from baybe import Campaign @@ -29,6 +27,7 @@ from baybe.strategies import TwoPhaseStrategy from baybe.targets import NumericalTarget from baybe.utils.botorch_wrapper import botorch_function_wrapper +from baybe.utils.plotting import create_plots ### Parameters for a full simulation loop @@ -129,8 +128,12 @@ n_mc_iterations=N_MC_ITERATIONS, ) -# The following lines plot the results and save the plot in run_analytical.png +# We use the plotting utility to create plots. -sns.lineplot(data=results, x="Num_Experiments", y="Target_CumBest", hue="Scenario") -plt.gcf().set_size_inches(24, 8) -plt.savefig("./run_analytical.png") +create_plots( + data=results, + name="botorch_analytical", + x="Num_Experiments", + y="Target_CumBest", + hue="Scenario", +) From 032f7d610cc2c82feb2f3ddb7b71ee8e9603ba87 Mon Sep 17 00:00:00 2001 From: Martin Fitzner Date: Mon, 5 Feb 2024 12:43:36 +0100 Subject: [PATCH 05/20] Activate SMOKE_SCREEN in relevant tox environments --- tox.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tox.ini b/tox.ini index 3f21262723..beb59affbf 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,8 @@ isolated_build = True description = Run PyTest with all extra functionality extras = test,chem,examples,simulation,onnx passenv = CI +setenv = + SMOKE_TEST = true commands = python --version pytest -p no:warnings --cov=baybe --durations=5 {posargs} @@ -15,6 +17,8 @@ commands = description = Run PyTest with core functionality extras = test passenv = CI +setenv = + SMOKE_TEST = true commands = python --version pytest -p no:warnings --cov=baybe --durations=5 {posargs} @@ -51,6 +55,8 @@ commands = description = Build documentation extras = docs passenv = BAYBE_DOCS_LINKCHECK_IGNORE +setenv = + SMOKE_TEST = true commands = python --version python docs/scripts/convert_code_to_documentation.py {posargs} \ No newline at end of file From 7d98dbe2e1766fb42514de3cf5192240c2a42a8d Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Mon, 12 Feb 2024 14:38:40 +0100 Subject: [PATCH 06/20] Include stubs for seaborn --- mypy.ini | 2 +- pyproject.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy.ini b/mypy.ini index 1972c91542..ef9147e251 100644 --- a/mypy.ini +++ b/mypy.ini @@ -40,4 +40,4 @@ ignore_missing_imports = True ignore_missing_imports = True [mypy-mordred] -ignore_missing_imports = True +ignore_missing_imports = True \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8dde74272a..fdb2ae868b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,8 @@ mypy = [ "mypy>=1.6.1", "pandas-stubs>=2.0.3.230814", "funcy-stubs>=0.1.1", - "types-requests>=2.31.0.20240106" + "types-requests>=2.31.0.20240106", + "types-seaborn>=0.12.2" ] simulation = [ From 4552359ae54384cd4c339f30af8ff84c5de396f0 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Tue, 13 Feb 2024 15:30:08 +0100 Subject: [PATCH 07/20] Use paths and base_name for determining plot output location --- baybe/utils/plotting.py | 23 ++++++++++------------ examples/Backtesting/botorch_analytical.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py index 5f34d4ce56..709f2a2042 100644 --- a/baybe/utils/plotting.py +++ b/baybe/utils/plotting.py @@ -2,7 +2,6 @@ import json import os -import sys import warnings from pathlib import Path @@ -11,27 +10,25 @@ import seaborn as sns -def create_plots(data: pd.DataFrame, name: str, **kwargs) -> None: +def create_plots(data: pd.DataFrame, path: Path, base_name: str, **kwargs) -> None: """Create plots from a given data frame and save them as a svg file. - The plots will be saved in the location in which the file that calls this function - is being located. This method is intended to be used for plotting the results of the - examples, but can also be used for other plots. + The plots will be saved in the location specified by ``path``. + The attribute ``base_name`` is used to define the name of the outputs. If the ``SMOKE_TEST`` variable is set, no plots are being created and this method immediately returns. Using the ``BAYBE_MULTIVERSION_PLOTS`` environment variable, it is possible to create plots for the light and dark version of the documentation. If this variable - is not set, a single file named `{name}_check.svg` is created. - - Note that it is necessary to provide keyword arguments for setting up the exact - plot. + is not set, a single file named ``{base_name}_check.svg`` is created. Args: data: The data frame containing the data to be plotted. - name: The name of the plot that should be created. - **kwargs: Keyword arguments. Used for specifying the plot. + path: The path to the directory in which the plots should be saved. + base_name: The base name that is used for naming the output files. + **kwargs: Keyword arguments. They are directly passed to ``sns.lineplot`` and + are used for specifying the plot. """ # Check whether we immediately return due to just running a SMOKE_TEST if "SMOKE_TEST" in os.environ: @@ -52,9 +49,9 @@ def create_plots(data: pd.DataFrame, name: str, **kwargs) -> None: # Only if kwargs are being provided, a plot is actually created if kwargs: sns.lineplot(data=data, **kwargs) - path = Path(sys.path[0], f"{name}_{theme_name}.svg") + output_path = Path(path, f"{base_name}_{theme_name}.svg") plt.savefig( - path, + output_path, format="svg", transparent=True, ) diff --git a/examples/Backtesting/botorch_analytical.py b/examples/Backtesting/botorch_analytical.py index 134e7093d2..694b50863e 100644 --- a/examples/Backtesting/botorch_analytical.py +++ b/examples/Backtesting/botorch_analytical.py @@ -14,6 +14,8 @@ ### Necessary imports for this example import os +import sys +from pathlib import Path import numpy as np from botorch.test_functions import Rastrigin @@ -130,9 +132,16 @@ # We use the plotting utility to create plots. +# ```{note} +# We cannot use `__file__` here since we convert these examples to jupyter notebooks and +# these do not have a `__file__` attribute. +# ``` + +path = Path(sys.path[0]) create_plots( data=results, - name="botorch_analytical", + path=path, + base_name="botorch_analytical", x="Num_Experiments", y="Target_CumBest", hue="Scenario", From 6a8b95c1502c01891a2fc79886b18b6f4e842f98 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Tue, 13 Feb 2024 16:03:22 +0100 Subject: [PATCH 08/20] Shorten comments and descriptions --- examples/Backtesting/botorch_analytical.py | 42 +++++----------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/examples/Backtesting/botorch_analytical.py b/examples/Backtesting/botorch_analytical.py index 694b50863e..fb7cfd3135 100644 --- a/examples/Backtesting/botorch_analytical.py +++ b/examples/Backtesting/botorch_analytical.py @@ -1,8 +1,6 @@ -## Example for full simulation loop using a BoTorch test function +## Simulation loop using a BoTorch test function # This example shows a simulation loop for a single target with a BoTorch test function as lookup. -# That is, we perform several Monte Carlo runs with several iterations. -# In addition, we also store and display the results. # This example assumes some basic familiarity with using BayBE and how to use BoTorch test # functions in discrete searchspaces. @@ -11,7 +9,7 @@ # 2. [`discrete_space`](./../Searchspaces/discrete_space.md) for details on using a # BoTorch test function. -### Necessary imports for this example +### Imports import os import sys @@ -23,24 +21,24 @@ from baybe import Campaign from baybe.objective import Objective from baybe.parameters import NumericalDiscreteParameter -from baybe.recommenders import RandomRecommender, SequentialGreedyRecommender +from baybe.recommenders import RandomRecommender from baybe.searchspace import SearchSpace from baybe.simulation import simulate_scenarios -from baybe.strategies import TwoPhaseStrategy from baybe.targets import NumericalTarget from baybe.utils.botorch_wrapper import botorch_function_wrapper from baybe.utils.plotting import create_plots ### Parameters for a full simulation loop -# For the full simulation, we need to define some additional parameters. -# These are the number of Monte Carlo runs and the number of experiments to be conducted per run. +# For the full simulation, we need to define the number of Monte Carlo runs +# and the number of experiments to be conducted per run. SMOKE_TEST = "SMOKE_TEST" in os.environ N_MC_ITERATIONS = 2 if SMOKE_TEST else 30 N_DOE_ITERATIONS = 2 if SMOKE_TEST else 15 BATCH_SIZE = 1 if SMOKE_TEST else 3 +POINTS_PER_DIM = 10 ### Defining the test function @@ -65,10 +63,6 @@ ### Creating the searchspace and the objective -# The parameter `POINTS_PER_DIM` controls the number of points per dimension. -# Note that the searchspace will have `POINTS_PER_DIM**DIMENSION` many points. - -POINTS_PER_DIM = 10 parameters = [ NumericalDiscreteParameter( name=f"x_{k+1}", @@ -89,34 +83,21 @@ mode="SINGLE", targets=[NumericalTarget(name="Target", mode="MIN")] ) -### Constructing campaigns for the simulation loop - -# To simplify adjusting the example for other strategies, we construct some strategy objects. -# For details on strategy objects, we refer to [`strategies`](./../Basics/strategies.md). - -seq_greedy_EI_strategy = TwoPhaseStrategy( - recommender=SequentialGreedyRecommender(acquisition_function_cls="qEI"), -) -random_strategy = TwoPhaseStrategy(recommender=RandomRecommender()) - -# We now create one campaign per strategy. +### Constructing campaigns seq_greedy_EI_campaign = Campaign( searchspace=searchspace, - strategy=seq_greedy_EI_strategy, objective=objective, ) random_campaign = Campaign( searchspace=searchspace, - strategy=random_strategy, + strategy=RandomRecommender(), objective=objective, ) ### Performing the simulation loop -# We can now use the `simulate_scenarios` function to simulate a full experiment. -# Note that this function enables to run multiple scenarios by a single function call. -# For this, it is necessary to define a dictionary mapping scenario names to campaigns. +# We use [simulate_scenarios](baybe.simulation.simulate_scenarios) to simulate a full experiment. scenarios = { "Sequential greedy EI": seq_greedy_EI_campaign, @@ -132,11 +113,6 @@ # We use the plotting utility to create plots. -# ```{note} -# We cannot use `__file__` here since we convert these examples to jupyter notebooks and -# these do not have a `__file__` attribute. -# ``` - path = Path(sys.path[0]) create_plots( data=results, From 66e009df2767c8934c8253b863f0ee8ce6e09a80 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Wed, 14 Feb 2024 15:28:57 +0100 Subject: [PATCH 09/20] Improve search for plotting_themes and include fallback scheme --- baybe/utils/plotting.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py index 709f2a2042..55c60edb86 100644 --- a/baybe/utils/plotting.py +++ b/baybe/utils/plotting.py @@ -2,6 +2,7 @@ import json import os +import sys import warnings from pathlib import Path @@ -34,8 +35,26 @@ def create_plots(data: pd.DataFrame, path: Path, base_name: str, **kwargs) -> No if "SMOKE_TEST" in os.environ: return - # File containing all the themes - themes = json.load(open(Path("plotting_themes.json"))) + # First, we see if we happen to find the plotting themes in the current folder. + # This is e.g. the case if we convert the file to a jupyter notebook. + try: + themes = json.load(open("plotting_themes.json")) + except FileNotFoundError: + # Try to find the plotting themes by backtracking + # Get the absolute path of the current script + script_path = Path(sys.argv[0]).resolve() + # Backtrack until either the "baybe" folder is found or backtracking is no + # longer possible + while not script_path.name == "baybe" and script_path != script_path.parent: + script_path = script_path.parent + if script_path == script_path.parent: + warnings.warn("No themes for plotting found. A default theme is used.") + themes = {"check": {"font_scale": 1.75, "rc_params": {}}} + else: + # Open the file containing all the themes + # This currently still assumes that the file is in the repo folder + themes = json.load(open(script_path / "plotting_themes.json")) + # Environment variables for checking whether we want to have multiversion plots BAYBE_MULTIVERSION_PLOTS = "BAYBE_MULTIVERSION_PLOTS" in os.environ From 8f53931fe28bfcec263281339f6aeb151cb15b38 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Thu, 15 Feb 2024 10:53:43 +0100 Subject: [PATCH 10/20] Exlcude utils/plotting.py from coverage --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..45dd9ef6a4 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = baybe/utils/plotting.py \ No newline at end of file From fd2a463fa30ceafdeab1c71eabe4e6e1b39feb12 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Thu, 15 Feb 2024 15:01:01 +0100 Subject: [PATCH 11/20] Remove multiversion environment variable --- baybe/utils/plotting.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py index 55c60edb86..8323779ae5 100644 --- a/baybe/utils/plotting.py +++ b/baybe/utils/plotting.py @@ -20,9 +20,9 @@ def create_plots(data: pd.DataFrame, path: Path, base_name: str, **kwargs) -> No If the ``SMOKE_TEST`` variable is set, no plots are being created and this method immediately returns. - Using the ``BAYBE_MULTIVERSION_PLOTS`` environment variable, it is possible to - create plots for the light and dark version of the documentation. If this variable - is not set, a single file named ``{base_name}_check.svg`` is created. + The function attempts to read the predefined themes from ``plotting_themes.json``. + For each theme it finds, a file ``{base_name}_{theme}.svg`` is being created. + If the file cannot be found, a single fallback theme is used. Args: data: The data frame containing the data to be plotted. @@ -55,12 +55,7 @@ def create_plots(data: pd.DataFrame, path: Path, base_name: str, **kwargs) -> No # This currently still assumes that the file is in the repo folder themes = json.load(open(script_path / "plotting_themes.json")) - # Environment variables for checking whether we want to have multiversion plots - BAYBE_MULTIVERSION_PLOTS = "BAYBE_MULTIVERSION_PLOTS" in os.environ - - # Choose either all available or just the `check` theme. - chosen_themes = themes if BAYBE_MULTIVERSION_PLOTS else ["check"] - for theme_name in chosen_themes: + for theme_name in themes: # Extract and set the current theme theme = themes[theme_name] font_scale, rc_params = theme["font_scale"], theme["rc_params"] From 50f0d9d7aa62ec413ca4a29a203dc04af28ee1e1 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Thu, 15 Feb 2024 15:01:45 +0100 Subject: [PATCH 12/20] Rename function to create_example_plots --- baybe/utils/plotting.py | 4 +++- examples/Backtesting/botorch_analytical.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py index 8323779ae5..0f55d56221 100644 --- a/baybe/utils/plotting.py +++ b/baybe/utils/plotting.py @@ -11,7 +11,9 @@ import seaborn as sns -def create_plots(data: pd.DataFrame, path: Path, base_name: str, **kwargs) -> None: +def create_example_plots( + data: pd.DataFrame, path: Path, base_name: str, **kwargs +) -> None: """Create plots from a given data frame and save them as a svg file. The plots will be saved in the location specified by ``path``. diff --git a/examples/Backtesting/botorch_analytical.py b/examples/Backtesting/botorch_analytical.py index fb7cfd3135..cfc0418368 100644 --- a/examples/Backtesting/botorch_analytical.py +++ b/examples/Backtesting/botorch_analytical.py @@ -26,7 +26,7 @@ from baybe.simulation import simulate_scenarios from baybe.targets import NumericalTarget from baybe.utils.botorch_wrapper import botorch_function_wrapper -from baybe.utils.plotting import create_plots +from baybe.utils.plotting import create_example_plots ### Parameters for a full simulation loop @@ -114,7 +114,7 @@ # We use the plotting utility to create plots. path = Path(sys.path[0]) -create_plots( +create_example_plots( data=results, path=path, base_name="botorch_analytical", From 1327d905e3d870e7cd92a5716b2697734e874045 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 16 Feb 2024 10:18:57 +0100 Subject: [PATCH 13/20] Full rework of plotting utility --- baybe/utils/plotting.py | 128 +++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py index 0f55d56221..22ef5c2fe6 100644 --- a/baybe/utils/plotting.py +++ b/baybe/utils/plotting.py @@ -7,14 +7,15 @@ from pathlib import Path import matplotlib.pyplot as plt -import pandas as pd -import seaborn as sns +from matplotlib.axes import Axes def create_example_plots( - data: pd.DataFrame, path: Path, base_name: str, **kwargs + ax: Axes, + path: Path, + base_name: str, ) -> None: - """Create plots from a given data frame and save them as a svg file. + """Create plots from an Axes object and save them as a svg file. The plots will be saved in the location specified by ``path``. The attribute ``base_name`` is used to define the name of the outputs. @@ -24,53 +25,100 @@ def create_example_plots( The function attempts to read the predefined themes from ``plotting_themes.json``. For each theme it finds, a file ``{base_name}_{theme}.svg`` is being created. - If the file cannot be found, a single fallback theme is used. + If the file cannot be found, if the JSON cannot be loaded or if the JSON is not well + configured, a fallback theme is used. Args: - data: The data frame containing the data to be plotted. + ax: The Axes object containing the figure that should be plotted. path: The path to the directory in which the plots should be saved. base_name: The base name that is used for naming the output files. - **kwargs: Keyword arguments. They are directly passed to ``sns.lineplot`` and - are used for specifying the plot. """ # Check whether we immediately return due to just running a SMOKE_TEST if "SMOKE_TEST" in os.environ: return - # First, we see if we happen to find the plotting themes in the current folder. - # This is e.g. the case if we convert the file to a jupyter notebook. - try: - themes = json.load(open("plotting_themes.json")) - except FileNotFoundError: - # Try to find the plotting themes by backtracking - # Get the absolute path of the current script - script_path = Path(sys.argv[0]).resolve() - # Backtrack until either the "baybe" folder is found or backtracking is no - # longer possible - while not script_path.name == "baybe" and script_path != script_path.parent: - script_path = script_path.parent - if script_path == script_path.parent: - warnings.warn("No themes for plotting found. A default theme is used.") - themes = {"check": {"font_scale": 1.75, "rc_params": {}}} - else: - # Open the file containing all the themes - # This currently still assumes that the file is in the repo folder + # Define a fallback theme in case no configuration is found + fallback = { + "color": "black", + "figsize": (24, 8), + "fontsize": 22, + "framealpha": 0.3, + } + + # Try to find the plotting themes by backtracking + # Get the absolute path of the current script + script_path = Path(sys.path[0]).resolve() + while ( + not Path(script_path / "plotting_themes.json").is_file() + and script_path != script_path.parent + ): + script_path = script_path.parent + if script_path == script_path.parent: + warnings.warn("No themes for plotting found. A fallback theme is used.") + themes = {"fallback": fallback} + else: + # Open the file containing all the themes + # If we reach this point, we know that the file exists, so we try to load it. + # If the file is no proper json, the fallback theme is used. + try: themes = json.load(open(script_path / "plotting_themes.json")) + except json.JSONDecodeError: + warnings.warn( + "The JSON containing the themes could not be loaded." + "A fallback theme is used.", + UserWarning, + ) + themes = {"fallback": fallback} for theme_name in themes: - # Extract and set the current theme - theme = themes[theme_name] - font_scale, rc_params = theme["font_scale"], theme["rc_params"] - sns.set_theme(style="ticks", font_scale=font_scale, rc=rc_params) - # Only if kwargs are being provided, a plot is actually created - if kwargs: - sns.lineplot(data=data, **kwargs) - output_path = Path(path, f"{base_name}_{theme_name}.svg") - plt.savefig( - output_path, - format="svg", - transparent=True, + # Get all of the values from the themes + # TODO This can probably be generalized and improved later on such that the + # keys fit the rc_params of matplotlib + # TODO We might want to add a generalization here + necessary_keys = ("color", "figsize", "fontsize", "framealpha") + if not all(key in themes[theme_name] for key in necessary_keys): + warnings.warn( + "Provided theme does not contain the necessary keys." + "Using a fallback theme instead.", + UserWarning, ) - plt.clf() + current_theme = fallback else: - warnings.warn("No keyword arguments were provided when plotting.") + current_theme = themes[theme_name] + color = current_theme["color"] + figsize = current_theme["figsize"] + fontsize = current_theme["fontsize"] + framealpha = current_theme["framealpha"] + + # Adjust the axes of the plot + for key in ax.spines.keys(): + ax.spines[key].set_color(color) + ax.xaxis.label.set_color(color) + ax.xaxis.label.set_fontsize(fontsize) + ax.yaxis.label.set_color(color) + ax.yaxis.label.set_fontsize(fontsize) + + # Adjust the size of the ax + ax.figure.set_size_inches(*figsize) + + # Adjust the labels + for label in ax.get_xticklabels() + ax.get_yticklabels(): + label.set_color(color) + label.set_fontsize(fontsize) + + # Adjust the legend + legend = ax.get_legend() + legend.get_frame().set_alpha(framealpha) + legend.get_title().set_color(color) + legend.get_title().set_fontsize(fontsize) + for text in legend.get_texts(): + text.set_fontsize(fontsize) + text.set_color(color) + + output_path = Path(path, f"{base_name}_{theme_name}.svg") + ax.figure.savefig( + output_path, + format="svg", + transparent=True, + ) + plt.close() From 15343aaf261a9e15302d53750ed5543955338b11 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 16 Feb 2024 10:19:35 +0100 Subject: [PATCH 14/20] Adjust to new signature of plotting utility --- examples/Backtesting/botorch_analytical.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/Backtesting/botorch_analytical.py b/examples/Backtesting/botorch_analytical.py index cfc0418368..c8b1381fc5 100644 --- a/examples/Backtesting/botorch_analytical.py +++ b/examples/Backtesting/botorch_analytical.py @@ -16,6 +16,7 @@ from pathlib import Path import numpy as np +import seaborn as sns from botorch.test_functions import Rastrigin from baybe import Campaign @@ -114,11 +115,16 @@ # We use the plotting utility to create plots. path = Path(sys.path[0]) -create_example_plots( +ax = sns.lineplot( data=results, - path=path, - base_name="botorch_analytical", + marker="o", + markersize=10, x="Num_Experiments", y="Target_CumBest", hue="Scenario", ) +create_example_plots( + ax=ax, + path=path, + base_name="botorch_analytical", +) From 8fbc639df26c868e77214a640c910fe0ffca5764 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 16 Feb 2024 10:21:23 +0100 Subject: [PATCH 15/20] Move plotting_themes.json into examples folder --- examples/plotting_themes.json | 20 ++++++++++++++++ plotting_themes.json | 44 ----------------------------------- 2 files changed, 20 insertions(+), 44 deletions(-) create mode 100644 examples/plotting_themes.json delete mode 100644 plotting_themes.json diff --git a/examples/plotting_themes.json b/examples/plotting_themes.json new file mode 100644 index 0000000000..1dece5ee4b --- /dev/null +++ b/examples/plotting_themes.json @@ -0,0 +1,20 @@ +{ + "dark": { + "color": "white", + "figsize": [ + 24, + 8 + ], + "fontsize": 22, + "framealpha": 0.3 + }, + "light": { + "color": "black", + "figsize": [ + 24, + 8 + ], + "fontsize": 22, + "framealpha": 0.3 + } +} \ No newline at end of file diff --git a/plotting_themes.json b/plotting_themes.json deleted file mode 100644 index 8162729967..0000000000 --- a/plotting_themes.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "dark": { - "font_scale": 1.75, - "rc_params": { - "axes.edgecolor": "white", - "axes.labelcolor": "white", - "xtick.color": "white", - "ytick.color": "white", - "legend.framealpha": 0.3, - "text.color": "white", - "figure.figsize": [ - 24, - 8 - ], - "lines.marker": "o", - "lines.markersize": 10 - } - }, - "light": { - "font_scale": 1.75, - "rc_params": { - "figure.figsize": [ - 24, - 8 - ], - "legend.labelcolor": "black", - "text.color": "black", - "lines.marker": "o", - "lines.markersize": 10 - } - }, - "check": { - "font_scale": 1.75, - "rc_params": { - "figure.figsize": [ - 24, - 8 - ], - "legend.labelcolor": "black", - "lines.marker": "o", - "lines.markersize": 10 - } - } -} \ No newline at end of file From 732f822092942155010c4ff01b622de8f6ee5ff8 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 2 Feb 2024 17:52:02 +0100 Subject: [PATCH 16/20] Enable plotting of dark, light and default plots in examples --- docs/scripts/utils.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/scripts/utils.py b/docs/scripts/utils.py index aa54034ef2..83e8dd18ca 100644 --- a/docs/scripts/utils.py +++ b/docs/scripts/utils.py @@ -110,6 +110,7 @@ def create_example_documentation(example_dest_dir: str, ignore_examples: bool): # Include the name of the file to the toctree # Format it by replacing underscores and capitalizing the words file_name = file.stem + formatted = " ".join(word.capitalize() for word in file_name.split("_")) # Remove duplicate "constraints" for the files in the constraints folder. if "Constraints" in folder_name and "Constraints" in formatted: @@ -162,7 +163,24 @@ def create_example_documentation(example_dest_dir: str, ignore_examples: bool): with open(markdown_path, "r", encoding="UTF-8") as markdown_file: lines = markdown_file.readlines() + # Delete lines we do not want to have in our documentation + lines = [line for line in lines if "![svg]" not in line] lines = [line for line in lines if "![png]" not in line] + lines = [line for line in lines if "
Date: Wed, 7 Feb 2024 10:41:32 +0100 Subject: [PATCH 17/20] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a397314067..644a0e6597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - Subpackages for the available recommender types +- Multi-style plotting capabilities for generated example plots +- JSON file for plotting themes +- Smoke testing in relevant tox environments ### Changed - `Recommender`s now share their core logic via their base class From 7170a9fbac889d991e4cffd4f6b92268dca86ec3 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 16 Feb 2024 10:36:41 +0100 Subject: [PATCH 18/20] Add static svg files --- .../Backtesting/botorch_analytical_dark.svg | 1328 +++++++++++++++++ .../Backtesting/botorch_analytical_light.svg | 1328 +++++++++++++++++ 2 files changed, 2656 insertions(+) create mode 100644 examples/Backtesting/botorch_analytical_dark.svg create mode 100644 examples/Backtesting/botorch_analytical_light.svg diff --git a/examples/Backtesting/botorch_analytical_dark.svg b/examples/Backtesting/botorch_analytical_dark.svg new file mode 100644 index 0000000000..ac64e07c2f --- /dev/null +++ b/examples/Backtesting/botorch_analytical_dark.svg @@ -0,0 +1,1328 @@ + + + + + + + + 2024-02-16T10:29:16.552511 + image/svg+xml + + + Matplotlib v3.8.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Backtesting/botorch_analytical_light.svg b/examples/Backtesting/botorch_analytical_light.svg new file mode 100644 index 0000000000..63e04a5175 --- /dev/null +++ b/examples/Backtesting/botorch_analytical_light.svg @@ -0,0 +1,1328 @@ + + + + + + + + 2024-02-16T10:29:16.569568 + image/svg+xml + + + Matplotlib v3.8.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 176e8f9ccf5cf2bf5b4b6620c09500ba921ef745 Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 16 Feb 2024 16:36:23 +0100 Subject: [PATCH 19/20] Add type hints to loaded json fields --- baybe/utils/plotting.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py index 22ef5c2fe6..1b4671762e 100644 --- a/baybe/utils/plotting.py +++ b/baybe/utils/plotting.py @@ -5,6 +5,7 @@ import sys import warnings from pathlib import Path +from typing import Tuple import matplotlib.pyplot as plt from matplotlib.axes import Axes @@ -85,10 +86,10 @@ def create_example_plots( current_theme = fallback else: current_theme = themes[theme_name] - color = current_theme["color"] - figsize = current_theme["figsize"] - fontsize = current_theme["fontsize"] - framealpha = current_theme["framealpha"] + color: str = current_theme["color"] + figsize: Tuple[int, int] = current_theme["figsize"] + fontsize: int = current_theme["fontsize"] + framealpha: float = current_theme["framealpha"] # Adjust the axes of the plot for key in ax.spines.keys(): From 5de7c4258ac1e43cc2cf58b19b93d405888beaab Mon Sep 17 00:00:00 2001 From: "Alexander V. Hopp" Date: Fri, 16 Feb 2024 17:15:56 +0100 Subject: [PATCH 20/20] Fix mypy errors --- baybe/utils/plotting.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py index 1b4671762e..5c6e72035a 100644 --- a/baybe/utils/plotting.py +++ b/baybe/utils/plotting.py @@ -5,10 +5,11 @@ import sys import warnings from pathlib import Path -from typing import Tuple +from typing import Any, Dict, Tuple import matplotlib.pyplot as plt from matplotlib.axes import Axes +from matplotlib.figure import Figure def create_example_plots( @@ -39,7 +40,7 @@ def create_example_plots( return # Define a fallback theme in case no configuration is found - fallback = { + fallback: Dict[str, Any] = { "color": "black", "figsize": (24, 8), "fontsize": 22, @@ -100,7 +101,11 @@ def create_example_plots( ax.yaxis.label.set_fontsize(fontsize) # Adjust the size of the ax - ax.figure.set_size_inches(*figsize) + # mypy thinks that ax.figure might become None, hence the explicit ignore + if isinstance(ax.figure, Figure): + ax.figure.set_size_inches(*figsize) + else: + warnings.warn("Could not adjust size of plot due to it not being a Figure.") # Adjust the labels for label in ax.get_xticklabels() + ax.get_yticklabels(): @@ -117,9 +122,13 @@ def create_example_plots( text.set_color(color) output_path = Path(path, f"{base_name}_{theme_name}.svg") - ax.figure.savefig( - output_path, - format="svg", - transparent=True, - ) + # mypy thinks that ax.figure might become None, hence the explicit ignore + if isinstance(ax.figure, Figure): + ax.figure.savefig( + output_path, + format="svg", + transparent=True, + ) + else: + warnings.warn("Plots could not be saved.") plt.close()