@@ -1250,26 +1250,25 @@ def _on_create(evt):
12501250
12511251
12521252class DescriptiveStatsAction (PlotAction ):
1253- """Display per-series descriptive statistics in a Panel table.
1253+ """Display per-series descriptive statistics in a summary table.
12541254
1255- Computes for each selected series (after active transforms are applied):
1256- count, first/last timestamp, min, max, mean, std, skew,
1257- percentiles (5 / 25 / 50 / 75 / 95), and cumulative sum.
1258-
1259- Reuses :class:`PlotAction`'s threaded callback for progress-bar and
1260- tab management; only :meth:`render` is overridden.
1255+ Shows a Tabulator with one row per selected series containing:
1256+ count, date range (start/end), 5-number summary (min, p25, p50, p75, max),
1257+ mean, std, skew, and sum. Transforms are applied via
1258+ ``_process_curve_data`` before computing statistics so the numbers match
1259+ what the user sees on the plot.
12611260 """
12621261
12631262 def get_tab_label (self , tab_count : int ) -> str :
12641263 return f"S{ tab_count } "
12651264
12661265 def render (self , df , refs_and_data , manager ):
12671266 rows = []
1267+
12681268 for row , ref , data in refs_and_data :
12691269 if data is None or (hasattr (data , "empty" ) and data .empty ):
12701270 continue
12711271 try :
1272- # Apply active transforms so stats reflect what the user sees on the plot.
12731272 time_range = getattr (manager , "time_range" , None )
12741273 if hasattr (manager , "_process_curve_data" ):
12751274 data = manager ._process_curve_data (data , row , time_range )
@@ -1282,34 +1281,39 @@ def render(self, df, refs_and_data, manager):
12821281 rows .append ({
12831282 "series" : name ,
12841283 "count" : int (len (s )),
1285- "start" : str (s .index .min ())[:19 ],
1286- "end" : str (s .index .max ())[:19 ],
1287- "min" : round (float (s .min ()), 6 ),
1288- "max" : round (float (s .max ()), 6 ),
1289- "mean" : round (float (s .mean ()), 6 ),
1290- "std" : round (float (s .std ()), 6 ),
1291- "skew" : round (float (s .skew ()), 6 ),
1292- "p5" : round (float (s .quantile (0.05 )), 6 ),
1293- "p25" : round (float (s .quantile (0.25 )), 6 ),
1294- "p50" : round (float (s .quantile (0.50 )), 6 ),
1295- "p75" : round (float (s .quantile (0.75 )), 6 ),
1296- "p95" : round (float (s .quantile (0.95 )), 6 ),
1297- "sum" : round (float (s .sum ()), 6 ),
1284+ "start" : str (s .index .min ())[:10 ],
1285+ "end" : str (s .index .max ())[:10 ],
1286+ "min" : round (float (s .min ()), 4 ),
1287+ "p25" : round (float (s .quantile (0.25 )), 4 ),
1288+ "p50" : round (float (s .quantile (0.50 )), 4 ),
1289+ "p75" : round (float (s .quantile (0.75 )), 4 ),
1290+ "max" : round (float (s .max ()), 4 ),
1291+ "mean" : round (float (s .mean ()), 4 ),
1292+ "std" : round (float (s .std ()), 4 ),
1293+ "skew" : round (float (s .skew ()), 4 ),
1294+ "sum" : round (float (s .sum ()), 4 ),
12981295 })
12991296 except Exception as e :
13001297 logger .warning (
13011298 "DescriptiveStatsAction: stats failed for %s: %s" ,
13021299 row .get ("name" , "?" ), e ,
13031300 )
1301+
13041302 if not rows :
13051303 return pn .pane .Markdown ("*No data to summarise.*" )
1304+
13061305 stats_df = pd .DataFrame (rows ).set_index ("series" )
1307- return pn .widgets .Tabulator (
1306+
1307+ table = pn .widgets .Tabulator (
13081308 stats_df ,
13091309 sizing_mode = "stretch_width" ,
1310+ layout = "fit_columns" ,
13101311 show_index = True ,
1312+ disabled = True ,
1313+ theme = "materialize" ,
13111314 header_filters = False ,
13121315 )
1316+ return table
13131317
13141318
13151319class AddSourceFilesAction :
0 commit comments