Repository navigation
Keep row validity and payloads in fixed-shape ArrayXD pandas round trips - #8889
choksi2212 wants to merge 1 commit into
Conversation
…pandas round trips PandasArrayExtensionDtype.__from_arrow__ materialized every fixed-shape ArrayXD column through ArrayExtensionArray.to_numpy, which pads the null rows with NaN and promotes the whole column to float64. The pandas boundary therefore lost the Arrow row validity (a null row and a legitimate all-NaN array became indistinguishable) and the exact payloads of integer columns: values above 2**53 lost precision, and converting the NaN rows back to int64 raised ArrowInvalid. A fixed-shape column with null rows is now materialized as one exact ndarray per valid row and None per null row, like a dynamic shape already does, so Dataset.from_pandas(ds.to_pandas(), features=...) keeps the row validity bitmap and the payloads. The pandas dtype keeps the fixed shape and the value type, so an all-null column can rebuild its extension type as well, and the dtype is carried through copy/slice/take/concat/_from_sequence. isna() no longer crashes on None rows. Dynamic shapes and fixed shapes without null rows keep their previous representations. Signed-off-by: choksi2212 <choksi2212@users.noreply.github.com>
|
CI note: this is the first PR from my fork, so the |
|
At from datasets import Array2D, Dataset, Features
features = Features({"x": Array2D((2, 3), "float64")})
source = Dataset.from_dict(
{"x": [[[0., 1., 2.], [3., 4., 5.]], None]}, features=features
)
result = Dataset.from_pandas(source.to_pandas(), features=features)
assert result.data.column("x").type.shape == result.features["x"].shape
# PR: (None, 3) != (2, 3)On base
Independently reproduced with Codex-assisted tests on Python 3.12.14, pandas 3.1.0, PyArrow 26.0.0 and NumPy 2.5.4. |
Fixes #8888
PandasArrayExtensionDtype.__from_arrow__materialized every fixed-shape ArrayXD column throughArrayExtensionArray.to_numpy, which pads the null rows with NaN and promotes the whole column to float64. Two consequences at the pandas boundary: the Arrow row validity was gone (a null row and a legitimate all-NaN array became indistinguishable), and integer columns lost their exact payloads, with values above 2**53 rounding andDataset.from_pandas(..., features=...)raisingArrowInvalidwhen converting the NaN rows back to int64.A fixed-shape column with null rows is now materialized as one exact ndarray per valid row and None per null row, the representation the dynamic shape already uses, so
Dataset.from_pandas(ds.to_pandas(), features=ds.features)keeps the validity bitmap and the exact values:The pandas dtype now also carries the fixed shape and the value type, so a column whose rows are all null still rebuilds its extension type (on main that round trip silently turned the nulls into valid all-NaN rows), and the dtype is carried through copy, slicing, take,
_concat_same_typeand_from_sequence.isna()used to crash on None rows (pd.isna(None).any()); it now counts scalar None/NaN rows as null without inspecting them, while ndarray rows keep the existing NaN inspection. Dynamic shapes and fixed shapes without null rows keep their previous representations, so the null-row regression from #8701 and the NumPy formatting behavior kept by #8363 are untouched.The new tests fail on main and pass here: validity round trips for (1,1) and (2,3) columns with null rows in the middle, exact int64 payloads above 2**53, a sliced column, and an all-null column. Verified locally on the issue's pins (pandas 3.1.0, pyarrow 26.0.0, numpy 2.5.4, Python 3.13):
tests/features/test_array_xd.py(111),tests/features/test_features.py,tests/test_table.py(598 passed together), andruff check/ruff formatare clean.