Add StandardSum: a sum aggregate where zero valid values yields null#8705
Add StandardSum: a sum aggregate where zero valid values yields null#8705mhk197 wants to merge 14 commits into
StandardSum: a sum aggregate where zero valid values yields null#8705Conversation
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Merging this PR will not alter performance
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
gatesn
left a comment
There was a problem hiding this comment.
I think... with the new Zoned layout, the aggregate partial has become a wire API.
That said, we haven't updated FileStats to use aggregate partials. And I don't believe zone maps use Sum?
A safer option would be to add a separate Sum function with this new behavior and different partial. @robert3005 any thoughts on a preferred direction?
Either way, we should separately make sure we have forward/backward compatibility tests for zoned statistics!
…s and dead code Signed-off-by: Matt Katz <mhkatz97@gmail.com>
|
we have SUM in zonemaps |
|
@mhk197 I think simpler would be to add another function. Maybe even rename them such that the new sql semantic sum is called |
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
…s vortex.sum Signed-off-by: Matt Katz <mhkatz97@gmail.com>
…ndardSum, used only by list_sum Signed-off-by: Matt Katz <mhkatz97@gmail.com>
…ns a lazy masked array Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
…accumulate Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
…op arithmetic duplicated by shared-kernel tests Signed-off-by: Matt Katz <mhkatz97@gmail.com>
StandardSum: a sum aggregate where zero valid values yields null
|
Why can we not use |
|
We can but in order to support it you have to change the partial and the partial is being written to the files therefore you have to handle partial being in the old form even if you want to compute the new sum, maybe the semantics are clear but I thought it might be easier this way. |
| // The field's values are unchanged, but `get_item` on a *nullable* struct has a | ||
| // nullable result dtype even when every struct row is valid, so a non-nullable | ||
| // field needs a nullability cast to match. | ||
| if child.as_ref().dtype().is_nullable() && !field.dtype().is_nullable() { |
There was a problem hiding this comment.
I feel like this was a long standing bug
| // Partials are `{sum, seen}` structs. A plain scalar is a cached `Stat::Sum` value, | ||
| // which cannot distinguish an empty sum from a zero sum, so it is treated as seen. | ||
| let (sum_value, other_seen) = if matches!(other.dtype(), DType::Struct(..)) { | ||
| if other.is_null() { |
There was a problem hiding this comment.
How can this happen?
There was a problem hiding this comment.
e.g. partial sum calculated over [-1,1], [0, 0, 0, 0] vs [null, null], []
joseph-isaacs
left a comment
There was a problem hiding this comment.
Lets make sure we get this right
| /// Return the sum of an array. The result is null when the array has no valid values or the sum | ||
| /// overflows. | ||
| /// | ||
| /// See [`StandardSum`] for details. |
There was a problem hiding this comment.
Can we instead store a overflowed and a nullable partial sum
{partial: T, overflowed: bool} that way you can first check overflow of both and return, then use NULL op x = x semantics. This seems much simpler.
I guess overflowed == seen /\ is_null(value). But this seems more complicated to me
There was a problem hiding this comment.
Yep makes sense, this has been done.
There was a problem hiding this comment.
Wrt comment below, we're changing the semantics of partial sum field (NULL op x = x instead of poisoning merges) and partials are already persisted on wire. This change would result in old stored Sum stats/zone maps creating a lot of work, since would have to fetch and canonicalize underlying arrays to fetch validity + count non-null bits
|
I really want to avoid 100s of very similar partial. I am not sure we want to create a new one here vs using a good default for the other. |
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
|
The other one is part of the serialized ZonedLayout. You cannot change an aggregate partial |
Adds a new aggregate function
StandardSumwhose sum over zero valid values (empty or all-null input) is null rather than zero, matchingDuckDB,arrow-rs, andDataFusion. Overflow still yields null.list_sumnow uses it, which makes its empty/all-null-list second pass unnecessary.The existing
Sumis untouched. Since the zoned layout persistsAggregateSpecProto{ id: "vortex.sum", .. } andSum's partial is wire format, it makes more sense to keep its name for now.Sumkeeps being what statistics store and merge (identity-on-empty is what stat merging requires now).StandardSum's partial aggregate is{sum, seen}rather than justseen. A nullsumalready means overflow and must poison merges, while "empty" must be the merge identity so one nullable scalar can't encode both. SoStandardSum's partial carries a seen flag like DuckDB'sisset. The identity is{0, false}, and merge is is null-poisoning add ofsumand union ofseen., Finalize mapsseen == 0to null.Implementation notes:
{sum, seen}rows directlyThis also fixes
StructGetItemRule.get_itemon a nullable struct has a nullable result dtype even when every row is valid, but the rule returned the raw non-nullable field, causing a reduced-dtype mismatch (surfaced by the lazy finalize over null groups; it's a standalone bug).