1+ #!/usr/bin/env python3
2+
13import argparse
24from collections import defaultdict
35from pathlib import Path
68import numpy as np
79import pandas as pd
810
9- from codeclash .analysis .viz .utils import MODEL_TO_DISPLAY_NAME
11+ from codeclash .analysis .viz .utils import FONT_BOLD , FONT_REG , MODEL_TO_DISPLAY_NAME
1012
1113
1214class GroundingValidationPlotter :
1315 """Create a triple plot showing grounding, validation, and testing/analysis metrics."""
1416
1517 # Styling parameters
1618 title_fontsize = 16
17- title_pad = 10
19+ title_pad = 45
1820 legend_fontsize = 14
21+ legend_title_fontsize = 12
1922 label_fontsize = 14
20- ytick_label_fontsize = 14
21- xtick_label_fontsize = 12
23+ ytick_label_fontsize = 15
24+ xtick_label_fontsize = 14
2225 in_bar_number_fontsize = 12
2326 in_bar_number_fontweight = "bold"
2427 total_number_fontsize = 12
2528 total_number_fontweight = "bold"
2629
2730 # Spacing parameters
2831 bar_height = 0.8 # Height of each bar (higher = less space between bars)
29- figure_height_per_model = 0.8 # Figure height scaling factor per model
3032
3133 # Color parameters - each as list of (color, alpha) tuples
3234 # Plot 1: Grounding (logs, insights)
@@ -68,17 +70,16 @@ def __init__(self, df: pd.DataFrame):
6870 # Add hallucination category column
6971 self .df ["hal_cat1" ] = self .df .apply (self ._get_l_reason_breakdown , axis = 1 )
7072
71- # Sort models alphabetically by display name
73+ # Sort models alphabetically by display name (reversed)
7274 models = [m for m in df ["model_name" ].unique () if isinstance (m , str )]
7375 model_display_pairs = [(m , MODEL_TO_DISPLAY_NAME .get (m , m )) for m in models ]
74- model_display_pairs .sort (key = lambda x : x [1 ].lower ())
76+ model_display_pairs .sort (key = lambda x : x [1 ].lower (), reverse = True )
7577 self .models = [m for m , _ in model_display_pairs ]
7678 self .display_names = [d for _ , d in model_display_pairs ]
7779 self .n_models = len (self .models )
7880
7981 # Create figure with 3 subplots sharing y-axis
80- fig_height = self .n_models * self .figure_height_per_model
81- self .fig , self .axes = plt .subplots (1 , 3 , figsize = (13 , fig_height ), sharey = True )
82+ self .fig , self .axes = plt .subplots (1 , 3 , figsize = (12 , 4 ), sharey = True )
8283 self .y_positions = np .arange (self .n_models )
8384
8485 def _get_l_reason_breakdown (self , row ) -> str :
@@ -109,12 +110,13 @@ def _add_stacked_bar_labels(self, ax, stacked_values: list[list[float]], min_val
109110 x_pos = left [i ] + val / 2
110111 # Determine text color based on segment index or value
111112 text_color = "white" if segment_idx < len (stacked_values ) - 1 else "black"
113+ font_in_bar = FONT_BOLD .copy ()
114+ font_in_bar .set_size (self .in_bar_number_fontsize )
112115 ax .text (
113116 x_pos ,
114117 self .y_positions [i ],
115118 f"{ val :.0f} %" ,
116- fontsize = self .in_bar_number_fontsize ,
117- fontweight = self .in_bar_number_fontweight ,
119+ fontproperties = font_in_bar ,
118120 ha = "center" ,
119121 va = "center" ,
120122 color = text_color ,
@@ -131,12 +133,13 @@ def _add_total_bar_labels(self, ax, totals: list[float], x_offset: float = 1):
131133 """
132134 for i , total in enumerate (totals ):
133135 if total > 0 :
136+ font_total = FONT_BOLD .copy ()
137+ font_total .set_size (self .total_number_fontsize )
134138 ax .text (
135139 total + x_offset ,
136140 self .y_positions [i ],
137141 f"{ total :.0f} %" ,
138- fontsize = self .total_number_fontsize ,
139- fontweight = self .total_number_fontweight ,
142+ fontproperties = font_total ,
140143 ha = "left" ,
141144 va = "center" ,
142145 )
@@ -194,18 +197,39 @@ def plot_grounding_analysis(self):
194197 totals = [logs + insights for logs , insights in zip (motivated_by_logs , motivated_by_insights_not_logs )]
195198 self ._add_total_bar_labels (ax , totals )
196199
197- ax .set_title (
198- "(a) Groundedness of edits" ,
199- fontsize = self .title_fontsize ,
200- fontweight = "bold" ,
201- pad = self .title_pad ,
200+ # Titles
201+ font_title = FONT_BOLD .copy ()
202+ font_title .set_size (self .title_fontsize )
203+ ax .set_title ("(a) Groundedness of edits" , fontproperties = font_title , pad = self .title_pad )
204+
205+ # Legend
206+ font_legend = FONT_REG .copy ()
207+ font_legend .set_size (self .legend_fontsize )
208+ leg = ax .legend (
209+ frameon = False ,
210+ prop = font_legend ,
211+ loc = "upper center" ,
212+ bbox_to_anchor = (0.5 , 1.25 ),
213+ borderaxespad = 0.0 ,
202214 )
203- ax .legend (loc = "upper right" , bbox_to_anchor = (1.2 , 1 ), fontsize = self .legend_fontsize , frameon = False )
215+ leg .set_in_layout (False )
216+
217+ # Y-axis
204218 ax .set_yticks (self .y_positions )
205- ax .set_yticklabels (self .display_names , fontsize = self .ytick_label_fontsize )
206- ax .set_xlabel ("Percentage of rounds" , fontsize = self .label_fontsize )
219+ font_ytick = FONT_REG .copy ()
220+ font_ytick .set_size (self .ytick_label_fontsize )
221+ ax .set_yticklabels (self .display_names , fontproperties = font_ytick )
222+
223+ # X-axis
224+ font_label = FONT_REG .copy ()
225+ font_label .set_size (self .label_fontsize )
226+ ax .set_xlabel ("Percentage of rounds" , fontproperties = font_label )
207227 ax .tick_params (axis = "y" , length = 0 )
208228 ax .tick_params (axis = "x" , labelsize = self .xtick_label_fontsize )
229+ font_xtick = FONT_REG .copy ()
230+ font_xtick .set_size (self .xtick_label_fontsize )
231+ for label in ax .get_xticklabels ():
232+ label .set_fontproperties (font_xtick )
209233 ax .xaxis .set_minor_locator (plt .MultipleLocator (5 ))
210234 ax .spines ["top" ].set_visible (False )
211235 ax .spines ["right" ].set_visible (False )
@@ -252,7 +276,7 @@ def plot_validation_feedback(self):
252276 self .y_positions ,
253277 tested_both ,
254278 self .bar_height ,
255- label = "Sim. + unittests " ,
279+ label = "Sim + tests " ,
256280 alpha = alpha_both ,
257281 color = color_both ,
258282 )
@@ -261,7 +285,7 @@ def plot_validation_feedback(self):
261285 tested_unit_only ,
262286 self .bar_height ,
263287 left = tested_both ,
264- label = "Unittests only " ,
288+ label = "Tests " ,
265289 alpha = alpha_unit ,
266290 color = color_unit ,
267291 )
@@ -270,7 +294,7 @@ def plot_validation_feedback(self):
270294 tested_sim_only ,
271295 self .bar_height ,
272296 left = np .array (tested_both ) + np .array (tested_unit_only ),
273- label = "Simulations only " ,
297+ label = "Sim. " ,
274298 alpha = alpha_sim ,
275299 color = color_sim ,
276300 )
@@ -280,16 +304,36 @@ def plot_validation_feedback(self):
280304 totals = [both + sim + unit for both , sim , unit in zip (tested_both , tested_sim_only , tested_unit_only )]
281305 self ._add_total_bar_labels (ax , totals )
282306
283- ax .set_title (
284- "(c) Validation of edits" ,
285- fontsize = self .title_fontsize ,
286- fontweight = "bold" ,
287- pad = self .title_pad ,
307+ # Titles
308+ font_title = FONT_BOLD .copy ()
309+ font_title .set_size (self .title_fontsize )
310+ ax .set_title ("(c) Validation of edits" , fontproperties = font_title , pad = self .title_pad )
311+
312+ # Legend
313+ font_legend = FONT_REG .copy ()
314+ font_legend .set_size (self .legend_fontsize )
315+ leg = ax .legend (
316+ frameon = False ,
317+ prop = font_legend ,
318+ loc = "upper center" ,
319+ bbox_to_anchor = (0.5 , 1.25 ),
320+ borderaxespad = 0.0 ,
321+ ncol = 2 ,
322+ columnspacing = 0.6 ,
323+ handletextpad = 0.4 ,
288324 )
289- ax .legend (loc = "upper right" , bbox_to_anchor = (1.15 , 1 ), fontsize = self .legend_fontsize , frameon = False )
290- ax .set_xlabel ("Percentage of rounds" , fontsize = self .label_fontsize )
325+ leg .set_in_layout (False )
326+
327+ # X-axis
328+ font_label = FONT_REG .copy ()
329+ font_label .set_size (self .label_fontsize )
330+ ax .set_xlabel ("Percentage of rounds" , fontproperties = font_label )
291331 ax .tick_params (axis = "y" , length = 0 )
292332 ax .tick_params (axis = "x" , labelsize = self .xtick_label_fontsize )
333+ font_xtick = FONT_REG .copy ()
334+ font_xtick .set_size (self .xtick_label_fontsize )
335+ for label in ax .get_xticklabels ():
336+ label .set_fontproperties (font_xtick )
293337 ax .xaxis .set_minor_locator (plt .MultipleLocator (5 ))
294338 ax .spines ["top" ].set_visible (False )
295339 ax .spines ["right" ].set_visible (False )
@@ -298,66 +342,82 @@ def plot_hallucination_categories(self):
298342 """Plot: Hallucinated/Unproven Loss Causality Claims."""
299343 ax = self .axes [1 ]
300344
301- category_order = ["logs/analysis" , "docs/tests/other" , "no source" ]
302-
303- # Collect data for each model
304- model_data = []
345+ # Combine categories into two: misinterpretation (logs/analysis + docs/tests/other) and no source
346+ misinterpretation = []
347+ no_source = []
305348 for model in self .models :
306349 model_df = self .df [self .df ["model_name" ] == model ]
307350 total_rounds = len (model_df )
308- category_values = []
309351
310- for category in category_order :
311- count = (model_df ["hal_cat1" ] == category ).sum ()
312- pct = (count / total_rounds * 100 ) if total_rounds > 0 else 0
313- category_values .append (pct )
352+ mis_count = ((model_df ["hal_cat1" ] == "logs/analysis" ) | (model_df ["hal_cat1" ] == "docs/tests/other" )).sum ()
353+ no_src_count = (model_df ["hal_cat1" ] == "no source" ).sum ()
314354
315- model_data .append (category_values )
355+ misinterpretation .append ((mis_count / total_rounds * 100 ) if total_rounds > 0 else 0 )
356+ no_source .append ((no_src_count / total_rounds * 100 ) if total_rounds > 0 else 0 )
316357
317358 # Plot stacked bars
318359 left = np .zeros (self .n_models )
319360
320- for cat_idx , category in enumerate (category_order ):
321- values = [model_data [model_idx ][cat_idx ] for model_idx in range (self .n_models )]
322- color , alpha = self .hallucination_colors [cat_idx ]
323-
324- ax .barh (
325- self .y_positions ,
326- values ,
327- self .bar_height ,
328- left = left ,
329- label = category ,
330- alpha = alpha ,
331- color = color ,
332- )
333- left = left + np .array (values )
361+ # Colors: use first for misinterpretation, third for no source
362+ color_mis , alpha_mis = self .hallucination_colors [0 ]
363+ color_no , alpha_no = self .hallucination_colors [2 ]
364+
365+ ax .barh (
366+ self .y_positions ,
367+ misinterpretation ,
368+ self .bar_height ,
369+ left = left ,
370+ label = "Misinterpreted source" ,
371+ alpha = alpha_mis ,
372+ color = color_mis ,
373+ )
374+ left = left + np .array (misinterpretation )
375+ ax .barh (
376+ self .y_positions ,
377+ no_source ,
378+ self .bar_height ,
379+ left = left ,
380+ label = "No source" ,
381+ alpha = alpha_no ,
382+ color = color_no ,
383+ )
334384
335385 # Add labels
336- stacked_values = [
337- [model_data [model_idx ][cat_idx ] for model_idx in range (self .n_models )]
338- for cat_idx in range (len (category_order ))
339- ]
386+ stacked_values = [misinterpretation , no_source ]
340387 self ._add_stacked_bar_labels (ax , stacked_values , min_value_to_show = 3.0 )
341- totals = [sum ( model_data [i ]) for i in range (self .n_models )]
388+ totals = [misinterpretation [i ] + no_source [ i ] for i in range (self .n_models )]
342389 self ._add_total_bar_labels (ax , totals )
343390
344- ax .set_title (
345- "(b) Hallucinated Loss Causality" ,
346- fontsize = self .title_fontsize ,
347- fontweight = "bold" ,
348- pad = self .title_pad ,
349- )
350- ax .legend (
351- loc = "center right" ,
352- bbox_to_anchor = (1.15 , 0.55 ),
353- fontsize = self .legend_fontsize ,
391+ # Titles
392+ font_title = FONT_BOLD .copy ()
393+ font_title .set_size (self .title_fontsize )
394+ ax .set_title ("(b) Hallucinated Loss Causality" , fontproperties = font_title , pad = self .title_pad )
395+
396+ # Legend
397+ font_legend = FONT_REG .copy ()
398+ font_legend .set_size (self .legend_fontsize )
399+ leg = ax .legend (
354400 frameon = False ,
355- title = "Hallucinated claims based on" ,
356- title_fontsize = 12 ,
401+ prop = font_legend ,
402+ loc = "upper center" ,
403+ bbox_to_anchor = (0.5 , 1.25 ),
404+ borderaxespad = 0.0 ,
405+ ncol = 1 ,
406+ columnspacing = 0.6 ,
407+ handletextpad = 0.4 ,
357408 )
358- ax .set_xlabel ("Percentage of rounds" , fontsize = self .label_fontsize )
409+ leg .set_in_layout (False )
410+
411+ # X-axis
412+ font_label = FONT_REG .copy ()
413+ font_label .set_size (self .label_fontsize )
414+ ax .set_xlabel ("Percentage of rounds" , fontproperties = font_label )
359415 ax .tick_params (axis = "y" , length = 0 )
360416 ax .tick_params (axis = "x" , labelsize = self .xtick_label_fontsize )
417+ font_xtick = FONT_REG .copy ()
418+ font_xtick .set_size (self .xtick_label_fontsize )
419+ for label in ax .get_xticklabels ():
420+ label .set_fontproperties (font_xtick )
361421 ax .xaxis .set_minor_locator (plt .MultipleLocator (5 ))
362422 ax .spines ["top" ].set_visible (False )
363423 ax .spines ["right" ].set_visible (False )
0 commit comments