Skip to content

Commit 7092055

Browse files
authored
fix(pandas): add missing columns without an explicit dtype (#2609)
Signed-off-by: chivector <200636136+chivector@users.noreply.github.com> Co-authored-by: chivector <200636136+chivector@users.noreply.github.com>
1 parent 48e0e7e commit 7092055

2 files changed

Lines changed: 67 additions & 3 deletions

File tree

‎pandera/backends/pandas/container.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,9 +653,10 @@ def _construct_missing_df(
653653
# series is relatively slow due to copying the index for
654654
# each one. Coerce dtypes afterwards instead.
655655
for c in missing_obj.columns:
656-
missing_obj[c] = missing_cols_schema[c].dtype.try_coerce(
657-
missing_obj[c]
658-
)
656+
if missing_cols_schema[c].dtype is not None:
657+
missing_obj[c] = missing_cols_schema[c].dtype.try_coerce(
658+
missing_obj[c]
659+
)
659660

660661
return missing_obj
661662

‎tests/pandas/test_schemas.py‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,69 @@ def test_add_missing_columns_dtype():
652652
pd.testing.assert_frame_equal(ref_df, test_df)
653653

654654

655+
@pytest.mark.parametrize(
656+
"default, nullable",
657+
[
658+
(None, True),
659+
(np.nan, True),
660+
(pd.NA, True),
661+
(pd.NaT, True),
662+
(7, False),
663+
("missing", False),
664+
(False, False),
665+
],
666+
)
667+
@pytest.mark.parametrize("size", [0, 2])
668+
@pytest.mark.parametrize("coerce", [False, True])
669+
@pytest.mark.parametrize("lazy", [False, True])
670+
def test_add_missing_columns_without_dtype(
671+
default: Any, nullable: bool, size: int, coerce: bool, lazy: bool
672+
) -> None:
673+
"""Missing untyped columns retain pandas' inferred default dtype."""
674+
index = pd.Index([4] * size, name="row")
675+
frame = pd.DataFrame({"present": range(size)}, index=index)
676+
schema = DataFrameSchema(
677+
{
678+
"untyped": Column(default=default, nullable=nullable),
679+
"present": Column(int),
680+
"typed": Column("Int8", default=pd.NA, nullable=True),
681+
"null": Column(nullable=True),
682+
"optional": Column(required=False, default=1),
683+
},
684+
add_missing_columns=True,
685+
coerce=coerce,
686+
)
687+
expected = pd.DataFrame(
688+
{
689+
"untyped": default,
690+
"present": range(size),
691+
"typed": pd.NA,
692+
"null": None,
693+
},
694+
index=index,
695+
).astype({"typed": "Int8"})
696+
697+
validated = schema.validate(frame, lazy=lazy)
698+
699+
pd.testing.assert_frame_equal(validated, expected)
700+
assert frame.columns.tolist() == ["present"]
701+
702+
703+
@pytest.mark.parametrize("lazy", [False, True])
704+
def test_add_missing_untyped_column_with_schema_dtype(lazy: bool) -> None:
705+
"""Schema-level coercion still applies to an added untyped column."""
706+
schema = DataFrameSchema(
707+
{"missing": Column(default="7")},
708+
dtype="int64",
709+
coerce=True,
710+
add_missing_columns=True,
711+
)
712+
713+
validated = schema.validate(pd.DataFrame(index=[0]), lazy=lazy)
714+
715+
pd.testing.assert_frame_equal(validated, pd.DataFrame({"missing": [7]}))
716+
717+
655718
def test_add_missing_columns_reports_all_in_lazy_validation():
656719
"""Lazy validation should report every missing non-nullable column without
657720
a default value, not only the first one.

0 commit comments

Comments
 (0)