時間データ処理#

時間データは、集計でも可視化でも必ず出てきます。ただし数値列より落とし穴が多く、同じ時刻を Date、Datetime、Time、Duration の 4通りで表せて、精度もナノ秒から年まで幅があります。書式とタイムゾーンの問題も加わります。この章でここを整理しておけば、以降の時系列処理で型変換に詰まることは減ります。

内容は、時間型の基本、型の間の演算、時間軸でのグループ化(リサンプリング)、文字列との相互変換の順です。

import polars as pl
from helper.jupyter import row

時間のデータ型#

Polars には日付・時刻・時間間隔のためのデータ型が 4つあります。用途に合わせて選んでください。

データ型

概要

最小単位

ゼロ点

用途

例

Date

日付のみ(時刻なし)

日

Unix epoch (1970-01-01)

日単位の集計

2024-07-15

Time

1日の中の時刻

ナノ秒(ns)

00:00:00

時刻そのものを扱う

12:30:45.123456789

Datetime

日付と時刻の組

ミリ秒・マイクロ秒・ナノ秒

Unix epoch (1970-01-01 00:00:00)

タイムスタンプの処理

2024-07-15 12:30:45.123456

Duration

時間の長さ・間隔

ミリ秒・マイクロ秒・ナノ秒

-

2つの時点の差

2 days, 3 hours

整数の列から時間型の列を作るには、次の 4つの関数を使います。

  • pl.date() — 年・月・日から Date 型を作る

  • pl.time() — 時・分・秒などから Time 型を作る

  • pl.datetime() — 年・月・日・時・分・秒から Datetime 型を作る

  • pl.duration() — 長さを Duration 型として作る

df_numbers = pl.DataFrame(
    dict(
        year=[2022, 2023, 2024],
        month=[11, 10, 12],
        day=[4, 31, 2],
        hour=[1, 6, 16],
        minute=[10, 50, 34],
        second=[12.2, 20.5, 21],
    )
)

df_numbers = df_numbers.with_columns(
    total_seconds=pl.col('hour') * 3600 + pl.col('minute') * 60 + pl.col('second')
)

次のコードは、数値の列から時間型の列へ変換する例です。各関数は、対応する型の列を返します。

Date、Time、Datetime は各フィールドに範囲があるので、上限を超えないようにしてください。整数でない値は整数部分だけが使われます。また、❶ 秒が小数で入っている場合は、小数部分を取り出してマイクロ秒に換算します。

❷ Duration には範囲の制限がないので、一番小さい単位(ここでは microseconds)で列を作るのが確実です。

df_times = df_numbers.select(
    date=pl.date('year', 'month', 'day'),
    time=pl.time('hour', 'minute', 'second', microsecond=pl.col('second').mod(1).mul(1e6).round()), #❶
    datetime=pl.datetime('year', 'month', 'day', 'hour', 'minute', 'second', 
                         microsecond=pl.col('second').mod(1).mul(1e6).round()),
    duration=pl.duration(microseconds=pl.col('total_seconds').mul(1e6)), #❷
)

row(df_numbers, df_times)
shape: (3, 7)
yearmonthdayhourminutesecondtotal_seconds
i64i64i64i64i64f64f64
202211411012.24212.2
2023103165020.524620.5
2024122163421.059661.0
shape: (3, 4)
datetimedatetimeduration
datetimedatetime[μs]duration[μs]
2022-11-0401:10:12.2002022-11-04 01:10:12.2001h 10m 12s 200ms
2023-10-3106:50:20.5002023-10-31 06:50:20.5006h 50m 20s 500ms
2024-12-0216:34:212024-12-02 16:34:2116h 34m 21s

Datetime と Duration は、内部の時間単位(ナノ秒・マイクロ秒・ミリ秒)を変えられます。dt.cast_time_unit() で変換します。大きい単位へ変換すると端数は切り捨てられるので、ns → ms では情報が落ちます。

