|
38 | 38 | to_offset, |
39 | 39 | ) |
40 | 40 | from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit |
| 41 | +from pandas._libs.tslibs.offsets import FY5253Mixin |
41 | 42 | from pandas.compat.numpy import function as nv |
42 | 43 | from pandas.errors import ( |
43 | 44 | InvalidIndexError, |
@@ -682,6 +683,72 @@ def astype(self, dtype, copy: bool = True): |
682 | 683 | result._data._freq = self.freq |
683 | 684 | return result |
684 | 685 |
|
| 686 | + def putmask(self, mask, value) -> Index: |
| 687 | + # GH#24555 putmask may modify values out-of-sequence; drop freq |
| 688 | + result = super().putmask(mask, value) |
| 689 | + if isinstance(result, type(self)): |
| 690 | + result._data._freq = None |
| 691 | + return result |
| 692 | + |
| 693 | + def _pin_freq(self, freq, validate_kwds: dict) -> None: |
| 694 | + """ |
| 695 | + Constructor helper to pin the appropriate ``freq`` attribute on |
| 696 | + ``self._data``. Assumes ``self._data._freq`` is currently set to any |
| 697 | + freq inferred from input data. |
| 698 | + """ |
| 699 | + arr = self._data |
| 700 | + if freq is None: |
| 701 | + # user explicitly passed None -> override any inferred_freq |
| 702 | + arr._freq = None |
| 703 | + elif freq == "infer": |
| 704 | + # if arr._freq is *not* None then we already inferred a freq |
| 705 | + # and there is nothing left to do |
| 706 | + if arr._freq is None: |
| 707 | + # Set _freq directly to bypass duplicative _validate_frequency |
| 708 | + # check. |
| 709 | + arr._freq = to_offset(self.inferred_freq) |
| 710 | + elif freq is lib.no_default: |
| 711 | + # user did not specify anything, keep inferred freq if the original |
| 712 | + # data had one, otherwise do nothing |
| 713 | + pass |
| 714 | + elif arr._freq is None: |
| 715 | + # We cannot inherit a freq from the data, so we need to validate |
| 716 | + # the user-passed freq |
| 717 | + freq = to_offset(freq) |
| 718 | + type(arr)._validate_frequency(self, freq, **validate_kwds) |
| 719 | + arr._freq = freq |
| 720 | + else: |
| 721 | + # Otherwise we just need to check that the user-passed freq |
| 722 | + # doesn't conflict with the one we already have. |
| 723 | + freq = to_offset(freq) |
| 724 | + if freq != arr._freq: |
| 725 | + # GH#61086 freq may be equivalent but not equal (e.g. |
| 726 | + # QS-FEB vs QS-MAY), so validate against the actual data. |
| 727 | + if len(self) == 0: |
| 728 | + pass |
| 729 | + elif len(self) == 1: |
| 730 | + if not freq.is_on_offset(self[0]): |
| 731 | + raise ValueError( |
| 732 | + f"Inferred frequency {arr._freq} from passed " |
| 733 | + "values does not conform to passed frequency " |
| 734 | + f"{freq.freqstr}" |
| 735 | + ) |
| 736 | + elif self[0] + freq == self[1]: |
| 737 | + # For standard offsets, the step is a deterministic |
| 738 | + # function of the date, so agreement on one step proves |
| 739 | + # equivalence. For Custom/FY5253 offsets, external |
| 740 | + # state (holidays, 52/53-week patterns) could cause |
| 741 | + # later steps to diverge, so we validate fully. |
| 742 | + if hasattr(freq, "_holidays") or isinstance(freq, FY5253Mixin): |
| 743 | + type(arr)._validate_frequency(self, freq, **validate_kwds) |
| 744 | + else: |
| 745 | + raise ValueError( |
| 746 | + f"Inferred frequency {arr._freq} from passed " |
| 747 | + "values does not conform to passed frequency " |
| 748 | + f"{freq.freqstr}" |
| 749 | + ) |
| 750 | + arr._freq = freq |
| 751 | + |
685 | 752 | def _get_arithmetic_result_freq(self, other) -> BaseOffset | None: |
686 | 753 | """ |
687 | 754 | Check if we can preserve self.freq in addition or subtraction. |
@@ -850,13 +917,10 @@ def as_unit(self, unit: TimeUnit) -> Self: |
850 | 917 |
|
851 | 918 | def _with_freq(self, freq): |
852 | 919 | # GH#29843 |
853 | | - if freq is None: |
854 | | - # Always valid |
| 920 | + if freq is None or (len(self) == 0 and isinstance(freq, BaseOffset)): |
| 921 | + # None is always valid. For offsets on empty index the array's |
| 922 | + # _with_freq below performs the m-dtype Tick validation. |
855 | 923 | pass |
856 | | - elif len(self) == 0 and isinstance(freq, BaseOffset): |
857 | | - # Always valid. In the TimedeltaArray case, we require a Tick offset |
858 | | - if self.dtype.kind == "m" and not isinstance(freq, (Tick, Day)): |
859 | | - raise TypeError("TimedeltaArray/Index freq must be a Tick") |
860 | 924 | else: |
861 | 925 | # As an internal method, we can ensure this assertion always holds |
862 | 926 | assert freq == "infer" |
@@ -1231,9 +1295,6 @@ def _get_getitem_freq(self, key) -> BaseOffset | None: |
1231 | 1295 | """ |
1232 | 1296 | Find the `freq` attribute to assign to the result of a __getitem__ lookup. |
1233 | 1297 | """ |
1234 | | - if self.ndim != 1: |
1235 | | - return None |
1236 | | - |
1237 | 1298 | key = check_array_indexer(self._data, key) # maybe ndarray[bool] -> slice |
1238 | 1299 | freq = None |
1239 | 1300 | if isinstance(key, slice): |
|
0 commit comments