Skip to content

Commit 344e7d5

Browse files
fix: count/nunique aggregates yield 0, not NaN, for parents without children
1 parent 5e84ae7 commit 344e7d5

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

src/auto_bayesian/materialize.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ def materialize_project(
5454
left_on=relation.parent_key,
5555
right_on=relation.parent_key,
5656
)
57+
count_columns = [
58+
aggregate.output_name(relation.child)
59+
for aggregate in relation.aggregations
60+
if aggregate.op in ("count", "nunique")
61+
]
62+
if count_columns:
63+
# A parent with no child rows has a count of 0, not a missing
64+
# value. Left NaN, these zeros would later be binned as
65+
# "__missing__" and become indistinguishable from genuinely
66+
# absent data. Other ops (mean, latest, ...) stay NaN, since
67+
# they are truly undefined over an empty set.
68+
parent[count_columns] = parent[count_columns].fillna(0)
5769
if relation.sequence_features:
5870
seq = _compute_sequence_features(child, relation, project.tables[relation.child])
5971
parent = parent.merge(

tests/test_materialize.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,51 @@ def test_materialize_project_joins_and_aggregates() -> None:
2222
assert lead_2["interaction_count"] == 2
2323
assert lead_2["channel_count"] == 2
2424
assert lead_2["latest_channel"] == "meeting"
25+
26+
27+
def test_one_to_many_count_is_zero_for_parents_without_children(tmp_path: Path) -> None:
28+
import pandas as pd
29+
30+
from auto_bayesian import build_project
31+
32+
parents = pd.DataFrame({"tender_id": ["T1", "T2", "T3"], "won": ["1", "0", "0"]})
33+
children = pd.DataFrame(
34+
{
35+
"bid_id": ["B1", "B2", "B3"],
36+
"tender_id": ["T1", "T1", "T2"],
37+
"bidder": ["a", "b", "a"],
38+
"price": [0.9, 1.1, 0.95],
39+
}
40+
)
41+
project = build_project(
42+
root=tmp_path,
43+
root_table="tenders",
44+
target_column="won",
45+
positive_label="1",
46+
output_dir=tmp_path / "artifacts",
47+
tables=[
48+
{"name": "tenders", "primary_key": "tender_id"},
49+
{"name": "bids", "primary_key": "bid_id"},
50+
],
51+
relations=[
52+
{
53+
"parent": "tenders",
54+
"child": "bids",
55+
"parent_key": "tender_id",
56+
"child_key": "tender_id",
57+
"kind": "one_to_many",
58+
"aggregations": [
59+
{"op": "count", "name": "bid_count"},
60+
{"column": "bidder", "op": "nunique", "name": "bidder_count"},
61+
{"column": "price", "op": "mean", "name": "mean_price"},
62+
],
63+
}
64+
],
65+
)
66+
frame = materialize_project(project, tables={"tenders": parents, "bids": children})
67+
childless = frame.loc[frame["tender_id"] == "T3"].iloc[0]
68+
69+
assert childless["bid_count"] == 0
70+
assert childless["bidder_count"] == 0
71+
assert pd.isna(childless["mean_price"])
72+
assert frame.loc[frame["tender_id"] == "T1"].iloc[0]["bid_count"] == 2

0 commit comments

Comments
 (0)