Skip to content

Commit 70b94a3

Browse files
author
miranov25
committed
PHASE 13.41.DF FIX2 — close all 5 panel-flagged P2/P3 items + FBY.23 lock
Items closed (per Sonet50 panel summary 2026-05-23): §2.1 (5/5 P2): FORWARDED_NAMES duplicate line-pairs — 4 tuples had share_x/y/across_figures inserted twice by FIX1 regex-replace. Removed; verified 6 single entries. Functional impact: zero (Python tuple in is idempotent). §2.2 (3/5 P2): 3D + auto_title=True was silent no-op (Sonnet54). Design: 3-case per-figure suptitle: user title= → '{title} ({figid_col} = {v})' auto_title=True → '{expr} [faceted by R × C × F = v]' default → '{figid_col} = {v}' (v1.6 baseline) Locked by FBY.23. §2.3 (4/5 P2): normalize+share_x silent consume — expanded _consumed inline doc comment from 7 to 18 lines documenting BEHAVIOR + WORKAROUND + FIX2 BACKL BEHAVIOR + (adv BEHAVIOR + WORKAROUND + FIX2 BACKL BEHAVIOR + (advget_supti BEHAVIOR + WORKAROUND + FIX2 BACKL with fallback for older versions). Used in FBY.22 + FBY.23. §2 §2 §2 §2 §2 §2 §2 §2 §2 §2 §2 §2 ch_inner_per_cell removed (per-cell auto_title is ALWAYS False â removed (per-cell auto_title is ALWAYS Fal Docstring updated. Tests: 945 → 946 (+1 §9 invariance: FBY.23).
1 parent b84576a commit 70b94a3

5 files changed

Lines changed: 141 additions & 30 deletions

File tree

UTILS/dfextensions/dfdraw/docs/CAPABILITY_MATRIX.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Capability Matrix — dfdraw
22

3-
**Generated:** 2026-05-23 10:28 UTC
3+
**Generated:** 2026-05-23 13:18 UTC
44
**Phase:** 13.15.DF
55
**Generator:** `scripts/generate_capability_matrix.py`
66
**Sources:** `tests/feature_taxonomy.py` + `tests/test_layer_classification.py`
@@ -14,8 +14,8 @@
1414
| 🧨 Broken | 0 | 0% |
1515
| 📋 Planned | 1 | 1% |
1616
| **Total features** | **107** | |
17-
| **Total proof tests** | **500** | |
18-
| **Invariance tests** | **271** | |
17+
| **Total proof tests** | **501** | |
18+
| **Invariance tests** | **272** | |
1919

2020
**Status key:**
2121
- ✅ Verified — has at least one invariance test (A ≡ B check)
@@ -170,7 +170,7 @@
170170
| | **HIST** | | |
171171
|| **HIST.cumulative** — hist() cumulative=True/-1/False — ROOT TH1::Draw('cumulative') equivalent. Three values: True (ascending CDF/ECDF), False (default, byte-identical backward compat), -1 (descending/survival, ROOT convention). matplotlib native cumulative= forwarded explicitly at 4 internal call sites (Phase 13.39 §2.2 lesson applied recursively: DFDraw.hist → draw_hist → _draw_hist_grouped → ax.hist; ALSO through _dispatch_faceted_render for facet_by composition). Composes with: norm='probability' (→ ECDF 0-1), group_by overlaid (per-group ECDFs), group_by stacked (CP2-1 regression lock for 3rd call site), facet_by (per-facet cumulative), histtype='step' (HEP-standard step ECDF). Correctness guard (M5): hist_errors+cumulative → NotImplementedError (Poisson per-bin errors are independent; cumulative counts are correlated). Vector dispatch [x,y] propagates cumulative correctly (Phase 13.16.DF FIX1 bug class lock) — Phase 13.40.DF | 10 | 0 |
172172
| | **FACET** | | |
173-
| ✅ | **FACET.list_grid** — facet_by accepts Union[str, List[str]] for 1D/2D/3D faceting. Convention LOCKED matching numpy/pandas (n_rows, n_cols, ...) shape: facet_by[0]=ROW (vertical within figure), facet_by[1]=COLUMN (horizontal within figure), facet_by[2]=FIGID (separate figures, one per value). facet_by[3+] raises NotImplementedError. 3D returns (List[Figure], List[axes_2d], List[stats_dict]) — DEVIATES from standard (fig, ax, stats) contract; documented prominently in inline help. New params: share_x/share_y ∈ {'all','row','col','none'} (within-figure axis sharing), share_across_figures: bool (3D global range lock). Per-plot-kind lock for share_across_figures (CP1-2): scatter locks x AND y; hist/profile locks x only (y auto-scales per figure to handle sparse-figID variance). New helpers: _normalize_facet_args, _to_mpl_share (symmetric {'all':True,'row':'row','col':'col','none':False} — v1.2 CP0-1 fix for Hard Constraint #3), _validate_share_axis_value, _resolve_facet_values (discrete or pd.cut/qcut Interval), _filter_facet_value (CP1-3 discrete vs binned), _compute_global_ranges. dfdraw is FIRST major plotting library with unified API where Nth faceting dimension generates separate figures (seaborn/ggplot2/plotly/altair all require manual loops). _validate_facet_by_binning guard for list input (v1.3 P1-A). Per-plot-kind dispatch: hist uses range= (matplotlib convention); profile uses range= which DFDraw.profile remaps to draw_profile's x_range= internally; scatter uses ax.set_xlim/set_ylim post-draw (no native range params); hist also locks ax.set_xlim post-draw (range= only locks bins, not axis xlim). Empty cell handling: '(no data)' diagnostic + stats={'n':0,'empty':True} — Phase 13.41.DF | 22 | 0 |
173+
| ✅ | **FACET.list_grid** — facet_by accepts Union[str, List[str]] for 1D/2D/3D faceting. Convention LOCKED matching numpy/pandas (n_rows, n_cols, ...) shape: facet_by[0]=ROW (vertical within figure), facet_by[1]=COLUMN (horizontal within figure), facet_by[2]=FIGID (separate figures, one per value). facet_by[3+] raises NotImplementedError. 3D returns (List[Figure], List[axes_2d], List[stats_dict]) — DEVIATES from standard (fig, ax, stats) contract; documented prominently in inline help. New params: share_x/share_y ∈ {'all','row','col','none'} (within-figure axis sharing), share_across_figures: bool (3D global range lock). Per-plot-kind lock for share_across_figures (CP1-2): scatter locks x AND y; hist/profile locks x only (y auto-scales per figure to handle sparse-figID variance). New helpers: _normalize_facet_args, _to_mpl_share (symmetric {'all':True,'row':'row','col':'col','none':False} — v1.2 CP0-1 fix for Hard Constraint #3), _validate_share_axis_value, _resolve_facet_values (discrete or pd.cut/qcut Interval), _filter_facet_value (CP1-3 discrete vs binned), _compute_global_ranges. dfdraw is FIRST major plotting library with unified API where Nth faceting dimension generates separate figures (seaborn/ggplot2/plotly/altair all require manual loops). _validate_facet_by_binning guard for list input (v1.3 P1-A). Per-plot-kind dispatch: hist uses range= (matplotlib convention); profile uses range= which DFDraw.profile remaps to draw_profile's x_range= internally; scatter uses ax.set_xlim/set_ylim post-draw (no native range params); hist also locks ax.set_xlim post-draw (range= only locks bins, not axis xlim). Empty cell handling: '(no data)' diagnostic + stats={'n':0,'empty':True} — Phase 13.41.DF | 23 | 0 |
174174

175175
---
176176

UTILS/dfextensions/dfdraw/drawer.py

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -821,8 +821,6 @@ def _auto_label(self, y_expr, x_expr=None):
821821
'facet_by_bins', 'facet_by_quantiles', # Phase 13.32.DF Sub-fix 3 (AD-79)
822822
# Phase 13.41.DF v1.6 + FIX1: N-D faceting axis-sharing controls
823823
'share_x', 'share_y', 'share_across_figures',
824-
# Phase 13.41.DF v1.6 + FIX1: N-D faceting axis-sharing controls
825-
'share_x', 'share_y', 'share_across_figures',
826824
# Phase 13.27.DF Commit 2 (Phase D): selection/weights vectors + per-curve label management
827825
'selection_vector', 'weights_vector',
828826
'selection_labels', 'weights_labels',
@@ -854,8 +852,6 @@ def _auto_label(self, y_expr, x_expr=None):
854852
'facet_by_bins', 'facet_by_quantiles', # Phase 13.32.DF Sub-fix 3 (AD-79)
855853
# Phase 13.41.DF v1.6 + FIX1: N-D faceting axis-sharing controls
856854
'share_x', 'share_y', 'share_across_figures',
857-
# Phase 13.41.DF v1.6 + FIX1: N-D faceting axis-sharing controls
858-
'share_x', 'share_y', 'share_across_figures',
859855
# Phase 13.27.DF Commit 2 (Phase D): selection/weights vectors + per-curve label management
860856
'selection_vector', 'weights_vector',
861857
'selection_labels', 'weights_labels',
@@ -899,8 +895,6 @@ def _auto_label(self, y_expr, x_expr=None):
899895
'facet_by_bins', 'facet_by_quantiles', # Phase 13.32.DF Sub-fix 3 (AD-79)
900896
# Phase 13.41.DF v1.6 + FIX1: N-D faceting axis-sharing controls
901897
'share_x', 'share_y', 'share_across_figures',
902-
# Phase 13.41.DF v1.6 + FIX1: N-D faceting axis-sharing controls
903-
'share_x', 'share_y', 'share_across_figures',
904898
'xerr', 'yerr', # Phase 13.38.DF: scatter error bars (column name or df.eval())
905899
'time_format', # Phase 13.39.DF: time-axis formatting (pre-conversion)
906900
# Phase 13.27.DF Commit 2 (Phase D): selection/weights vectors + per-curve label management
@@ -937,8 +931,6 @@ def _auto_label(self, y_expr, x_expr=None):
937931
'facet_by_bins', 'facet_by_quantiles', # Phase 13.32.DF Sub-fix 3 (AD-79)
938932
# Phase 13.41.DF v1.6 + FIX1: N-D faceting axis-sharing controls
939933
'share_x', 'share_y', 'share_across_figures',
940-
# Phase 13.41.DF v1.6 + FIX1: N-D faceting axis-sharing controls
941-
'share_x', 'share_y', 'share_across_figures',
942934
# Phase 13.27.DF Commit 2 (Phase D): selection/weights vectors + per-curve label management
943935
'selection_vector', 'weights_vector',
944936
'selection_labels', 'weights_labels',
@@ -3347,7 +3339,7 @@ def _dispatch_faceted_render(
33473339

33483340
def _dispatch_inner_per_cell(self, sub_df, x_expr, y_expr, plot_kind,
33493341
ax_ij, _lock_x_range, _lock_y_range,
3350-
_user_auto_title=False, **plot_kwargs):
3342+
**plot_kwargs):
33513343
"""Per-plot-kind inner dispatch within a 2D facet cell.
33523344
33533345
Phase 13.41.DF v1.4 CP1-1 — corrected per-function range params:
@@ -3359,8 +3351,10 @@ def _dispatch_inner_per_cell(self, sub_df, x_expr, y_expr, plot_kind,
33593351
_lock_x_range/_lock_y_range: internal cross-figure ranges; user-supplied
33603352
range (hist) / x_range (profile) come via plot_kwargs and take precedence.
33613353
3362-
_user_auto_title: Phase 13.41 FIX1 (Sonnet54 P2) — user's auto_title
3363-
choice; respected for hist/profile (scatter has no auto_title param).
3354+
Per-cell `auto_title` is ALWAYS False — cell titles are reserved for facet
3355+
labels (row_col=val, col_col=val). User's auto_title=True is honored at
3356+
the figure level (suptitle) in _dispatch_2d_facet (Phase 13.41 FIX1 +
3357+
FIX2 item 5: dead `_user_auto_title` param removed from this signature).
33643358
"""
33653359
# Build the y:x or just x expression for the inner call
33663360
if isinstance(y_expr, str) and y_expr:
@@ -3489,7 +3483,6 @@ def _dispatch_2d_facet(self, df, x_expr, y_expr, facet_list, bins_list,
34893483
stats_ij = self._dispatch_inner_per_cell(
34903484
sub_df, x_expr, y_expr, plot_kind, ax_ij,
34913485
_lock_x_range, _lock_y_range,
3492-
_user_auto_title=_user_auto_title,
34933486
**inner_kwargs)
34943487
stats_grid[(row_v, col_v)] = stats_ij
34953488

@@ -3547,18 +3540,39 @@ def _dispatch_3d_facet(self, df, x_expr, y_expr, facet_list, bins_list,
35473540
sub_df = _filter_facet_value(df, figid_col, figid_v,
35483541
bins_list[2], quantiles_list[2])
35493542

3543+
# Phase 13.41 FIX2 item 2 (Sonnet54 P2): in 3D mode, the 2D dispatch's
3544+
# auto_title suptitle would be overwritten by the figID label below.
3545+
# Don't forward _user_auto_title to 2D — handle the combined suptitle
3546+
# here so auto_title=True isn't a silent no-op in 3D.
35503547
fig, axes, stats = self._dispatch_2d_facet(
35513548
sub_df, x_expr, y_expr, facet_list[:2], bins_list[:2],
35523549
quantiles_list[:2], plot_kind,
35533550
share_x=share_x, share_y=share_y,
35543551
_lock_x_range=global_x_range, _lock_y_range=global_y_range,
3555-
_user_auto_title=_user_auto_title,
3552+
_user_auto_title=False, # 3D handles auto_title at fig-level (see below)
35563553
title=None, group_by=group_by, top_k=top_k,
35573554
quantiles=quantiles, quantile_mode=quantile_mode,
35583555
**plot_kwargs)
35593556

3560-
# Set per-figure title (figID value)
3561-
fig.suptitle(f"{figid_col} = {figid_v}", fontsize=12)
3557+
# Set per-figure title — 3 cases per Phase 13.41 FIX2 item 2 design:
3558+
# 1. user title= → "{user_title} ({figid_col} = {figid_v})"
3559+
# 2. auto_title=True → "{expr} [faceted by {r}×{c}×{f} = {figid_v}]"
3560+
# 3. default → "{figid_col} = {figid_v}" (Phase 13.41 v1.6)
3561+
if title:
3562+
fig.suptitle(f"{title} ({figid_col} = {figid_v})", fontsize=12)
3563+
elif _user_auto_title:
3564+
if isinstance(y_expr, str) and y_expr and x_expr:
3565+
_auto_expr = f"{y_expr} vs {x_expr}"
3566+
elif isinstance(y_expr, str) and y_expr:
3567+
_auto_expr = y_expr
3568+
else:
3569+
_auto_expr = x_expr or 'data'
3570+
fig.suptitle(
3571+
f"{_auto_expr} [faceted by {facet_list[0]} × {facet_list[1]} "
3572+
f"× {facet_list[2]} = {figid_v}]",
3573+
fontsize=11)
3574+
else:
3575+
fig.suptitle(f"{figid_col} = {figid_v}", fontsize=12)
35623576

35633577
figures.append(fig)
35643578
all_axes.append(axes)
@@ -4838,13 +4852,24 @@ def profile(
48384852
'selection_labels', 'weights_labels',
48394853
'selection_categorical', 'weights_categorical',
48404854
'delta_facet',
4841-
# Phase 13.41.DF FIX1.1: filter N-D axis-sharing controls
4842-
# from _passthrough sent to legacy normalize dispatchers
4843-
# (_render / _grouped / _faceted use their own GridSpec
4844-
# layout and don't accept share_x/y/across_figures). The
4845-
# K×2 normalize+facet grid pre-dates Phase 13.41's N-D
4846-
# faceting; future phase may add N-D support to normalize
4847-
# dispatcher. Until then, params silently ignored.
4855+
# ────────────────────────────────────────────────────────
4856+
# Phase 13.41.DF FIX1.1 + FIX2 item 3 (Sonnet51/52/54 P2):
4857+
# When normalize= AND facet_by= are BOTH set, routing goes
4858+
# to the legacy K×2 normalize+facet dispatcher (predates
4859+
# Phase 13.41 N-D faceting). That dispatcher uses its own
4860+
# GridSpec layout and does NOT honor share_x/share_y/
4861+
# share_across_figures. We filter these out of _passthrough
4862+
# so they don't reach _dispatch_normalize_faceted_render
4863+
# via **passthrough (which would silently swallow them
4864+
# anyway — but explicit filtering documents intent and
4865+
# avoids surprise in future maintenance).
4866+
#
4867+
# BEHAVIOR: d.profile(normalize=..., facet_by=['a','b'],
4868+
# share_x='row') → K×2 grid, share_x SILENTLY IGNORED.
4869+
# WORKAROUND: use normalize= or facet_by=List[str], not both.
4870+
# FIX2 BACKLOG: add N-D faceting support to normalize
4871+
# dispatcher (or raise NotImplementedError with hint).
4872+
# ────────────────────────────────────────────────────────
48484873
'share_x', 'share_y', 'share_across_figures',
48494874
}
48504875
_passthrough = {k: v for k, v in vector_kwargs.items()

UTILS/dfextensions/dfdraw/tests/feature_taxonomy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,8 @@
13591359
"test_phase_13_41_df_2d_faceting.py::TestFacetByListGrid::test_FBY_20_share_y_row_symmetry",
13601360
"test_phase_13_41_df_2d_faceting.py::TestFacetByListGrid::test_FBY_21_share_x_invalid_raises",
13611361
"test_phase_13_41_df_2d_faceting.py::TestFacetByListGrid::test_FBY_22_auto_title_suptitle_lock",
1362+
# Phase 13.41.DF FIX2 (1 additional regression lock)
1363+
"test_phase_13_41_df_2d_faceting.py::TestFacetByListGrid::test_FBY_23_3d_auto_title_combined_suptitle",
13621364
],
13631365
},
13641366
]

UTILS/dfextensions/dfdraw/tests/test_layer_classification.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@
348348
"test_phase_13_41_df_2d_faceting.py::TestFacetByListGrid::test_FBY_20_share_y_row_symmetry": "invariance",
349349
"test_phase_13_41_df_2d_faceting.py::TestFacetByListGrid::test_FBY_21_share_x_invalid_raises": "invariance",
350350
"test_phase_13_41_df_2d_faceting.py::TestFacetByListGrid::test_FBY_22_auto_title_suptitle_lock": "invariance",
351+
# Phase 13.41.DF FIX2 (1 additional regression lock)
352+
"test_phase_13_41_df_2d_faceting.py::TestFacetByListGrid::test_FBY_23_3d_auto_title_combined_suptitle": "invariance",
351353

352354
# Everything else defaults to "smoke"
353355
}

0 commit comments

Comments
 (0)