時間データ処理#
時間データは、集計でも可視化でも必ず出てきます。ただし数値列より落とし穴が多く、同じ時刻を Date、Datetime、Time、Duration の 4通りで表せて、精度もナノ秒から年まで幅があります。書式とタイムゾーンの問題も加わります。この章でここを整理しておけば、以降の時系列処理で型変換に詰まることは減ります。
内容は、時間型の基本、型の間の演算、時間軸でのグループ化(リサンプリング)、文字列との相互変換の順です。
import polars as pl
from helper.jupyter import row
時間のデータ型#
Polars には日付・時刻・時間間隔のためのデータ型が 4つあります。用途に合わせて選んでください。
データ型 |
概要 |
最小単位 |
ゼロ点 |
用途 |
例 |
|---|---|---|---|---|---|
|
日付のみ(時刻なし) |
日 |
Unix epoch (1970-01-01) |
日単位の集計 |
|
|
1日の中の時刻 |
ナノ秒(ns) |
00:00:00 |
時刻そのものを扱う |
|
|
日付と時刻の組 |
ミリ秒・マイクロ秒・ナノ秒 |
Unix epoch (1970-01-01 00:00:00) |
タイムスタンプの処理 |
|
|
時間の長さ・間隔 |
ミリ秒・マイクロ秒・ナノ秒 |
- |
2つの時点の差 |
|
整数の列から時間型の列を作るには、次の 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)
|
shape: (3, 4)
|
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'),
)
| datetime | datetime_ms | datetime_ns | duration | duration_ms | duration_ns |
|---|---|---|---|---|---|
| datetime[μs] | datetime[ms] | datetime[ns] | duration[μs] | duration[ms] | duration[ns] |
| 2022-11-04 01:10:12.200 | 2022-11-04 01:10:12.200 | 2022-11-04 01:10:12.200 | 1h 10m 12s 200ms | 1h 10m 12s 200ms | 1h 10m 12s 200ms |
| 2023-10-31 06:50:20.500 | 2023-10-31 06:50:20.500 | 2023-10-31 06:50:20.500 | 6h 50m 20s 500ms | 6h 50m 20s 500ms | 6h 50m 20s 500ms |
| 2024-12-02 16:34:21 | 2024-12-02 16:34:21 | 2024-12-02 16:34:21 | 16h 34m 21s | 16h 34m 21s | 16h 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), #❺
)
| datetime | date | time | duration | time2 |
|---|---|---|---|---|
| datetime[μs] | date | time | duration[μs] | time |
| 2022-11-04 01:10:12.200 | 2022-11-04 | 01:10:12.200 | 1h 10m 12s 200ms | 01:10:12.200 |
| 2023-10-31 06:50:20.500 | 2023-10-31 | 06:50:20.500 | 6h 50m 20s 500ms | 06:50:20.500 |
| 2024-12-02 16:34:21 | 2024-12-02 | 16:34:21 | 16h 34m 21s | 16: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()
)
| year | month | day | hour | minute | second | microsecond | week | weekday |
|---|---|---|---|---|---|---|---|---|
| i32 | i8 | i8 | i8 | i8 | i8 | i32 | i8 | i8 |
| 2022 | 11 | 4 | 1 | 10 | 12 | 200000 | 44 | 5 |
| 2023 | 10 | 31 | 6 | 50 | 20 | 500000 | 44 | 2 |
| 2024 | 12 | 2 | 16 | 34 | 21 | 0 | 49 | 1 |
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()
)
| duration | days | hours | minutes | seconds | microseconds |
|---|---|---|---|---|---|
| duration[μs] | i64 | i64 | i64 | i64 | i64 |
| 1h 10m 12s 200ms | 0 | 1 | 70 | 4212 | 4212200000 |
| 6h 50m 20s 500ms | 0 | 6 | 410 | 24620 | 24620500000 |
| 16h 34m 21s | 0 | 16 | 994 | 59661 | 59661000000 |
時間の移動#
日付を月や日の単位でそろえる、丸める、ずらすといった操作は、時系列の前処理でよく使います。ここでは 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(), # 月末の日時
)
| datetime | month_start | month_end |
|---|---|---|
| datetime[μs] | datetime[μs] | datetime[μs] |
| 2022-11-04 01:10:12.200 | 2022-11-01 01:10:12.200 | 2022-11-30 01:10:12.200 |
| 2023-10-31 06:50:20.500 | 2023-10-01 06:50:20.500 | 2023-10-31 06:50:20.500 |
| 2024-12-02 16:34:21 | 2024-12-01 16:34:21 | 2024-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ヶ月単位に切り捨て
)
| datetime | t_1s | t_30s | t_1h | t_1d | t_1mo |
|---|---|---|---|---|---|
| datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] |
| 2022-11-04 01:10:12.200 | 2022-11-04 01:10:12 | 2022-11-04 01:10:00 | 2022-11-04 01:00:00 | 2022-11-04 00:00:00 | 2022-11-01 00:00:00 |
| 2023-10-31 06:50:20.500 | 2023-10-31 06:50:20 | 2023-10-31 06:50:00 | 2023-10-31 06:00:00 | 2023-10-31 00:00:00 | 2023-10-01 00:00:00 |
| 2024-12-02 16:34:21 | 2024-12-02 16:34:21 | 2024-12-02 16:34:00 | 2024-12-02 16:00:00 | 2024-12-02 00:00:00 | 2024-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'),
)
| datetime | r_1s | r_30s | r_1h | r_1d | r_1mo |
|---|---|---|---|---|---|
| datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] |
| 2022-11-04 01:10:12.200 | 2022-11-04 01:10:12 | 2022-11-04 01:10:00 | 2022-11-04 01:00:00 | 2022-11-04 00:00:00 | 2022-11-01 00:00:00 |
| 2023-10-31 06:50:20.500 | 2023-10-31 06:50:21 | 2023-10-31 06:50:30 | 2023-10-31 07:00:00 | 2023-10-31 00:00:00 | 2023-11-01 00:00:00 |
| 2024-12-02 16:34:21 | 2024-12-02 16:34:21 | 2024-12-02 16:34:30 | 2024-12-02 17:00:00 | 2024-12-03 00:00:00 | 2024-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'),
)
| datetime | o_1s | o_30s | o_1h | o_1d | o_1mo |
|---|---|---|---|---|---|
| datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] | datetime[μs] |
| 2022-11-04 01:10:12.200 | 2022-11-04 01:10:13.200 | 2022-11-04 01:10:42.200 | 2022-11-04 02:10:12.200 | 2022-11-05 01:10:12.200 | 2022-12-04 01:10:12.200 |
| 2023-10-31 06:50:20.500 | 2023-10-31 06:50:21.500 | 2023-10-31 06:50:50.500 | 2023-10-31 07:50:20.500 | 2023-11-01 06:50:20.500 | 2023-11-30 06:50:20.500 |
| 2024-12-02 16:34:21 | 2024-12-02 16:34:22 | 2024-12-02 16:34:51 | 2024-12-02 17:34:21 | 2024-12-03 16:34:21 | 2025-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)
|
shape: (780, 6)
|
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)
|
shape: (25_547, 4)
|
他のデータとの変換#
文字列との変換#
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
| start | end | category |
|---|---|---|
| datetime[μs] | str | str |
| 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)
|
shape: (5, 1)
|
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)
|
shape: (5, 1)
|
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"),
)
)
| duration | duration_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 からの経過時間)は相互に変換できます。エポックは秒・ミリ秒・マイクロ秒の単位で扱われることが多いので、単位を明示します。
dt.epoch()—Datetimeをエポック時間にする。単位('s'、'ms'、'ns'など)を指定する。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)
|
shape: (5, 2)
|
shape: (5, 2)
|