df_times.select(
    'datetime',
    pl.col('datetime').dt.cast_time_unit('ms').alias('datetime_ms'),
    pl.col('datetime').dt.cast_time_unit('ns').alias('datetime_ns'),
    'duration',
    pl.col('duration').dt.cast_time_unit('ms').alias('duration_ms'),
    pl.col('duration').dt.cast_time_unit('ns').alias('duration_ns'),    
)
shape: (3, 6)
datetimedatetime_msdatetime_nsdurationduration_msduration_ns
datetime[μs]datetime[ms]datetime[ns]duration[μs]duration[ms]duration[ns]
2022-11-04 01:10:12.2002022-11-04 01:10:12.2002022-11-04 01:10:12.2001h 10m 12s 200ms1h 10m 12s 200ms1h 10m 12s 200ms
2023-10-31 06:50:20.5002023-10-31 06:50:20.5002023-10-31 06:50:20.5006h 50m 20s 500ms6h 50m 20s 500ms6h 50m 20s 500ms
2024-12-02 16:34:212024-12-02 16:34:212024-12-02 16:34:2116h 34m 21s16h 34m 21s16h 34m 21s

時間型の演算式#

時間型同士の変換#

異なる時間型のあいだで変換する例です。

❶ dt.combine() — date 列と time 列を合わせて Datetime 型にする
❷ dt.date() — datetime から日付部分だけを取り出す
❸ dt.time() — datetime から時刻部分だけを取り出す
❹ cast(pl.Duration) — time を Duration にキャストする
❺ Duration を直接 Time にする関数はないので、ナノ秒単位の Duration にしてから to_physical() で Int64 にし、cast(pl.Time) で戻します。

df_times.select(
    datetime=pl.col('date').dt.combine(pl.col('time')), #❶
    date=pl.col('datetime').dt.date(), #❷
    time=pl.col('datetime').dt.time(), #❸
    duration=pl.col('time').cast(pl.Duration), #❹
    time2=pl.col('duration').dt.cast_time_unit('ns').to_physical().cast(pl.Time), #❺
)
shape: (3, 5)
datetimedatetimedurationtime2
datetime[μs]datetimeduration[μs]time
2022-11-04 01:10:12.2002022-11-0401:10:12.2001h 10m 12s 200ms01:10:12.200
2023-10-31 06:50:20.5002023-10-3106:50:20.5006h 50m 20s 500ms06:50:20.500
2024-12-02 16:34:212024-12-0216:34:2116h 34m 21s16:34:21

時間のフィールド#

次のコードは、dt ネームスペースの下にある関数を使って、Datetime 型の列から日付や時間の各フィールドを取り出します。Date 型や Time 型の列にも同じ関数が使えます。

df_times.select(
    year=pl.col('datetime').dt.year(),
    month=pl.col('datetime').dt.month(),
    day=pl.col('datetime').dt.day(),
    hour=pl.col('datetime').dt.hour(),
    minute=pl.col('datetime').dt.minute(),
    second=pl.col('datetime').dt.second(),
    microsecond=pl.col('datetime').dt.microsecond(),
    week=pl.col('datetime').dt.week(),
    weekday=pl.col('datetime').dt.weekday()    
)
shape: (3, 9)
yearmonthdayhourminutesecondmicrosecondweekweekday
i32i8i8i8i8i8i32i8i8
202211411012200000445
2023103165020500000442
20241221634210491

Duration を日数や時間数に変換すると、結果は整数になり、小数部分は切り捨てられます。精度を落とさずに扱いたいときは、一番小さい単位(マイクロ秒など)で取り出してください。

df_times.select(
    pl.col('duration'),
    days=pl.col('duration').dt.total_days(),
    hours=pl.col('duration').dt.total_hours(),
    minutes=pl.col('duration').dt.total_minutes(),
    seconds=pl.col('duration').dt.total_seconds(),
    microseconds=pl.col('duration').dt.total_microseconds()
)
shape: (3, 6)
durationdayshoursminutessecondsmicroseconds
duration[μs]i64i64i64i64i64
1h 10m 12s 200ms017042124212200000
6h 50m 20s 500ms064102462024620500000
16h 34m 21s0169945966159661000000

