分组
按固定窗口分组
我们可以使用 group_by_dynamic
来将行按天/月/年等进行分组,从而计算时间统计数据。
年平均示例
在以下简单示例中,我们计算 Apple 股票价格的年平均收盘价。我们首先从 CSV 加载数据
df = pl.read_csv("docs/assets/data/apple_stock.csv", try_parse_dates=True)
df = df.sort("Date")
print(df)
let df = CsvReadOptions::default()
.map_parse_options(|parse_options| parse_options.with_try_parse_dates(true))
.try_into_reader_with_file_path(Some("docs/assets/data/apple_stock.csv".into()))
.unwrap()
.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 │
└────────────┴────────┘
信息
日期按升序排序——如果它们没有按此方式排序,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
· 在功能 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
,那么我们将得到以一年为间隔的分组,其中每个分组跨越两年。
如果未指定 period
参数,则它将设置为与 every
参数相等,因此如果 every
参数设置为 1y
,则每个分组也将跨越 1y
。
因为 every 不必等于 period,我们可以以非常灵活的方式创建许多分组。它们可能重叠或在它们之间留下边界。
让我们看看某些参数组合的窗口会是什么样子。让我们从无聊的开始。🥱
- every: 1 天 ->
"1d"
- period: 1 天 ->
"1d"
this creates adjacent windows of the same size
|--|
|--|
|--|
- every: 1 天 ->
"1d"
- period: 2 天 ->
"2d"
these windows have an overlap of 1 day
|----|
|----|
|----|
- every: 2 天 ->
"2d"
- period: 1 天 ->
"1d"
this would leave gaps between the windows
data points that in these gaps will not be a member of any group
|--|
|--|
|--|
truncate
truncate
truncate
参数是一个布尔变量,它决定了输出中每个分组关联的日期时间值。在上面的示例中,第一个数据点是 1981 年 2 月 23 日。如果 truncate = True
(默认值),则年平均值中第一年的日期是 1981 年 1 月 1 日。但是,如果 truncate = False
,则年平均值中第一年的日期是 1981 年 2 月 23 日的第一个数据点的日期。请注意,truncate
仅影响 Date
列中显示的内容,而不影响窗口边界。
在 group_by_dynamic
中使用表达式
我们不限于在分组操作中使用简单的聚合,例如 mean
——我们可以使用 Polars 中可用的全部表达式。
在下面的代码片段中,我们创建了一个 date range
,包含 2021 年的每一天("1d"
),并将其转换为一个 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
· 在功能 dtype-date 上可用 · 在功能 range 上可用 · 在功能 dynamic_group_by 上可用
let time = polars::time::date_range(
"time".into(),
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_column(),
))
},
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".into(),
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
· 在功能 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: (6, 5)
┌────────┬─────────────────────┬─────────────────────┬─────────────────────┬─────┐
│ groups ┆ _lower_boundary ┆ _upper_boundary ┆ time ┆ len │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ datetime[μs] ┆ datetime[μs] ┆ datetime[μs] ┆ u32 │
╞════════╪═════════════════════╪═════════════════════╪═════════════════════╪═════╡
│ 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 │
└────────┴─────────────────────┴─────────────────────┴─────────────────────┴─────┘