Shape / rows · All operations
Split rows into equal buckets
A variant of Number or rank rows
Assign each row to one of N buckets of equal size, in order.
Status
- In arcform
Runs as a SQL step
arcform carries it as a
sql:step today.
The DuckDB step
Split rows into N buckets of equal size, in order
It reads the orders table the proofs share. Create it first:
CREATE TABLE orders AS SELECT * FROM (VALUES
(1, 'Alice Ng', 'north', DATE '2026-01-03', 120.00, 'widget', 'a|b', '{"channel":"web","items":[1,2]}'),
(2, 'alice ng', 'north', DATE '2026-01-05', 80.50, 'gadget', 'b', '{"channel":"shop","items":[3]}'),
(3, 'Bob Okafor', 'south', DATE '2026-01-05', 300.00, 'widget', 'a|c', '{"channel":"web","items":[4,5,6]}'),
(4, 'Carla Díaz', 'east', DATE '2026-01-09', NULL, 'gizmo', NULL, '{"channel":"web","items":[]}'),
(5, 'Bob Okafor', 'south', DATE '2026-01-12', 45.25, 'gadget', 'c', '{"channel":"phone","items":[7]}'),
(6, 'Dev Patel', 'west', DATE '2026-01-12', 510.00, 'widget', 'a', '{"channel":"web","items":[8,9]}'),
(7, 'Carla Diaz', 'east', DATE '2026-01-20', 99.99, 'gizmo', 'b|c', '{"channel":"shop","items":[10]}'),
(8, 'Dev Patel', 'west', DATE '2026-02-02', 510.00, 'widget', 'a', '{"channel":"web","items":[8,9]}'),
(9, 'Eve Larsen', NULL, DATE '2026-02-03', 15.00, 'gadget', NULL, '{"channel":"web","items":[11]}'),
(10, 'Alice Ng', 'north', DATE '2026-02-10', 220.00, 'gizmo', 'a|b|c', '{"channel":"phone","items":[12,13]}')
) t(order_id, customer, region, order_date, amount, product, tags, meta);Then run the statement:
CREATE TEMP TABLE bucketed AS
SELECT order_id, amount,
ntile(5) OVER (ORDER BY amount NULLS LAST, order_id) AS fifth,
ntile(4) OVER (ORDER BY amount NULLS LAST, order_id) AS quarter
FROM orders;
SELECT * FROM bucketed ORDER BY amount NULLS LAST, order_id;
SELECT CASE WHEN ((SELECT list(ids ORDER BY fifth) FROM (SELECT fifth, list(order_id ORDER BY amount NULLS LAST, order_id) AS ids FROM bucketed GROUP BY fifth)) = [[9, 5], [2, 7], [1, 10], [3, 6], [8, 4]] AND (SELECT list(n ORDER BY quarter) FROM (SELECT quarter, count(*) AS n FROM bucketed GROUP BY quarter)) = [3, 3, 2, 2]) THEN 'as expected' ELSE error('ten rows in five buckets were not five pairs in amount order, or in four buckets were not sized 3, 3, 2 and 2') END AS verdict;- It needs nothing installed beyond DuckDB itself.
- The statement checks its own result and raises an error when it is wrong: “ten rows in five buckets were not five pairs in amount order, or in four buckets were not sized 3, 3, 2 and 2”.
- It passed on DuckDB
v2.0.0 (Cyanoptera) d54cbbe3df.
What other tools call it
Meridian's classification; an independent blind rating agreed on 95.7% of 324 rows (variant, version 3).
Apache DataFusion (1)
ntileread 2026-09-27Window: Bucket number from 1 to n dividing the partition as equally as possible
DuckDB (1)
ntileread 2026-09-27ntile aggregate
Duckle (1)
NTileread 2026-09-25NTILE bucket over a window
Ibis (1)
NTileread 2026-09-27Assign each row of an ordered window to one of N equal-sized buckets
RapidMiner Studio (1)
Generate Batchread 2026-09-27Add a batch-number column dividing rows into equally sized batches
Background
In SQL Server, NTILE distributes the rows of an ordered partition into a given number of numbered groups and returns the group each row belongs to.[1] Common quantiles have special names, such as quartiles (four groups), deciles (ten groups) and percentiles (100 groups).[2] In SQL Server, when the row count does not divide by the number of groups, group sizes differ by one and the larger groups come first.[1]
When to use it
In SQL Server, 53 rows split into five groups give three groups of 11 rows and two groups of 10 rows.[1] In the dplyr package for R, ntile() ignores ties: it creates evenly sized buckets even if the same value ends up in different buckets.[3]
In SQL Server, NTILE is nondeterministic.[1]
The method
In PostgreSQL and DuckDB, ntile returns an integer ranging from 1 to its argument, dividing the partition as equally as possible.[4][5] In SQL Server, NTILE with PARTITION BY first partitions the rows and then divides each partition into the given number of groups.[1] In statistics, q-quantiles are values that partition a finite set of values into q subsets of nearly equal sizes.[2] Quantiles are cut points, though the terms for them, such as quartiles, are more often used for the groups created than for the cut points.[2] Read an ntile bucket number as the group a row fell into, not as a boundary value, and compute the cut points separately if you need them. (Meridian's view)
Compared with the alternatives
In dplyr, ntile() is a sort of very rough rank, which breaks the input vector into n buckets.[3] Use a bucket when you want a fixed number of groups of about the same size, and a percentile rank when you want each row's own position as a fraction. (Meridian's view)
Sources
- [1]NTILE (Transact-SQL) - SQL Server | Microsoft Learndocumentation
- [2]Quantile - Wikipediasecondary source
- [3]R: Bucket a numeric vector into 'n' groupsdocumentation
- [4]PostgreSQL: Documentation: 18: 9.22. Window Functionsdocumentation
- [5]Window Functions – DuckDBdocumentation