時間の移動#

日付を月や日の単位でそろえる、丸める、ずらすといった操作は、時系列の前処理でよく使います。ここでは Datetime 列に対する操作をまとめます。

dt.month_start() と dt.month_end() は、その日時が含まれる月の開始日時と終了日時を返します。

df_times.select(    
    'datetime',
    month_start=pl.col('datetime').dt.month_start(),  # 月初の日時
    month_end=pl.col('datetime').dt.month_end(),      # 月末の日時
)
shape: (3, 3)
datetimemonth_startmonth_end
datetime[μs]datetime[μs]datetime[μs]
2022-11-04 01:10:12.2002022-11-01 01:10:12.2002022-11-30 01:10:12.200
2023-10-31 06:50:20.5002023-10-01 06:50:20.5002023-10-31 06:50:20.500
2024-12-02 16:34:212024-12-01 16:34:212024-12-31 16:34:21

dt.truncate() は、指定した単位で日時を切り捨てます。集計のための時間軸づくりに使います。

df_times.select(    
    'datetime', 
    t_1s=pl.col('datetime').dt.truncate('1s'),  # 1秒単位に切り捨て
    t_30s=pl.col('datetime').dt.truncate('30s'), # 30秒単位に切り捨て
    t_1h=pl.col('datetime').dt.truncate('1h'),  # 1時間単位に切り捨て
    t_1d=pl.col('datetime').dt.truncate('1d'),  # 1日単位に切り捨て
    t_1mo=pl.col('datetime').dt.truncate('1mo') # 1ヶ月単位に切り捨て
)
shape: (3, 6)
datetimet_1st_30st_1ht_1dt_1mo
datetime[μs]datetime[μs]datetime[μs]datetime[μs]datetime[μs]datetime[μs]
2022-11-04 01:10:12.2002022-11-04 01:10:122022-11-04 01:10:002022-11-04 01:00:002022-11-04 00:00:002022-11-01 00:00:00
2023-10-31 06:50:20.5002023-10-31 06:50:202023-10-31 06:50:002023-10-31 06:00:002023-10-31 00:00:002023-10-01 00:00:00
2024-12-02 16:34:212024-12-02 16:34:212024-12-02 16:34:002024-12-02 16:00:002024-12-02 00:00:002024-12-01 00:00:00

dt.round() は、指定した単位に丸めます。切り捨てより柔軟で、最も近い単位にそろえたいときに使います。

df_times.select(
    'datetime',
    r_1s=pl.col('datetime').dt.round('1s'),
    r_30s=pl.col('datetime').dt.round('30s'),
    r_1h=pl.col('datetime').dt.round('1h'),
    r_1d=pl.col('datetime').dt.round('1d'),
    r_1mo=pl.col('datetime').dt.round('1mo'),
)
shape: (3, 6)
datetimer_1sr_30sr_1hr_1dr_1mo
datetime[μs]datetime[μs]datetime[μs]datetime[μs]datetime[μs]datetime[μs]
2022-11-04 01:10:12.2002022-11-04 01:10:122022-11-04 01:10:002022-11-04 01:00:002022-11-04 00:00:002022-11-01 00:00:00
2023-10-31 06:50:20.5002023-10-31 06:50:212023-10-31 06:50:302023-10-31 07:00:002023-10-31 00:00:002023-11-01 00:00:00
2024-12-02 16:34:212024-12-02 16:34:212024-12-02 16:34:302024-12-02 17:00:002024-12-03 00:00:002024-12-01 00:00:00

dt.offset_by() は、指定した分だけ日時をずらします。正の値で未来、負の値で過去に移動します。

