[SPARK-58545][PYTHON][TEST] Add tests for pa.Array.to_pandas with types_mapper - #57771
Closed
Spenserrrr wants to merge 1 commit into
Closed
[SPARK-58545][PYTHON][TEST] Add tests for pa.Array.to_pandas with types_mapper#57771Spenserrrr wants to merge 1 commit into
Spenserrrr wants to merge 1 commit into
Conversation
…es_mapper Pin the behavior of `pa.Array.to_pandas(types_mapper=pd.ArrowDtype)`, which PySpark calls in `ArrowArrayToPandasConversion.convert_numpy`, so that a pandas/PyArrow upgrade that changes it fails loudly instead of silently altering the pandas objects handed to a pandas UDF. Co-authored-by: Isaac
Spenserrrr
marked this pull request as ready for review
August 4, 2026 19:17
Contributor
Author
|
Hi @Yicong-Huang @zhengruifeng! This is the PR testing the types_mapper = pd.ArrowDtype for pa.Array.to_pandas. Could you take a look when you have a moment? Thank you! |
uros-b
approved these changes
Aug 5, 2026
Member
|
Thank you @Spenserrrr! Adding @Yicong-Huang for further review |
zhengruifeng
approved these changes
Aug 5, 2026
uros-b
pushed a commit
that referenced
this pull request
Aug 5, 2026
…es_mapper ### What changes were proposed in this pull request? This PR adds a golden-file test pinning the behavior of `pa.Array.to_pandas(types_mapper=pd.ArrowDtype)`, under the umbrella [SPARK-54936](https://issues.apache.org/jira/browse/SPARK-54936) (monitor behavior changes from upstream). It is a new test method on the existing `PyArrowArrayToPandasZeroCopyTests`, so it reuses that class's row set and the `_verify_zero_copy` / `_arrow_buffers` helpers rather than adding new machinery: - `test_to_pandas_zero_copy_only_arrow_backed`, with the new golden file `golden_pyarrow_arrow_to_pandas_zero_copy_arrow_backed.{csv,md}` (125 rows x 4 columns). - Columns: `pyarrow array` | `types_mapper=pd.ArrowDtype, zero_copy_only=False` | `types_mapper=pd.ArrowDtype, zero_copy_only=True` | `verified zero-copy`. Both `zero_copy_only` states are recorded even though the flag makes no observable difference on this backend today. Omitting a column because it looks redundant would bake that assumption into a monitoring test; recording both means a version where the flag starts to matter shows up as a diff instead of passing silently. No `dev/sparktestsupport/modules.py` change is needed, since this is a new method in an already-registered test module. ### Why are the changes needed? PySpark calls this argument in production, at `ArrowArrayToPandasConversion.convert_numpy` (`python/pyspark/sql/conversion.py`), for `ByteType` / `ShortType` / `IntegerType` / `LongType` when `spark.sql.execution.pythonUDF.pandas.preferIntExtensionDtype` is enabled. That config exists because the integer dtype handed to a pandas UDF otherwise depends on whether the current batch happens to contain a null (SPARK-55788), so a change in how `types_mapper` maps Arrow types would change what user UDF code receives. The existing goldens cover `to_pandas` on the NumPy backend. Asking pandas for Arrow-backed storage is a different conversion, and the point of that backend is that it can keep pointing at the Arrow buffers instead of materializing them into NumPy. Recording it in its own golden file lets the two be read side by side, and every row in this golden is `zero-copy`: strings, nested types and multi-chunk arrays, which `golden_pyarrow_arrow_to_pandas_zero_copy` records as copied on the NumPy backend, all reuse their buffers here. The tz-aware timestamp rows are the clearest contrast, since those are the ones where the NumPy backend reports `zero_copy_only=True` but pandas copies anyway; on this path the flag and the verified result agree. ### Does this PR introduce _any_ user-facing change? No. Test-only. ### How was this patch tested? New golden-file test. Generated with `SPARK_GENERATE_GOLDEN_FILES=1`, then every cell was reviewed before being committed. The whole `python/pyspark/tests/upstream/pyarrow/` directory passes (49 passed, 1 skipped), and regeneration is byte-identical, so the golden is deterministic. Validated across **PyArrow 18-25 x pandas 2 and pandas 3 = 16/16 combinations pass**, each in a fresh venv running the committed test file against the committed golden. All 16 pass with an empty `overrides` dict, which is worth noting because the sibling methods in this file do need a pandas-3 override for their string rows. `types_mapper=pd.ArrowDtype` selects the dtype explicitly, so pandas' default never applies and the pandas-3 change to it does not reach these cells: the default string conversion goes `object` (pandas 2) -> `str` (pandas 3), while `types_mapper` reads `string[pyarrow]` on both. This was checked cell by cell, not just on the string rows -- all 125 cells are identical between pandas 2 and 3 (on both PyArrow 18 and 24) and between PyArrow 18 and 24. Since this golden is uniformly `zero-copy`, the `verified zero-copy` column was sanity-checked rather than trusted: the empty rows perform a real buffer comparison rather than a vacuous zero-length one, and multi-chunk arrays preserve both chunk addresses instead of being combined. The same helper produces 86 `copied` rows in `golden_pyarrow_arrow_to_pandas_zero_copy`, so it is not rubber-stamping. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 5) Closes #57771 from Spenserrrr/types-mapper-tests. Authored-by: Spenser Sun <hsun112358@gmail.com> Signed-off-by: Uros Bojanic <221401595+uros-b@users.noreply.github.com> (cherry picked from commit 055c0b9) Signed-off-by: Uros Bojanic <221401595+uros-b@users.noreply.github.com>
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes were proposed in this pull request?
This PR adds a golden-file test pinning the behavior of
pa.Array.to_pandas(types_mapper=pd.ArrowDtype), under the umbrella SPARK-54936 (monitor behavior changes from upstream). It is a new test method on the existingPyArrowArrayToPandasZeroCopyTests, so it reuses that class's row set and the_verify_zero_copy/_arrow_buffershelpers rather than adding new machinery:test_to_pandas_zero_copy_only_arrow_backed, with the new golden filegolden_pyarrow_arrow_to_pandas_zero_copy_arrow_backed.{csv,md}(125 rows x 4 columns).pyarrow array|types_mapper=pd.ArrowDtype, zero_copy_only=False|types_mapper=pd.ArrowDtype, zero_copy_only=True|verified zero-copy.Both
zero_copy_onlystates are recorded even though the flag makes no observable difference on this backend today. Omitting a column because it looks redundant would bake that assumption into a monitoring test; recording both means a version where the flag starts to matter shows up as a diff instead of passing silently.No
dev/sparktestsupport/modules.pychange is needed, since this is a new method in an already-registered test module.Why are the changes needed?
PySpark calls this argument in production, at
ArrowArrayToPandasConversion.convert_numpy(python/pyspark/sql/conversion.py), forByteType/ShortType/IntegerType/LongTypewhenspark.sql.execution.pythonUDF.pandas.preferIntExtensionDtypeis enabled. That config exists because the integer dtype handed to a pandas UDF otherwise depends on whether the current batch happens to contain a null (SPARK-55788), so a change in howtypes_mappermaps Arrow types would change what user UDF code receives.The existing goldens cover
to_pandason the NumPy backend. Asking pandas for Arrow-backed storage is a different conversion, and the point of that backend is that it can keep pointing at the Arrow buffers instead of materializing them into NumPy. Recording it in its own golden file lets the two be read side by side, and every row in this golden iszero-copy: strings, nested types and multi-chunk arrays, whichgolden_pyarrow_arrow_to_pandas_zero_copyrecords as copied on the NumPy backend, all reuse their buffers here. The tz-aware timestamp rows are the clearest contrast, since those are the ones where the NumPy backend reportszero_copy_only=Truebut pandas copies anyway; on this path the flag and the verified result agree.Does this PR introduce any user-facing change?
No. Test-only.
How was this patch tested?
New golden-file test. Generated with
SPARK_GENERATE_GOLDEN_FILES=1, then every cell was reviewed before being committed.The whole
python/pyspark/tests/upstream/pyarrow/directory passes (49 passed, 1 skipped), and regeneration is byte-identical, so the golden is deterministic.Validated across PyArrow 18-25 x pandas 2 and pandas 3 = 16/16 combinations pass, each in a fresh venv running the committed test file against the committed golden.
All 16 pass with an empty
overridesdict, which is worth noting because the sibling methods in this file do need a pandas-3 override for their string rows.types_mapper=pd.ArrowDtypeselects the dtype explicitly, so pandas' default never applies and the pandas-3 change to it does not reach these cells: the default string conversion goesobject(pandas 2) ->str(pandas 3), whiletypes_mapperreadsstring[pyarrow]on both. This was checked cell by cell, not just on the string rows -- all 125 cells are identical between pandas 2 and 3 (on both PyArrow 18 and 24) and between PyArrow 18 and 24.Since this golden is uniformly
zero-copy, theverified zero-copycolumn was sanity-checked rather than trusted: the empty rows perform a real buffer comparison rather than a vacuous zero-length one, and multi-chunk arrays preserve both chunk addresses instead of being combined. The same helper produces 86copiedrows ingolden_pyarrow_arrow_to_pandas_zero_copy, so it is not rubber-stamping.Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 5)