diff --git a/python/xy/_figure.py b/python/xy/_figure.py index 4252f430..80ea37bc 100644 --- a/python/xy/_figure.py +++ b/python/xy/_figure.py @@ -1075,16 +1075,18 @@ def _zero_baseline_anchor(self, axis_id: str) -> Optional[str]: continue base = t.x0.values if axis == "x" else t.y0.values value = t.x1.values if axis == "x" else t.y1.values + # Ask the questions as masked predicates rather than compacting + # `base`/`value` down to their finite rows first: the compaction + # allocates and copies two full columns per axis per build, and + # every question here is a single "does any row violate this?". finite = np.isfinite(base) & np.isfinite(value) - if not np.any(finite): + if not finite.any(): continue - base = base[finite] - value = value[finite] - if not np.all(base == 0.0): + if (finite & (base != 0.0)).any(): continue - if np.all(value >= 0.0): + if not (finite & (value < 0.0)).any(): return "lo" - if np.all(value <= 0.0): + if not (finite & (value > 0.0)).any(): return "hi" return None diff --git a/python/xy/marks.py b/python/xy/marks.py index aad4d409..1db275ab 100644 --- a/python/xy/marks.py +++ b/python/xy/marks.py @@ -1001,8 +1001,21 @@ def _auto_cap_size(positions: np.ndarray) -> float: 0.25x the median adjacent spacing of the distinct finite positions along the cap's axis; 0.4 when fewer than two are distinct (no spacing exists). + + The positions an error bar carries are usually an ordered independent + variable, and `np.unique` sorts unconditionally — an O(N log N) pass over + the whole column to answer a question about adjacent gaps. One O(N) diff + both proves the column is already non-decreasing and yields those gaps + directly; only an out-of-order column pays for the sort. Same distinct + values in the same order either way, so the median is identical. """ - distinct = np.unique(positions[np.isfinite(positions)]) + finite = positions[np.isfinite(positions)] + if len(finite) >= 2: + gaps = np.diff(finite) + if (gaps >= 0.0).all(): + positive = gaps[gaps != 0.0] + return 0.25 * float(np.median(positive)) if len(positive) else 0.4 + distinct = np.unique(finite) if len(distinct) < 2: return 0.4 return 0.25 * float(np.median(np.diff(distinct)))