Pandas Period の freq
Pandasでは日付を Period
として扱うことができる。
メリットをあげると例えば以下がある。
- 期間を扱えるため、
四半期後
とか半年後
などの計算が簡単になる。(+ 1
とかすればいい) - 期中の中途半端な日付を情報を保存しつつ丸めることが可能
PandasのPeriodは使いこなせれば便利だけど、datetime
との行き来がめんどくさかったり、期待している挙動をしないことが多いためメモしておく。 今回は freq
に関して。 (そのうちdatetime
変換のところもまとめる。)
Periodを使う際に期間の切り方に関するパラメータが freq
。
Q
(クォーター) のデフォルトでは Q-DEC
になっているため、年末くぎり。 日本の__年度__は4月からなので、それらを指定したいときとかに使う。
パラメータに関する本家リンク
freq
で指定できるパラメータはこの辺。 どこで使える使えないの情報は細かく記載されていない。
https://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
https://pandas.pydata.org/pandas-docs/stable/timeseries.html#anchored-offsets
まとめ
パラメータの指定が全部動くのは date_range
だけど、それだと Period
の恩恵を受けられない。
とりあえずの対処法としては期間指定とかを諦めてデフォルトで変換して処理を行い、処理結果を表示するときは datetime
などに戻すのがよさそう。
特にPeriodの QS
はBug(enhancement?)っぽいので issue
あげるなり、原因特定してコミットしたいところ(ちょっとみたところpandas内の tslib
はCythonだからしんどそう…)。
対応表
Q | 2Q | QS | Q-APR | 2QS | 2Q-APR | QS-APR | 2QS-APR | |
---|---|---|---|---|---|---|---|---|
Period | ○ | ○ | × | ○ | × | ○ | × | × |
period_range | ○ | ○ | × | ○ | × | ○ | × | × |
date_range | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ |
date_range.to_period | ○ | × | ※1 | ○ | × | × | ※1 | × |
※1: エラーははかれないが freq
がデフォルト( Q-DEC
)になる
出力結果 (エラーなど)
Pandas のバージョン
In [82]: pd.__version__
Out[82]: '0.23.4'
Period
2Q
In [56]: pd.Period('2018-04-01', freq='2Q')
Out[56]: Period('2018Q2', '2Q-DEC')
QS
In [37]: pd.Period('2018-04-01', freq='QS')
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies._period_str_to_code()
KeyError: 'QS-JAN'
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
<ipython-input-37-670d308b00be> in <module>()
----> 1 pd.Period('2018-04-01', freq='QS')
pandas/_libs/tslibs/period.pyx in pandas._libs.tslibs.period.Period.__new__()
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies.get_freq_code()
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies.get_freq_code()
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies._period_str_to_code()
ValueError: Invalid frequency: QS-JAN
period_range
2Q
In [26]: pd.period_range('2018-04-01', '2020-01-01', freq='2Q')
Out[26]: PeriodIndex(['2018Q2', '2018Q4', '2019Q2', '2019Q4'], dtype='period[2Q-DEC]', freq='2Q-DEC')
2Q + 1
In [27]: pd.period_range('2018-04-01', '2020-01-01', freq='2Q') + 1
Out[27]: PeriodIndex(['2018Q4', '2019Q2', '2019Q4', '2020Q2'], dtype='period[2Q-DEC]', freq='2Q-DEC')
QS
In [33]: pd.period_range('2018-04-01', '2020-01-01', freq='QS')
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies._period_str_to_code()
KeyError: 'QS-JAN'
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
<ipython-input-33-25a035e5f517> in <module>()
----> 1 pd.period_range('2018-04-01', '2020-01-01', freq='QS')
~/.pyenv/versions/miniconda-latest/envs/test/lib/python3.6/site-packages/pandas/core/indexes/period.py in period_range(start, end, periods, freq, name)
1295
1296 return PeriodIndex(start=start, end=end, periods=periods,
-> 1297 freq=freq, name=name)
~/.pyenv/versions/miniconda-latest/envs/test/lib/python3.6/site-packages/pandas/core/indexes/period.py in __new__(cls, data, ordinal, freq, start, end, periods, tz, dtype, copy, name, **fields)
275 else:
276 data, freq = cls._generate_range(start, end, periods,
--> 277 freq, fields)
278 return cls._from_ordinals(data, name=name, freq=freq)
279
~/.pyenv/versions/miniconda-latest/envs/test/lib/python3.6/site-packages/pandas/core/indexes/period.py in _generate_range(cls, start, end, periods, freq, fields)
331 raise ValueError('Can either instantiate from fields '
332 'or endpoints, but not both')
--> 333 subarr, freq = _get_ordinal_range(start, end, periods, freq)
334 elif field_count > 0:
335 subarr, freq = _range_from_fields(freq=freq, **fields)
~/.pyenv/versions/miniconda-latest/envs/test/lib/python3.6/site-packages/pandas/core/indexes/period.py in _get_ordinal_range(start, end, periods, freq, mult)
1144
1145 if freq is not None:
-> 1146 _, mult = _gfc(freq)
1147
1148 if start is not None:
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies.get_freq_code()
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies.get_freq_code()
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies.get_freq_code()
pandas/_libs/tslibs/frequencies.pyx in pandas._libs.tslibs.frequencies._period_str_to_code()
ValueError: Invalid frequency: QS-JAN
date_range
QS-APR
In [55]: pd.date_range('2018-04-01', '2020-01-01', freq='QS-APR')
Out[55]:
DatetimeIndex(['2018-04-01', '2018-07-01', '2018-10-01', '2019-01-01',
'2019-04-01', '2019-07-01', '2019-10-01', '2020-01-01'],
dtype='datetime64[ns]', freq='QS-APR')
2QS
In [29]: pd.date_range('2018-04-01', '2020-01-01', freq='2QS')
Out[29]: DatetimeIndex(['2018-04-01', '2018-10-01', '2019-04-01', '2019-10-01'], dtype='datetime64[ns]', freq='2QS-JAN')
2QS-APR
In [30]: pd.date_range('2018-04-01', '2020-01-01', freq='2QS-APR')
Out[30]: DatetimeIndex(['2018-04-01', '2018-10-01', '2019-04-01', '2019-10-01'], dtype='datetime64[ns]', freq='2QS-APR')
2QS-APR + 1
In [31]: pd.date_range('2018-04-01', '2020-01-01', freq='2QS-APR') + 1
Out[31]: DatetimeIndex(['2018-10-01', '2019-04-01', '2019-10-01', '2020-04-01'], dtype='datetime64[ns]', freq='2QS-APR')
date_range.to_period
Q
In [48]: pd.date_range('2018-04-01', '2020-01-01', freq='Q').to_period()
Out[48]:
PeriodIndex(['2018Q2', '2018Q3', '2018Q4', '2019Q1', '2019Q2', '2019Q3',
'2019Q4'],
dtype='period[Q-DEC]', freq='Q-DEC')
QS
QS
の部分はperiodに変換したときに無視されている
In [49]: pd.date_range('2018-04-01', '2020-01-01', freq='QS').to_period()
Out[49]:
PeriodIndex(['2018Q2', '2018Q3', '2018Q4', '2019Q1', '2019Q2', '2019Q3',
'2019Q4', '2020Q1'],
dtype='period[Q-DEC]', freq='Q-DEC')
Q-APR
Q-APR
は保存される
In [53]: pd.date_range('2018-04-01', '2020-01-01', freq='Q-APR').to_period()
Out[53]:
PeriodIndex(['2018Q4', '2019Q1', '2019Q2', '2019Q3', '2019Q4', '2020Q1',
'2020Q2'],
dtype='period[Q-APR]', freq='Q-APR')
QS-APR
APRも無視される
In [54]: pd.date_range('2018-04-01', '2020-01-01', freq='QS-APR').to_period()
Out[54]:
PeriodIndex(['2018Q2', '2018Q3', '2018Q4', '2019Q1', '2019Q2', '2019Q3',
'2019Q4', '2020Q1'],
dtype='period[Q-DEC]', freq='Q-DEC')
2Q
なぜかエラー
In [42]: pd.date_range('2018-04-01', '2020-01-01', freq='2Q').to_period()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-42-ab52acd3f100> in <module>()
----> 1 pd.date_range('2018-04-01', '2020-01-01', freq='2Q').to_period()
~/.pyenv/versions/miniconda-latest/envs/test/lib/python3.6/site-packages/pandas/core/indexes/datetimes.py in to_period(self, freq)
1117 freq = get_period_alias(freq)
1118
-> 1119 return PeriodIndex(self.values, name=self.name, freq=freq, tz=self.tz)
1120
1121 def snap(self, freq='S'):
~/.pyenv/versions/miniconda-latest/envs/test/lib/python3.6/site-packages/pandas/core/indexes/period.py in __new__(cls, data, ordinal, freq, start, end, periods, tz, dtype, copy, name, **fields)
303 # datetime other than period
304 if is_datetime64_dtype(data.dtype):
--> 305 data = dt64arr_to_periodarr(data, freq, tz)
306 return cls._from_ordinals(data, name=name, freq=freq)
307
~/.pyenv/versions/miniconda-latest/envs/test/lib/python3.6/site-packages/pandas/core/indexes/period.py in dt64arr_to_periodarr(data, freq, tz)
65 raise ValueError('Wrong dtype: %s' % data.dtype)
66
---> 67 freq = Period._maybe_convert_freq(freq)
68 base, mult = _gfc(freq)
69 return period.dt64arr_to_periodarr(data.view('i8'), base, tz)
pandas/_libs/tslibs/period.pyx in pandas._libs.tslibs.period._Period._maybe_convert_freq()
AttributeError: 'NoneType' object has no attribute 'n'