awareなDataTimeIndexのshiftによる、不可解なAmbiguousTimeErrorについて


freq='D'のDateTimeIndexで、ニューヨーク時刻の2022/02/07を92日前にずらすとAmbiguousTimeErrorが発生します。

freq属性をNoneにすればエラーを回避できることが確認できましたが、現在の振る舞い/仕様が不可解で納得できずにいます。  
タイムゾーンを変換するときに、夏時間によるあいまいさが発生するのは理解でできたのですが、freq属性の有無で振る舞いが変わることが理解できません。  
この振る舞いは仕様（期待どおりの振る舞い）なのでしょうか？  
もし仕様だとしたら、それを裏付ける背景/理由などがご存じでしょうか？

Windows10, Python 3.9.1, Pandas 1.4.1 で100%再現しました。

[code]

    import platform
    import pandas as pd
    print(f'- Python:[{platform.python_version()}], Pandas:[{pd.__version__}]')
    
    datetime_index = pd.DatetimeIndex([pd.Timestamp('2022-02-07',tz='America/New_York')], freq='D')
    # datetime_index.freq = None # <---この行を実行するとエラー回避できた
    print('入力:', datetime_index)
    
    datetime_index = datetime_index.shift(periods=-92, freq='D')
    print('出力:', datetime_index)
    
[/code]

[![画像の説明をここに入力](https://i.stack.imgur.com/J7lzv.png)](https://i.stack.imgur.com/J7lzv.png)




> この振る舞いは仕様（期待どおりの振る舞い）なのでしょうか？

ソースコードの通りに動作しているので仕様と言えるのでしょう、おそらく。。。  
※ 本来であれば `shift` メソッドに `ceil` や `floor` メソッドと同様に `ambiguous`
キーワードが用意されているとよいのですけれども。

最初に、`pandas.core.indexes.datetimelike.DatetimeIndexOpsMixin.shift`
メソッドのソースコードは以下になります。

[pandas/datetimelike.py at 1.4.x · pandas-
dev/pandas](https://github.com/pandas-
dev/pandas/blob/1.4.x/pandas/core/indexes/datetimelike.py#L356)

[code]

    def shift(self: _T, periods: int = 1, freq=None) -> _T:
                      :
    
        arr = self._data.view()
        arr._freq = self.freq
        result = arr._time_shift(periods, freq=freq)
        return type(self)._simple_new(result, name=self.name)
    
[/code]

ここで、`pandas.core.arrays.datetimelike.DatetimeLikeArrayMixin._time_shift`
を呼び出しています。

[pandas/datetimelike.py at 1.4.x · pandas-
dev/pandas](https://github.com/pandas-
dev/pandas/blob/1.4.x/pandas/core/arrays/datetimelike.py#L1230)

[code]

    def _time_shift(
            self: DatetimeLikeArrayT, periods: int, freq=None
    ) -> DatetimeLikeArrayT:
                      :
    
        if freq is not None and freq != self.freq:
            if isinstance(freq, str):
                freq = to_offset(freq)
            offset = periods * freq
            return self + offset
                      :
    
    
[/code]

今回の場合、`freq` は `shift` の parameter で `-92` です。そして、

[code]

        datetime_index.freq = None
    
[/code]

を実行した場合、`self.freq` は `None` になります。つまり、この `if` statement が成立して(`True`)、

[code]

    >>> datetime_index + (-92) * pd._libs.tslibs.to_offset('D')
    DatetimeIndex(['2021-11-07 01:00:00-04:00'], dtype='datetime64[ns, America/New_York]', freq=None)
    
[/code]

が戻される事になります(DST は考慮されません)。




> この振る舞いは仕様（期待どおりの振る舞い）なのでしょうか？

ソースコードの通りに動作しているので仕様と言えるのでしょう、おそらく。。。  
※ 本来であれば `shift` メソッドに `ceil` や `floor` メソッドと同様に `ambiguous`
キーワードが用意されているとよいのですけれども。

最初に、`pandas.core.indexes.datetimelike.DatetimeIndexOpsMixin.shift`
メソッドのソースコードは以下になります。

[pandas/datetimelike.py at 1.4.x · pandas-
dev/pandas](https://github.com/pandas-
dev/pandas/blob/1.4.x/pandas/core/indexes/datetimelike.py#L356)

[code]

    def shift(self: _T, periods: int = 1, freq=None) -> _T:
                      :
    
        arr = self._data.view()
        arr._freq = self.freq
        result = arr._time_shift(periods, freq=freq)
        return type(self)._simple_new(result, name=self.name)
    
[/code]

ここで、`pandas.core.arrays.datetimelike.DatetimeLikeArrayMixin._time_shift`
を呼び出しています。

[pandas/datetimelike.py at 1.4.x · pandas-
dev/pandas](https://github.com/pandas-
dev/pandas/blob/1.4.x/pandas/core/arrays/datetimelike.py#L1230)

[code]

    def _time_shift(
            self: DatetimeLikeArrayT, periods: int, freq=None
    ) -> DatetimeLikeArrayT:
                      :
    
        if freq is not None and freq != self.freq:
            if isinstance(freq, str):
                freq = to_offset(freq)
            offset = periods * freq
            return self + offset
                      :
    
    
[/code]

今回の場合、`freq` は `shift` の parameter で `-92` です。そして、

[code]

        datetime_index.freq = None
    
[/code]

を実行した場合、`self.freq` は `None` になります。つまり、この `if` statement が成立して(`True`)、

[code]

    >>> datetime_index + (-92) * pd._libs.tslibs.to_offset('D')
    DatetimeIndex(['2021-11-07 01:00:00-04:00'], dtype='datetime64[ns, America/New_York]', freq=None)
    
[/code]

が戻される事になります(DST は考慮されません)。

