[python3][pandas]時間でroundを使う際の細かい話

# pyhton3のdt.round
時間を指定した区切りに丸めることができる。
```python
rng = pd.date_range('1/1/2018 11:59:00', periods=3, freq='min')

print(rng)
print(rng.round('H')) # 'H'：時間で丸める

"""出力
DatetimeIndex(['2018-01-01 11:59:00', '2018-01-01 12:00:00',
               '2018-01-01 12:01:00'],
              dtype='datetime64[ns]', freq='T')
DatetimeIndex(['2018-01-01 12:00:00', '2018-01-01 12:00:00',
               '2018-01-01 12:00:00'],
              dtype='datetime64[ns]', freq=None)
"""
```
# ドキュメント
https://pandas.pydata.org/docs/reference/api/pandas.Series.dt.round.html

# やること
このdt.round(時間の丸め)を使う際に入力時間を細かくして確認したときに
想定と違う動作をしたので、それについてアウトプットする。

# 実行環境
windows 11
python 3.10.5
pandas 1.5.1

# 使い方
```python
import pandas as pd
df_date = pd.DataFrame({'初期': ['2022-11-26 12:07:30', '2022-11-26 12:07:31']})
df_date['初期'] = pd.to_datetime(df_date['初期']) # 文字列をdatetimeに変換する
df_date['round'] = df_date['初期'].dt.round("15T")
print(df_date)

""" 出力
                   初期               round
0 2022-11-26 12:07:30 2022-11-26 12:00:00
1 2022-11-26 12:07:31 2022-11-26 12:15:00
"""
```
round("15T")で15分丸めを指定した場合
半分である7分30秒を基準時間として出力が
「切り上げ、切り捨て」と変化する

# 入力時間を細かく変えて確認
## 15分丸め

```python
df_time = pd.DataFrame(pd.date_range(start='2022-01-01 12:07:00', periods=60, freq="1S", name="初期"))

# 半分を基準に切り上げ、切り捨てする
df_time['round'] = df_time['初期'].dt.round("15T")

print(df_time)
df_time.to_csv("time_round.csv")
```
丸め時間を「15分、30分、45分、00分」と変化するように
入力データ（1秒ごとの時間データ）を生成して確認すると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/004b3d97-3283-47b4-f099-fb7adb534ee6.png)

丸め時間によって切り替わる基準時間が
30秒と31秒に分かれる。

## 10分丸め
roundは「偶数に丸めようとする」

https://qiita.com/yoshi-kin/items/26e0cfc1c6768be2fc83#round

というのがあるので、丸め時間を10分（偶数）にすると
切り替わる基準時間が同じになると想定し試すと
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/5e3a4737-7f48-b97b-c313-563e5083a77b.png)
基準時間が1秒と0秒と別れ、想定通りにならなかったが
15分丸めと同じように「切り上げ、切り捨て」を繰り替えしているのがわかる

## 1分丸め
さらに細かくしても「切り上げ、切り捨て」を繰り替えす
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/a40c82b7-ad18-ba17-0a89-6b773c8332d9.png)

# 見えてきた法則
x(=1)分区切りの時

0. 丸め時間の半分が基準時間となるx分/2(=30秒)の
0. 「x(=1)分、3x(=3)分、5x(=5)分・・・」の時の基準時間は切り捨て(奇数のような動作？)
0. 「0分、2x(=2)分、4x(=4)分・・・」の時の基準時間は切り上げ(偶数のような動作？)

3のように「丸め時間の2倍」を「偶数」のように扱い、
基準時間の時に「偶数」側に近づくように丸める

# 丸め基準時間を固定するには
「時間で判定して切り上げ、切り捨て」というように
処理を自分で書くしかなさそう
## 丸め基準時間固定の1分区切り
```python
def df_time_round(df_datetimes):
    # 引数のDataFrameをコピー
    return_df_datetimes = df_datetimes.copy()
    for index, time in enumerate(df_datetimes):
        # 30秒以下の場合
        if time.second <= 30:
            # 1分区切りの切り捨て
            return_df_datetimes[index] = time.floor(freq='1T')
        # 31秒以上の場合
        else:
            # 1分区切りの切り上げ
            return_df_datetimes[index] = time.ceil(freq='1T')
    
    return return_df_datetimes

minute_num = 1
df_time = pd.DataFrame(pd.date_range(start='2022-01-01 12:' + str(minute_num) + ':00', periods=60, freq="1S", name="initial"))

# 関数で処理した結果を返し、csvに出力
df_time["new_round"] = df_time_round(df_time['initial'])
df_time.to_csv("time_round1_" + str(minute_num) + ".csv")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/558e3b02-6b0d-1d94-e016-741767f9e721.png)
基準時間の31秒になると繰り上げとなるようにできた！


# まとめ
細かい話で確認や推測を立てるのが大変だが
分析すると法則が見えてくるなと思いました。