
本文介绍使用 polars 一次性抽取无放回的多个互斥随机样本,并分组求和,避免数据重复使用,兼顾性能与正确性。
本文介绍使用 polars 一次性抽取无放回的多个互斥随机样本,并分组求和,避免数据重复使用,兼顾性能与正确性。
在统计分析或蒙特卡洛模拟中,我们常需对大规模 DataFrame 多次采样并计算汇总值(如每组样本的和)。但若采用循环调用 .sample()(默认有放回),会导致同一行数据被重复抽中——这违背“样本互斥”(disjunct)的要求,影响统计独立性与结果可靠性。
核心思路:一次性抽足所需总量,再按组切分
Polars 的 .sample() 默认 with_replacement=False(即无放回抽样),因此最高效的方式是:
- 一次性抽取 N_samples × N_logs 行(确保总量足够且不重复);
- 利用 pl.int_range(pl.len()) // N_logs 为每 N_logs 行分配相同组 ID(0, 0, ..., 0, 1, 1, ..., 1, ...);
- 按该组 ID 分组聚合,直接计算每组的 sum()。
✅ 示例代码(推荐写法):
import polars as pl
import numpy as np
df = pl.DataFrame({"a": np.random.random(1000)})
N_samples = 50
N_logs = 20
# 一次性无放回抽样,再分组求和
sums = (
df.sample(N_samples * N_logs)
.with_row_index("group_id") # 更清晰的替代写法(Polars ≥ 0.20.0)
.with_columns((pl.col("group_id") // N_logs).alias("batch"))
.group_by("batch", maintain_order=True)
.agg(pl.col("a").sum().alias("sum_a"))
.get_column("sum_a")
)
print(sums.shape) # (50,)
⚠️ 注意事项:
- 数据量校验:确保 df.height >= N_samples * N_logs,否则 .sample() 会报错(可加 frac=None 显式控制,或捕获 ComputeError);
- 顺序无关性:.sample() 结果随机,但分组逻辑依赖索引顺序,因此无需额外 shuffle;若需严格随机分组,可在 .sample() 后接 .select(pl.all().shuffle());
- 内存友好:全程链式操作,不创建中间 Python 列表,比循环调用快 10× 以上(尤其对百万级数据);
- 扩展性强:只需修改 agg() 内部表达式,即可轻松切换为均值(.mean())、标准差(.std())等其他统计量。
总结:避免循环抽样、拥抱向量化分组,是 Polars 中实现高效互斥采样的最佳实践。