df_times.select(
    'datetime',
    o_1s=pl.col('datetime').dt.offset_by('1s'),
    o_30s=pl.col('datetime').dt.offset_by('30s'),
    o_1h=pl.col('datetime').dt.offset_by('1h'),
    o_1d=pl.col('datetime').dt.offset_by('1d'),
    o_1mo=pl.col('datetime').dt.offset_by('1mo'),
)
shape: (3, 6)
datetimeo_1so_30so_1ho_1do_1mo
datetime[μs]datetime[μs]datetime[μs]datetime[μs]datetime[μs]datetime[μs]
2022-11-04 01:10:12.2002022-11-04 01:10:13.2002022-11-04 01:10:42.2002022-11-04 02:10:12.2002022-11-05 01:10:12.2002022-12-04 01:10:12.200
2023-10-31 06:50:20.5002023-10-31 06:50:21.5002023-10-31 06:50:50.5002023-10-31 07:50:20.5002023-11-01 06:50:20.5002023-11-30 06:50:20.500
2024-12-02 16:34:212024-12-02 16:34:222024-12-02 16:34:512024-12-02 17:34:212024-12-03 16:34:212025-01-02 16:34:21

リサンプリング#

リサンプリングは、時系列の時間間隔を変える処理です。

  • ダウンサンプリング — 長い間隔にまとめる(1分ごと → 1時間ごと)

  • アップサンプリング — 短い間隔に展開する(1時間ごと → 1分ごと)

group_by_dynamic() を使うとダウンサンプリングできます。次のコードは air_quality_long.csv を読み、場所(location)とパラメータ(parameter)ごとに 8時間単位で集計します。

❶ 並べ替え — location、parameter、date.utc の順に並べ、各グループを時間順にします。
❷ group_by_dynamic() — date.utc を基準に 8時間ごとのグループを作ります。
❸ 集計 — 各グループで value の最小・平均・最大を計算します。

df = pl.read_csv('data/air_quality_long.csv', try_parse_dates=True).drop('city', 'country', 'unit')
df = df.sort('location', 'parameter', 'date.utc') #❶
df_down = df.group_by_dynamic('date.utc', every='8h', group_by=['location', 'parameter']).agg( #❷
    pl.col('value').min().alias('value_min'),  #❸
    pl.col('value').mean().alias('value_mean'),#❸
    pl.col('value').max().alias('value_max')   #❸
)
row(df, df_down)
shape: (5_272, 4)
date.utclocationparametervalue
datetime[μs, UTC]strstrf64
2019-04-09 01:00:00 UTC"BETR801""no2"22.5
2019-04-09 02:00:00 UTC"BETR801""no2"53.5
2019-04-09 03:00:00 UTC"BETR801""no2"54.5
2019-04-09 04:00:00 UTC"BETR801""no2"34.5
2019-04-09 05:00:00 UTC"BETR801""no2"46.5
…………
2019-06-20 20:00:00 UTC"London Westminster""pm25"8.0
2019-06-20 21:00:00 UTC"London Westminster""pm25"8.0
2019-06-20 22:00:00 UTC"London Westminster""pm25"7.0
2019-06-20 23:00:00 UTC"London Westminster""pm25"7.0
2019-06-21 00:00:00 UTC"London Westminster""pm25"7.0
shape: (780, 6)
locationparameterdate.utcvalue_minvalue_meanvalue_max
strstrdatetime[μs, UTC]f64f64f64
"BETR801""no2"2019-04-09 00:00:00 UTC22.542.85714354.5
"BETR801""no2"2019-04-09 08:00:00 UTC27.532.2539.0
"BETR801""no2"2019-04-10 00:00:00 UTC11.512.513.5
"BETR801""no2"2019-04-11 00:00:00 UTC13.513.7514.0
"BETR801""no2"2019-04-12 00:00:00 UTC22.523.7525.0
………………
"London Westminster""pm25"2019-06-19 00:00:00 UTC11.011.011.0
"London Westminster""pm25"2019-06-19 08:00:00 UTC12.012.012.0
"London Westminster""pm25"2019-06-20 08:00:00 UTC8.08.08.0
"London Westminster""pm25"2019-06-20 16:00:00 UTC7.07.758.0
"London Westminster""pm25"2019-06-21 00:00:00 UTC7.07.07.0

