グループ化
固定ウィンドウによるグループ化
group_by_dynamic
を使って、日/月/年などのテンポラル統計を計算することができます。
年間平均の例
以下の簡単な例では、Apple の株価の年間平均終値を計算します。まずはCSVからデータを読み込みます:
df = pl.read_csv("docs/data/apple_stock.csv", try_parse_dates=True)
df = df.sort("Date")
print(df)
let df = CsvReader::from_path("docs/data/apple_stock.csv")
.unwrap()
.with_try_parse_dates(true)
.finish()
.unwrap()
.sort(
["Date"],
SortMultipleOptions::default().with_maintain_order(true),
)?;
println!("{}", &df);
shape: (100, 2)
┌────────────┬────────┐
│ Date ┆ Close │
│ --- ┆ --- │
│ date ┆ f64 │
╞════════════╪════════╡
│ 1981-02-23 ┆ 24.62 │
│ 1981-05-06 ┆ 27.38 │
│ 1981-05-18 ┆ 28.0 │
│ 1981-09-25 ┆ 14.25 │
│ 1982-07-08 ┆ 11.0 │
│ … ┆ … │
│ 2012-05-16 ┆ 546.08 │
│ 2012-12-04 ┆ 575.85 │
│ 2013-07-05 ┆ 417.42 │
│ 2013-11-07 ┆ 512.49 │
│ 2014-02-25 ┆ 522.06 │
└────────────┴────────┘
Info
日付は昇順にソートされている必要があります - そうでない場合、group_by_dynamic
の出力は正しくありません!
年間平均終値を得るには、group_by_dynamic
に以下のように指定します:
Date
列で年単位(1y
)でグループ化するClose
列の平均値を各年について取る
annual_average_df = df.group_by_dynamic("Date", every="1y").agg(pl.col("Close").mean())
df_with_year = annual_average_df.with_columns(pl.col("Date").dt.year().alias("year"))
print(df_with_year)
group_by_dynamic
· Available on feature dynamic_group_by
let annual_average_df = df
.clone()
.lazy()
.group_by_dynamic(
col("Date"),
[],
DynamicGroupOptions {
every: Duration::parse("1y"),
period: Duration::parse("1y"),
offset: Duration::parse("0"),
..Default::default()
},
)
.agg([col("Close").mean()])
.collect()?;
let df_with_year = annual_average_df
.lazy()
.with_columns([col("Date").dt().year().alias("year")])
.collect()?;
println!("{}", &df_with_year);
年間平均終値は以下のようになります:
shape: (34, 3)
┌────────────┬───────────┬──────┐
│ Date ┆ Close ┆ year │
│ --- ┆ --- ┆ --- │
│ date ┆ f64 ┆ i32 │
╞════════════╪═══════════╪══════╡
│ 1981-01-01 ┆ 23.5625 ┆ 1981 │
│ 1982-01-01 ┆ 11.0 ┆ 1982 │
│ 1983-01-01 ┆ 30.543333 ┆ 1983 │
│ 1984-01-01 ┆ 27.583333 ┆ 1984 │
│ 1985-01-01 ┆ 18.166667 ┆ 1985 │
│ … ┆ … ┆ … │
│ 2010-01-01 ┆ 278.265 ┆ 2010 │
│ 2011-01-01 ┆ 368.225 ┆ 2011 │
│ 2012-01-01 ┆ 560.965 ┆ 2012 │
│ 2013-01-01 ┆ 464.955 ┆ 2013 │
│ 2014-01-01 ┆ 522.06 ┆ 2014 │
└────────────┴───────────┴──────┘
group_by_dynamic
のパラメーター
動的ウィンドウは以下のように定義されます:
- every: ウィンドウの間隔を示します
- period: ウィンドウの期間を示します
- offset: ウィンドウの開始をオフセットするのに使用できます
every
の値は、グループの開始頻度を設定します。時間期間の値は柔軟です - 例えば、1y
を2y
に置き換えることで、2年間隔の平均を取ることができます。また、1y
を1y6mo
に置き換えることで、18ヶ月間隔の平均を取ることができます。
period
パラメーターを使って、各グループの時間期間の長さを設定することもできます。例えば、every
パラメーターを1y
に、period
パラメーターを2y
に設定すると、1年間隔でそれぞれ2年間のグループが作成されます。
period
パラメーターが指定されない場合は、every
パラメーターと同じ値に設定されます。つまり、every
パラメーターが1y
に設定されている場合、各グループも1y
のスパンになります。
every_とperiod_が等しくなる必要がないため、非常に柔軟な方法でたくさんのグループを作成できます。グループが重複したり、境界線が空いたりする可能性があります。
いくつかのパラメーター組み合わせでのウィンドウの様子を見てみましょう。退屈な例から始めましょう。🥱
- every: 1 day ->
"1d"
- period: 1 day ->
"1d"
この操作は同じサイズの隣接するウィンドウを作成します
|--|
|--|
|--|
- every: 1 day ->
"1d"
- period: 2 days ->
"2d"
これらのウィンドウには 1 日の重複があります
|----|
|----|
|----|
- every: 2 days ->
"2d"
- period: 1 day ->
"1d"
これではウィンドウの間に隙間ができます
これらの隙間のデータポイントは、どのグループにも属しません
|--|
|--|
|--|
truncate
truncate
パラメーターは、出力の各グループに関連付けられる datetime 値を決定する Boolean 変数です。上記の例では、最初のデータポイントが 1981 年 2 月 23 日です。truncate = True
(デフォルト)の場合、年間平均の最初の年の日付は 1981 年 1 月 1 日になります。一方、truncate = False
の場合、年間平均の最初の年の日付は 1981 年 2 月 23 日の最初のデータポイントの日付になります。truncate
は Date
列に表示される内容にのみ影響し、ウィンドウの境界には影響しません。
group_by_dynamic
での式の使用
グループ化操作では、mean
のような単純な集計だけでなく、Polars で利用可能な全ての式を使用することができます。
以下のスニペットでは、2021 年の 毎日 ("1d"
) の date range
を作成し、これを DataFrame
に変換しています。
その後、group_by_dynamic
で 毎月 ("1mo"
) 始まる動的ウィンドウを作成し、ウィンドウ長を 1
か月に設定しています。これらの動的ウィンドウに一致する値は、そのグループに割り当てられ、強力な式 API を使って集計することができます。
以下の例では、group_by_dynamic
を使って以下を計算しています:
- 月末までの残り日数
- 月の日数
group_by_dynamic
· DataFrame.explode
· date_range
df = (
pl.date_range(
start=date(2021, 1, 1),
end=date(2021, 12, 31),
interval="1d",
eager=True,
)
.alias("time")
.to_frame()
)
out = df.group_by_dynamic("time", every="1mo", period="1mo", closed="left").agg(
pl.col("time").cum_count().reverse().head(3).alias("day/eom"),
((pl.col("time") - pl.col("time").first()).last().dt.total_days() + 1).alias(
"days_in_month"
),
)
print(out)
group_by_dynamic
· DataFrame.explode
· date_range
· Available on feature dynamic_group_by · Available on feature dtype-date · Available on feature range
let time = polars::time::date_range(
"time",
NaiveDate::from_ymd_opt(2021, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
NaiveDate::from_ymd_opt(2021, 12, 31)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
Duration::parse("1d"),
ClosedWindow::Both,
TimeUnit::Milliseconds,
None,
)?
.cast(&DataType::Date)?;
let df = df!(
"time" => time,
)?;
let out = df
.clone()
.lazy()
.group_by_dynamic(
col("time"),
[],
DynamicGroupOptions {
every: Duration::parse("1mo"),
period: Duration::parse("1mo"),
offset: Duration::parse("0"),
closed_window: ClosedWindow::Left,
..Default::default()
},
)
.agg([
col("time")
.cum_count(true) // python example has false
.reverse()
.head(Some(3))
.alias("day/eom"),
((col("time").last() - col("time").first()).map(
// had to use map as .duration().days() is not available
|s| {
Ok(Some(
s.duration()?
.into_iter()
.map(|d| d.map(|v| v / 1000 / 24 / 60 / 60))
.collect::<Int64Chunked>()
.into_series(),
))
},
GetOutput::from_type(DataType::Int64),
) + lit(1))
.alias("days_in_month"),
])
.collect()?;
println!("{}", &out);
shape: (12, 3)
┌────────────┬──────────────┬───────────────┐
│ time ┆ day/eom ┆ days_in_month │
│ --- ┆ --- ┆ --- │
│ date ┆ list[u32] ┆ i64 │
╞════════════╪══════════════╪═══════════════╡
│ 2021-01-01 ┆ [31, 30, 29] ┆ 31 │
│ 2021-02-01 ┆ [28, 27, 26] ┆ 28 │
│ 2021-03-01 ┆ [31, 30, 29] ┆ 31 │
│ 2021-04-01 ┆ [30, 29, 28] ┆ 30 │
│ 2021-05-01 ┆ [31, 30, 29] ┆ 31 │
│ … ┆ … ┆ … │
│ 2021-08-01 ┆ [31, 30, 29] ┆ 31 │
│ 2021-09-01 ┆ [30, 29, 28] ┆ 30 │
│ 2021-10-01 ┆ [31, 30, 29] ┆ 31 │
│ 2021-11-01 ┆ [30, 29, 28] ┆ 30 │
│ 2021-12-01 ┆ [31, 30, 29] ┆ 31 │
└────────────┴──────────────┴───────────────┘
ローリングウィンドウによるグループ化
ローリング操作 rolling
は、group_by
/agg
コンテキストへの別のアクセス方法です。しかし、group_by_dynamic
とは異なり、ウィンドウは every
と period
というパラメーターで固定されるのではありません。rolling
では、ウィンドウは全く固定されていません! index_column
の値によって決まります。
例えば、時間列に {2021-01-06, 2021-01-10}
の値があり、period="5d"
の場合、以下のようなウィンドウが作成されます:
2021-01-01 2021-01-06
|----------|
2021-01-05 2021-01-10
|----------|
ローリンググループ化のウィンドウは常に DataFrame
列の値によって決まるため、グループの数は常に元の DataFrame
と同じになります。
グループ化操作の組み合わせ
ローリングおよびダイナミックなグループ化操作は、通常のグループ化操作と組み合わせることができます。
以下は、ダイナミックなグループ化を使った例です。
df = pl.DataFrame(
{
"time": pl.datetime_range(
start=datetime(2021, 12, 16),
end=datetime(2021, 12, 16, 3),
interval="30m",
eager=True,
),
"groups": ["a", "a", "a", "b", "b", "a", "a"],
}
)
print(df)
let time = polars::time::date_range(
"time",
NaiveDate::from_ymd_opt(2021, 12, 16)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
NaiveDate::from_ymd_opt(2021, 12, 16)
.unwrap()
.and_hms_opt(3, 0, 0)
.unwrap(),
Duration::parse("30m"),
ClosedWindow::Both,
TimeUnit::Milliseconds,
None,
)?;
let df = df!(
"time" => time,
"groups"=> ["a", "a", "a", "b", "b", "a", "a"],
)?;
println!("{}", &df);
shape: (7, 2)
┌─────────────────────┬────────┐
│ time ┆ groups │
│ --- ┆ --- │
│ datetime[μs] ┆ str │
╞═════════════════════╪════════╡
│ 2021-12-16 00:00:00 ┆ a │
│ 2021-12-16 00:30:00 ┆ a │
│ 2021-12-16 01:00:00 ┆ a │
│ 2021-12-16 01:30:00 ┆ b │
│ 2021-12-16 02:00:00 ┆ b │
│ 2021-12-16 02:30:00 ┆ a │
│ 2021-12-16 03:00:00 ┆ a │
└─────────────────────┴────────┘
out = df.group_by_dynamic(
"time",
every="1h",
closed="both",
group_by="groups",
include_boundaries=True,
).agg(pl.len())
print(out)
group_by_dynamic
· Available on feature dynamic_group_by
let out = df
.clone()
.lazy()
.group_by_dynamic(
col("time"),
[col("groups")],
DynamicGroupOptions {
every: Duration::parse("1h"),
period: Duration::parse("1h"),
offset: Duration::parse("0"),
include_boundaries: true,
closed_window: ClosedWindow::Both,
..Default::default()
},
)
.agg([len()])
.collect()?;
println!("{}", &out);
shape: (7, 5)
┌────────┬─────────────────────┬─────────────────────┬─────────────────────┬─────┐
│ groups ┆ _lower_boundary ┆ _upper_boundary ┆ time ┆ len │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ datetime[μs] ┆ datetime[μs] ┆ datetime[μs] ┆ u32 │
╞════════╪═════════════════════╪═════════════════════╪═════════════════════╪═════╡
│ a ┆ 2021-12-15 23:00:00 ┆ 2021-12-16 00:00:00 ┆ 2021-12-15 23:00:00 ┆ 1 │
│ a ┆ 2021-12-16 00:00:00 ┆ 2021-12-16 01:00:00 ┆ 2021-12-16 00:00:00 ┆ 3 │
│ a ┆ 2021-12-16 01:00:00 ┆ 2021-12-16 02:00:00 ┆ 2021-12-16 01:00:00 ┆ 1 │
│ a ┆ 2021-12-16 02:00:00 ┆ 2021-12-16 03:00:00 ┆ 2021-12-16 02:00:00 ┆ 2 │
│ a ┆ 2021-12-16 03:00:00 ┆ 2021-12-16 04:00:00 ┆ 2021-12-16 03:00:00 ┆ 1 │
│ b ┆ 2021-12-16 01:00:00 ┆ 2021-12-16 02:00:00 ┆ 2021-12-16 01:00:00 ┆ 2 │
│ b ┆ 2021-12-16 02:00:00 ┆ 2021-12-16 03:00:00 ┆ 2021-12-16 02:00:00 ┆ 1 │
└────────┴─────────────────────┴─────────────────────┴─────────────────────┴─────┘