
本文介绍使用 polars 一次性抽取无放回的批量样本,并按组求和,确保每个数据点仅被使用一次,避免传统循环采样导致的重复问题。
本文介绍使用 polars 一次性抽取无放回的批量样本,并按组求和,确保每个数据点仅被使用一次,避免传统循环采样导致的重复问题。
在统计分析或蒙特卡洛模拟中,常需对大规模 DataFrame 多次随机抽样并计算统计量(如每组样本的和)。若采用循环调用 df.sample()(如原代码所示),默认虽为无放回,但每次调用独立采样,无法保证不同样本间互斥——即同一行可能被多次选中,违背“disjunct”(互不相交)要求。
正确做法是:一次性抽取全部所需数据点,再均匀划分成互斥子组。Polars 提供了高效、向量化的方式实现该逻辑:
import polars as pl
import numpy as np
df = pl.DataFrame({"a": np.random.random(1000)})
N_samples = 50
N_logs = 20 # 每个样本含 20 行
# ✅ 正确:一次性抽取 N_samples × N_logs 行(无放回),再分组求和
sums = (
df.sample(N_samples * N_logs, with_replacement=False) # 显式指定更清晰(默认即 False)
.with_row_index() # 可选:便于理解分组逻辑(实际可用 int_range 替代)
.with_columns(pl.int_range(pl.len()) // N_logs) # 生成组 ID:0,0,...,0, 1,1,...,1, ...
.group_by(pl.col("literal")) # 按组 ID 分组(列名取决于 Polars 版本,推荐用列索引或重命名)
.agg(pl.col("a").sum())
.get_column("a")
)
更简洁且推荐的写法(与答案一致,利用 pl.int_range(pl.len()) 直接构造分组键):
sums = (
df.sample(N_samples * N_logs)
.group_by(pl.int_range(pl.len()) // N_logs)
.sum()
.get_column("a")
)
✅ 关键要点说明:
- sample(n) 默认 with_replacement=False,确保全局唯一抽样;
- pl.int_range(pl.len()) // N_logs 生成形如 [0,0,…,0, 1,1,…,1, …, 49,49,…,49] 的整数序列,长度为 N_samples * N_logs,自然将样本划分为 N_samples 组,每组 N_logs 行;
- group_by(...).sum() 向量化聚合,性能远超 Python 循环 + 多次 sample();
- 最终 .get_column("a") 返回长度为 N_samples 的 Series,每个元素即对应组的 a 列之和。
⚠️ 注意事项:
- 确保 N_samples * N_logs ≤ df.height,否则 sample() 会报错(可加 error_on_insufficient=True 显式控制);
- 若需保留原始行索引或元信息,可在 sample() 后使用 with_row_index() 并在分组中一并保留;
- 该方法本质是随机打乱后切片,等价于 df.sample(...).partition_by(...) 的高效替代。
此方案兼顾正确性、性能与简洁性,是 Polars 生态下处理互斥多样本统计任务的标准实践。