upsample() は逆に、短い間隔へ展開します。引数は group_by_dynamic() と同じで、存在しなかった時刻の行は NULL で埋められます。

NULL のままだと扱いにくいので、forward_fill() で直前の値で埋めて連続したデータにすることが多いです。

df_up = df.upsample('date.utc', every='20m', group_by=['location', 'parameter'])
df_up_fill = df_up.with_columns(pl.col('location', 'parameter', 'value').forward_fill())
row(df_up, df_up_fill)
shape: (25_547, 4)
date.utclocationparametervalue
datetime[μs, UTC]strstrf64
2019-04-09 02:00:00 UTC"London Westminster""no2"67.0
2019-04-09 02:20:00 UTC"London Westminster""no2"null
2019-04-09 02:40:00 UTC"London Westminster""no2"null
2019-04-09 03:00:00 UTC"London Westminster""no2"67.0
2019-04-09 03:20:00 UTC"London Westminster""no2"null
…………
2019-06-20 22:40:00 UTC"London Westminster""pm25"null
2019-06-20 23:00:00 UTC"London Westminster""pm25"7.0
2019-06-20 23:20:00 UTC"London Westminster""pm25"null
2019-06-20 23:40:00 UTC"London Westminster""pm25"null
2019-06-21 00:00:00 UTC"London Westminster""pm25"7.0
shape: (25_547, 4)
date.utclocationparametervalue
datetime[μs, UTC]strstrf64
2019-04-09 02:00:00 UTC"London Westminster""no2"67.0
2019-04-09 02:20:00 UTC"London Westminster""no2"67.0
2019-04-09 02:40:00 UTC"London Westminster""no2"67.0
2019-04-09 03:00:00 UTC"London Westminster""no2"67.0
2019-04-09 03:20:00 UTC"London Westminster""no2"67.0
…………
2019-06-20 22:40:00 UTC"London Westminster""pm25"7.0
2019-06-20 23:00:00 UTC"London Westminster""pm25"7.0
2019-06-20 23:20:00 UTC"London Westminster""pm25"7.0
2019-06-20 23:40:00 UTC"London Westminster""pm25"7.0
2019-06-21 00:00:00 UTC"London Westminster""pm25"7.0

他のデータとの変換#

文字列との変換#

CSV から読んだ時間は文字列のままです。Datetime、Date、Time と文字列の相互変換は、前処理で必ず出てきます。

文字列から時間型へ#

pl.read_csv() に try_parse_dates=True を指定すると、日時として解釈できる列が自動的に Datetime 型に変換されます。

例: 次の CSV データでは、start 列が自動的に日時型に変換されます。

%%writefile data/test_time.csv
start,end,category
2024-12-10 10:32:14.200,20241210_103715.1,B
2024-12-11 02:40:02.340,20241211_024628.2,A
2024-12-11 15:40:30.550,20241211_154317.6,A
2024-12-12 05:48:19.000,20241212_054905.9,B
2024-12-12 13:42:35.990,20241212_135252.98,B
Overwriting data/test_time.csv
df = pl.read_csv('data/test_time.csv', try_parse_dates=True)
df
shape: (5, 3)
startendcategory
datetime[μs]strstr
2024-12-10 10:32:14.200"20241210_103715.1""B"
2024-12-11 02:40:02.340"20241211_024628.2""A"
2024-12-11 15:40:30.550"20241211_154317.6""A"
2024-12-12 05:48:19"20241212_054905.9""B"
2024-12-12 13:42:35.990"20241212_135252.98""B"

end 列のように独自形式の文字列は、str.to_datetime() にフォーマットを渡して変換します。フォーマット指定子は chrono の strftime 記法です。

https://docs.rs/chrono/latest/chrono/format/strftime/index.html

