Skip to content

fix(histogram): a float column holding an infinity keeps a numeric histogram in xorq, pandas and polars (#1055) - #1069

Merged
paddymul merged 5 commits into
mainfrom
fix/1055-inf-histogram
Oct 6, 2026
Merged

paddymul merged 5 commits into
mainfrom
fix/1055-inf-histogram

Conversation

@paddymul

@paddymul paddymul commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

This PR fixes the xorq side (#1055) and, since #1072 was merged into this branch, the pandas and polars side. #1072 went in with only its test commit (b0c69d3); its fix commit is ac5a0cd here.

Problem

xorq: _numeric_histogram in xorq_stats_v2.py buckets with (x - min) / (max - min) * 10 cast to int64. A float column holding inf or -inf makes max - min infinite, so that row's bucket is NaN and the query fails with Can't cast value NaN to type Int64. histogram's default=[] swallows the error (#687), so the column shows no histogram. histogram_bins computes [nan, inf, inf, ...] from the same infinite range.

pandas and polars: histogram_series and pl_histogram_series take the 1% and 99% tails with np.quantile / Series.quantile over the raw column, then histogram the values between them. With an infinity in the column, numpy's interpolation can return NaN (inf - inf, or inf * 0, inside np.quantile). Every comparison against a NaN tail is False, so the middle is empty, histogram_args comes back empty, and the column falls to the categorical histogram (top value counts) with no histogram_bins, so color_map has nothing to read. Where the quantile is inf instead, the middle includes every finite value, the finite buckets stretch to the maximum, and the tail label reads infT–infT. Which one happens depends on where 0.99 * (n - 1) falls beside the infinity: with a single +inf, the quantile is NaN at 50 and 101 non-null values and inf at 99 and 100.

Change

xorq: when a float column's min or max is infinite, _numeric_histogram and histogram_bins call _finite_range, which runs one query for the min and max of the column's finite values. The histogram's bucket query drops the infinite rows, so the populations sum to 100% of the finite values, and histogram_bins spans the finite range. min and max still report the infinities. A column with no finite values, or one, still gets no histogram. A column with a finite range runs no extra query. The issue allowed either leaving the infinities out of the buckets or counting them in the end buckets. I left them out: an end bucket that includes inf would be labelled with a finite range it doesn't cover. histogram_bins now takes expr, execute and orig_col_name for that query, so the pipeline treats it as a query stat (_is_query_func) and caches its cell the way it caches histogram's.

pandas and polars: both functions take the tails over the finite values. The infinities fall outside them and land in the tail buckets, and the buckets of the finite values are the same with or without the infinities. The polars filter applies to float dtypes only, so integer and Decimal columns take the path they did before. A numeric column with 5 or fewer distinct values is still categorical (an infinity counts as a distinct value), and a column with no finite values still gets the categorical histogram.

fmt_num returns inf, -inf or nan for a non-finite bound, so a tail reads 198–inf and -inf<>1.5 instead of infT.

df_with_infinity no longer fails the warm-load check in test_stat_cache.py: the strict #1057 xfail on it XPASSed, so its entry comes out of _DDD_KNOWN_FAILURES, as that table's comment says to do. #1057 still applies to pl_df_with_temporal_edges.

New DDD frames: df_with_infinities_among_values and pl_df_with_infinities_among_values (101-row float columns where np.quantile is NaN, plus a control without infinities; the polars frame adds a real NaN and a null). They are on the docs DDD page, and test_stat_cache.py picks them up automatically.

Tests

xorq, TestHistogram in tests/unit/test_xorq_stats_v2.py, each for +inf, -inf and both: the histogram is non-empty and sums to 100% with min/max still reporting the infinities, and histogram_bins has 11 finite edges spanning the finite values. These failed on CI before the xorq fix (run 37506145362, Python / Test (Max Versions) (3.13): 6 failed, 1508 passed).

pandas (tests/unit/histogram_test.py) and polars (TestPlHistogram in tests/unit/test_pl_stats_v2.py):

  • a sweep over 5 infinity shapes (+inf, -inf, two -inf, both, 5 on each side) and 12 finite-value counts that straddle the NaN/inf boundary; polars also runs each alone, beside a NaN, and beside a null. Each asserts a numeric histogram (no categorical entries), 11 finite histogram_bins, and the same finite buckets as the column without the infinities
  • every DDD float column that holds an infinity beside more than 5 distinct finite values (ddd_float_columns_with_infinities in tests/unit/test_utils.py reads them from ddd_library, so frames added later are covered); the xorq test file runs the same columns
  • pandas float64, float32 and nullable Float64
  • tail buckets name the infinity, and fmt_tail_bucket formats infinite bounds as inf

These failed on CI before the pandas/polars fix (run 37513631803 on #1072's branch, and run 37513943690 on this PR after #1072's test commit merged: Python / Test (3.14), 240 failed, 997 passed; all 240 are the new tests). The fix commit adds guards that pass either way: columns with nothing to bucket, polars Float32, and the xorq DDD check. Locally the full unit suite passes on this branch (1940 passed, 17 skipped, 15 xfailed).

Not in this PR

Found while checking; each is separate from these fixes:

Closes #1055.

🤖 Generated with Claude Code

…1055)

One inf makes max - min infinite, every bucket NaN, and the int64 cast
fails; histogram's default=[] hides the error and histogram_bins comes
back as [nan, inf, inf, ...].

Covers +inf, -inf and both, for histogram and histogram_bins.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
)

When a float column's min or max is infinite, _numeric_histogram and
histogram_bins now look up the min and max of the finite values with one
small query and bucket over that range. The infinite rows are filtered
out of the bucket query, so the population sums to 100% of the finite
values. min and max still report the infinities. A column with no finite
values, or only one, still gets no histogram.

histogram_bins takes expr, execute and orig_col_name for that query, so
it is now a query stat. Columns with a finite range run no extra query.

df_with_infinity no longer fails the warm-load check in test_stat_cache.py
(the #1057 xfail was strict), so its entry comes out of
_DDD_KNOWN_FAILURES. #1057 still applies to pl_df_with_temporal_edges.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

📦 TestPyPI package published

pip install --index-strategy unsafe-best-match --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ buckaroo==0.15.9.dev37516214008

or with uv:

uv pip install --index-strategy unsafe-best-match --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ buckaroo==0.15.9.dev37516214008

MCP server for Claude Code

claude mcp add buckaroo-table -- uvx --from "buckaroo[mcp]==0.15.9.dev37516214008" --index-strategy unsafe-best-match --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ buckaroo-table

📖 Docs preview

🎨 Storybook preview

@paddymul
paddymul marked this pull request as ready for review October 6, 2026 18:13
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

paddymul and others added 2 commits October 6, 2026 14:46
…istogram in pandas and polars (#1072)

The pandas and polars histogram takes its 1% and 99% tails with
np.quantile / Series.quantile over the raw column. With an infinity in
it, numpy's interpolation can return NaN (inf - inf, or inf * 0), the
meat comes out empty, and the column drops to the categorical histogram
with no histogram_bins. Where the quantile is inf instead, the tail
labels read 'infT'. Which one depends on where 0.99 * (n - 1) falls
beside the infinity, e.g. NaN at 50 and 101 non-null values with one
+inf, inf at 99 and 100.

Tests, for pandas and polars:

- a sweep over 5 infinity shapes x 12 finite-value counts (and, for
  polars, alone / beside a NaN / beside a null): the histogram is numeric,
  histogram_bins is 11 finite edges, and the finite buckets equal those
  of the column without the infinities
- every DDD float column that holds an infinity beside more than 5
  distinct finite values, picked up automatically from ddd_library
- pandas float64, float32 and nullable Float64
- tail buckets name the infinity ('-inf...', '...-inf'), and
  fmt_tail_bucket formats infinite bounds as 'inf'

New DDD frames, df_with_infinities_among_values and its polars
counterpart: 101-row float columns where np.quantile is NaN, plus a
control without infinities. They also run through test_stat_cache's
warm-load checks.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
@paddymul
paddymul enabled auto-merge October 6, 2026 18:47
… infinity (pandas, polars)

histogram_series and pl_histogram_series took the 1% and 99% tails over
the raw column. With an infinity in it np.quantile can return NaN (inf -
inf inside its interpolation), which emptied the middle and dropped the
column to the categorical histogram; where it returned inf instead, the
finite buckets stretched to the maximum and the tail read 'infT'.

Tails are now taken over the finite values. The infinities fall outside
them, so they land in the tail buckets, and the buckets of the finite
values are the same with or without them. A column with no finite values
still gets the categorical histogram. Integer and Decimal columns take
the same path as before (polars guards on float dtypes).

fmt_num returns 'inf', '-inf' or 'nan' for a non-finite bound instead of
running it through the SI-prefix branch, so a tail reads '198–inf' and
'-inf<>1.5'.

Also adds guards that pass without the fix: columns with nothing to
bucket (only infinities, a constant beside an infinity), polars Float32,
and the DDD-driven check for xorq. The two new DDD frames are added to the
docs DDD page.

#1072 was merged into this branch with only its test commit (b0c69d3);
this is its fix commit, cherry-picked from 374333a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@paddymul paddymul changed the title fix(xorq): a float column holding an infinity keeps its histogram (#1055) fix(histogram): a float column holding an infinity keeps a numeric histogram in xorq, pandas and polars (#1055) Oct 6, 2026
@paddymul
paddymul added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit e88ace9 Oct 6, 2026
28 checks passed

This branch was successfully deployed

1 active deployment
testpypi — ac5a0cd0 Deployed Oct 6, 2026 by paddymul via Publish to TestPyPI #1806
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

xorq stats: a float column holding inf gets no histogram and NaN histogram_bins

1 participant