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

1"""Helper classes/method for LaTeX and bibTeX.""" 

2import math 

3 

4import numpy as np 

5import pandas as pd 

6import plotly 

7import plotly.express as px 

8import pylatex as pl 

9import kaleido 

10 

11kaleido.get_chrome_sync() # Ensure chrome is available for Kaleido 

12 

13 

14class AutoRef(pl.base_classes.CommandBase): 

15 """AutoRef command for PyLateX.""" 

16 _latex_name = "autoref" 

17 packages = [pl.Package("hyperref")] 

18 

19 

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. 

23 

24 The first column is used for the x axis, the second for the y axis. 

25 

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 

32 

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) 

42 

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) 

46 

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