時間データ処理#
この章では、時間データの処理に焦点を当て、時間データについての計算、時間ベースのグループ化や集計、時間ウィンドウの操作方法について説明します。
import polars as pl
from helper.jupyter import row
時間のデータ型#
時間データを効率的に扱うために、Polarsでは日付や時刻、時間間隔に関連する4つのデータ型を提供しています。それぞれのデータ型には異なる特徴と用途があり、時間ベースのデータ処理において重要な役割を果たします。
データ型 |
概要 |
最小単位 |
ゼロ点 |
用途 |
例 |
---|---|---|---|---|---|
|
日付を表すデータ型、時刻情報なし |
日 |
Unix epoch (1970-01-01) |
日付のみが重要なデータの管理や処理 |
|
|
1日の中の時刻を表すデータ型 |
ナノ秒(ns) |
00:00:00(1日の開始時刻) |
1日の中の時刻や時間間隔を扱う |
|
|
日付と時刻を組み合わせたデータ型 |
ミリ秒(ms)・マイクロ秒(us)・ナノ秒(ns) |
Unix epoch (1970-01-01 00:00:00) |
時系列データやタイムスタンプの処理 |
|
|
時間の長さや間隔を表すデータ型 |
ミリ秒(ms)・マイクロ秒(us)・ナノ秒(ns) |
- |
2つの時点間の差分や時間間隔の計算 |
|
次の演算式関数で、複数の整数列から時間列に変換することができます。
pl.date()
は、年、月、日を指定してDate
型を作成します。これにより、year
、month
、day
の列を使用して日付データを作成します。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')
)
次のコードでは、複数の数値列を持つデータフレームに対して、pl.date()
、pl.time()
、pl.datetime()
、pl.duration()
を使用し、整数列から時間列への変換を行っています。それぞれの関数は対応する時間データ型の列を生成します。
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
列から日付部分だけを抽出し、Date
型の列を作成します。
❸ dt.time()
でdatetime
列から時刻部分だけを抽出し、Time
型の列を作成します。
❹ cast(pl.Duration)
でtime
列をDuration
型にキャストします。
❺ Duration
型を直接Time
型に変換する関数がないため、一旦列dt.cast_time_unit('ns')
を使用してをナノ秒単位のDuration
型に変換し、to_physical()
でInt64
型に変換後、cast(pl.Time)
で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 |
時間の移動#
時間データの処理では、特定の単位に基づいて日付や時間を移動、丸め、または切り捨てる操作が頻繁に必要になります。このセクションでは、Polarsの便利な時間操作関数を活用し、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分ごとに拡張)。
DataFrame.group_by_dynamic()
を使用すると、時系列データをダウンサンプリングできます。次のコードでは、air_quality_long.csv
ファイルを読み込みます。このファイルには、複数の場所 (location
)、複数のパラメータ (parameter
) の時間データが含まれています。場所とパラメータごとにダウンサンプリングを行うために、以下の手順を実行します。
❶ データの並び替え: まず、location
、parameter
、date.utc
の順にデータを並び替えます。これにより、各グループのデータが時間の昇順に整列されます。
❷ group_by_dynamic()
を適用: date.utc
列を基準に group_by_dynamic()
を使用してダウンサンプリングします。この例では、8時間ごとにグループを作成します。group_by
引数で、時間列のグループ列名を指定します。
❸ 集計処理: 各グループ内で、value
列の最小値、平均値、および最大値を計算します。
このように、group_by_dynamic()
を活用することで、時系列データを効率的に集約できます。
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)
|
DataFrame.upsample()
を使用すると、時系列データをアップサンプリングできます。引数は group_by_dynamic()
と同じですが、出力はアップサンプリングされたデータフレームとなり、もともと存在しない時間のデータはすべて NULL
で埋められます。
一般的には、forward_fill()
を適用して、直前のデータで NULL
を埋めることで、データの連続性を保ちます。
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)
|
他のデータとの変換#
文字列との変換#
時間データの処理では、文字列と日時型を相互に変換する操作が非常に重要です。Polarsでは、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()
を使用して、文字列をDatetime
型に変換できます。フォーマット指定子(例: %Y%m%d_%H%M%S
)を指定することで、適切に変換が行えます。各個フィールドのフォーマットを指定する方法は次のURLに参考してください。
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
型に変換してから、Duration
型にキャストすることができます。
時間型から文字列へ#
時間型データを特定のフォーマットで文字列に変換したい場合は、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()
を使って必要な形式の文字列に整形します。この方法は、カスタムフォーマットを作成したい場合や、単位を変更して表示したい場合に便利です。
以下のコードでは、最小単位の値を適切に計算するために、cast()
メソッドを使って十進数型(Decimal
)にキャストします。これにより、より精度の高い計算が可能になります。
(
df2
.select(duration=pl.col('end') - pl.col('start'))
.select(
'duration',
pl.format('{} secs',
pl.col('duration')
.dt.total_microseconds()
.cast(pl.Decimal) / 1e6)
.alias('duration_str')
)
)
duration | duration_str |
---|---|
duration[μs] | str |
5m 900ms | "300.9000 secs" |
6m 25s 860ms | "385.8600 secs" |
2m 47s 50ms | "167.0500 secs" |
46s 900ms | "46.9000 secs" |
10m 16s 990ms | "616.9900 secs" |
Pythonの時間との変換#
Epochとの変換#
Polars
では、Datetime
型とエポック(1970年1月1日00:00:00 UTCからの経過時間)との相互変換を簡単に行うことができます。エポック時間は秒、ミリ秒、マイクロ秒といった単位で表されることが多く、データ処理や統計解析において広く使用されています。
dt.epoch()
Datetime
型の列をエポック時間に変換します。単位(例: 秒's'
、ナノ秒'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)
|