Coverage for sparkle/platform/latex.py: 34%
35 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-01 13:21 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-01 13:21 +0000
1"""Helper classes/method for LaTeX and bibTeX."""
2import math
4import numpy as np
5import pandas as pd
6import plotly
7import plotly.express as px
8import pylatex as pl
9import kaleido
11kaleido.get_chrome_sync() # Ensure chrome is available for Kaleido
14class AutoRef(pl.base_classes.CommandBase):
15 """AutoRef command for PyLateX."""
16 _latex_name = "autoref"
17 packages = [pl.Package("hyperref")]
20def comparison_plot(data_frame: pd.DataFrame,
21 title: str = None) -> plotly.graph_objects.Figure:
22 """Creates a comparison plot from the given data frame.
24 The first column is used for the x axis, the second for the y axis.
26 Args:
27 data_frame: The data frame with the data
28 x_label: The label for the x axis
29 y_label: The label for the y axis
30 title: The title of the plot
31 output_plot: The path where the plot should be written to
33 Returns:
34 The plot object
35 """
36 from scipy import stats
37 # Determine if data is log scale, linregress tells us how linear the data is
38 linregress = stats.linregress(
39 data_frame[data_frame.columns[0]].to_numpy(),
40 data_frame[data_frame.columns[1]].to_numpy())
41 log_scale = not (linregress.rvalue > 0.65 and linregress.pvalue < 0.05)
43 if log_scale and (data_frame < 0).any(axis=None):
44 # Log scale cannot deal with negative and zero values, set to smallest non zero
45 data_frame[data_frame < 0] = np.nextafter(0, 1)
47 # Maximum value should come from the objective?
48 min_value, max_value = data_frame.min(axis=None), data_frame.max(axis=None)
49 # Slightly more than min/max for interpretability
50 if log_scale: # Take next step on log scale
51 max_value = 10 ** math.ceil(math.log(max_value, 10))
52 plot_range = (min_value, max_value)
53 else: # Take previous/next step on linear scale
54 order_magnitude = math.ceil(math.log(max_value, 10))
55 next_step_max = math.ceil(
56 max_value / (10 ** (order_magnitude - 1))) * 10 ** (order_magnitude - 1)
57 plot_range = (0, next_step_max) if min_value > 0 else (min_value, next_step_max)
58 fig = px.scatter(data_frame=data_frame,
59 x=data_frame.columns[0], y=data_frame.columns[1],
60 range_x=plot_range, range_y=plot_range,
61 title=title, log_x=log_scale, log_y=log_scale,
62 width=1000, height=1000)
63 # Add dividing diagonal
64 fig.add_shape(type="line", x0=0, y0=0, x1=max_value, y1=max_value,
65 line=dict(color="grey", dash="dot", width=1))
66 # Add maximum lines
67 fig.add_shape(type="line", opacity=0.7,
68 x0=0, y0=max_value, x1=max_value, y1=max_value,
69 line=dict(color="red", width=1.5, dash="longdash"))
70 fig.add_shape(type="line", opacity=0.7,
71 x0=max_value, y0=0, x1=max_value, y1=max_value,
72 line=dict(color="red", width=0.5, dash="longdash"))
73 fig.update_traces(marker=dict(color="RoyalBlue", symbol="x"))
74 fig.update_layout(
75 plot_bgcolor="white",
76 autosize=False,
77 width=1000, height=1000
78 )
79 minor = dict(ticks="inside", ticklen=6, showgrid=True) if log_scale else None
80 # Tick every 10^(log(max)) / 10 for linear scale, log scale is resolved by plotly
81 dtick = 1 if log_scale else 10 ** (math.ceil(math.log(max_value, 10)) - 1)
82 fig.update_xaxes(
83 mirror=True,
84 tickmode="linear",
85 ticks="outside",
86 tick0=0,
87 minor=minor,
88 dtick=dtick,
89 showline=True,
90 linecolor="black",
91 gridcolor="lightgrey"
92 )
93 fig.update_yaxes(
94 mirror=True,
95 tickmode="linear",
96 ticks="outside",
97 tick0=0,
98 minor=minor,
99 dtick=dtick,
100 showline=True,
101 linecolor="black",
102 gridcolor="lightgrey"
103 )
104 return fig