|
4 | 4 | date, |
5 | 5 | datetime, |
6 | 6 | ) |
| 7 | +from decimal import Decimal |
7 | 8 | import functools |
8 | 9 | import operator |
9 | 10 | from pathlib import Path |
@@ -481,6 +482,31 @@ def _cast_pointwise_result(self, values) -> ArrayLike: |
481 | 482 | except pa.lib.ArrowInvalid: |
482 | 483 | # e.g. test_combine_add if we can't cast |
483 | 484 | pass |
| 485 | + elif pa.types.is_null(arr.type): |
| 486 | + # ``pa.array`` will produce null-dtype arrays when every value is a |
| 487 | + # Decimal NaN. Try to preserve decimal storage by rebuilding the array |
| 488 | + # with an explicit decimal type derived from the input values. |
| 489 | + decimals = [val for val in values if isinstance(val, Decimal)] |
| 490 | + if decimals and all( |
| 491 | + isinstance(val, Decimal) or isna(val) for val in values |
| 492 | + ): |
| 493 | + decimal_type: pa.DataType | None = None |
| 494 | + for dec in decimals: |
| 495 | + if getattr(dec, "is_nan", None) and dec.is_nan(): |
| 496 | + continue |
| 497 | + try: |
| 498 | + decimal_type = pa.scalar(dec).type |
| 499 | + break |
| 500 | + except pa.ArrowInvalid: |
| 501 | + continue |
| 502 | + if decimal_type is None: |
| 503 | + # All decimals were NaN -> fall back to a wide decimal so we |
| 504 | + # can retain the decimal dtype even though values stay null. |
| 505 | + decimal_type = pa.decimal128(38, 18) |
| 506 | + try: |
| 507 | + arr = pa.array(values, type=decimal_type, from_pandas=True) |
| 508 | + except (pa.ArrowInvalid, pa.ArrowTypeError): |
| 509 | + pass |
484 | 510 |
|
485 | 511 | if isinstance(self.dtype, StringDtype): |
486 | 512 | if pa.types.is_string(arr.type) or pa.types.is_large_string(arr.type): |
|
0 commit comments