df2 = df.with_columns(
    end=pl.col('end').str.to_datetime('%Y%m%d_%H%M%S%.f')
)
row(df.select('end'), df2.select('end'))
shape: (5, 1)
end
str
"20241210_103715.1"
"20241211_024628.2"
"20241211_154317.6"
"20241212_054905.9"
"20241212_135252.98"
shape: (5, 1)
end
datetime[μs]
2024-12-10 10:37:15.100
2024-12-11 02:46:28.200
2024-12-11 15:43:17.600
2024-12-12 05:49:05.900
2024-12-12 13:52:52.980

str.to_date() と str.to_time() を使えば Date 型・Time 型にも変換できます。Duration にしたいときは、いったん Time に変換してからキャストします。

時間型から文字列へ#

時間型のデータを特定のフォーマットの文字列にしたいときは、dt.strftime() を使います。フォーマット指定子で必要な形式を指定します。

df3 = df2.select(
    formatted_start=pl.col('start').dt.strftime('%Y/%m/%d %H:%M:%S')
)

row(df2.select('start'), df3)
shape: (5, 1)
start
datetime[μs]
2024-12-10 10:32:14.200
2024-12-11 02:40:02.340
2024-12-11 15:40:30.550
2024-12-12 05:48:19
2024-12-12 13:42:35.990
shape: (5, 1)
formatted_start
str
"2024/12/10 10:32:14"
"2024/12/11 02:40:02"
"2024/12/11 15:40:30"
"2024/12/12 05:48:19"
"2024/12/12 13:42:35"

Duration を文字列にするには、最小単位の値に直してから pl.format() で組み立てます。表示用の書式を自分で決めたいときに便利です。

次のコードでは、マイクロ秒を秒に換算し、round(3) で小数点以下 3桁に丸めてから埋め込んでいます。丸めずにそのまま書式化すると、385.85999999999996 のような浮動小数点の誤差が表に出ます。

(
    df2.select(duration=pl.col("end") - pl.col("start"))
    .select(
        "duration",
        pl.format(
            "{} secs",
            (pl.col("duration").dt.total_microseconds() / 1e6).round(3),
        ).alias("duration_str"),
    )
)
shape: (5, 2)
durationduration_str
duration[μs]str
5m 900ms"300.9 secs"
6m 25s 860ms"385.86 secs"
2m 47s 50ms"167.05 secs"
46s 900ms"46.9 secs"
10m 16s 990ms"616.99 secs"

Epoch との変換#

Datetime とエポック(1970-01-01 00:00:00 UTC からの経過時間)は相互に変換できます。エポックは秒・ミリ秒・マイクロ秒の単位で扱われることが多いので、単位を明示します。

  1. dt.epoch() — Datetime をエポック時間にする。単位('s'、'ms'、'ns' など)を指定する。

  2. pl.from_epoch() — エポック時間を Datetime にする。こちらも単位を指定する。

df2 = df.select(
    epoch_s=pl.col('start').dt.epoch('s'),
    epoch_ns=pl.col('start').dt.epoch('ns'),
)
df3 = df2.select(
    start_s = pl.from_epoch('epoch_s', 's'),
    start_ns = pl.from_epoch('epoch_ns', 'ns')
)
row(df.select('start'), df2, df3)
shape: (5, 1)
start
datetime[μs]
2024-12-10 10:32:14.200
2024-12-11 02:40:02.340
2024-12-11 15:40:30.550
2024-12-12 05:48:19
2024-12-12 13:42:35.990
shape: (5, 2)
epoch_sepoch_ns
i64i64
17338267341733826734200000000
17338848021733884802340000000
17339316301733931630550000000
17339824991733982499000000000
17340109551734010955990000000
shape: (5, 2)
start_sstart_ns
datetime[μs]datetime[ns]
2024-12-10 10:32:142024-12-10 10:32:14.200
2024-12-11 02:40:022024-12-11 02:40:02.340
2024-12-11 15:40:302024-12-11 15:40:30.550
2024-12-12 05:48:192024-12-12 05:48:19
2024-12-12 13:42:352024-12-12 13:42:35.990