在处理表格数据的时候,我们常常遇到这样的需求:某一行需要根据某个计数值被复制成多行。比如一条购买记录里商品数量是三,就希望展开为三条明细。Polars作为用Rust编写的高性能DataFrame库,提供了repeat_by和flatten两个表达式方法来优雅地解决这一类行复制问题。这两个方法组合使用,可以在不写循环的情况下完成向量化展开。

repeat_by方法的基本用法
repeat_by是Polars中Expr对象的一个方法,它的作用是:对列中的每一个元素,根据给定的整数表达式重复该元素,生成一个列表(List类型)。例如,如果我们有一列数量qty,对某一列值调用repeat_by('qty'),就会把该列每个值重复qty次,放进一个列表里。这一步并不会增加行数,而是把单行内容变成嵌套结构。
下面用一个简单示例说明。假设我们有一个商品表,包含商品名称和数量,希望把名称按数量复制成列表:
import polars as pl
df = pl.DataFrame({
'item': ['apple', 'banana', 'cherry'],
'qty': [2, 3, 1]
})
# 对item列使用repeat_by,按qty重复
res = df.with_columns(
pl.col('item').repeat_by('qty').alias('item_repeated')
)
print(res)
运行后,item_repeated列是一个列表列,apple对应['apple', 'apple'],banana对应['banana', 'banana', 'banana'],cherry对应['cherry']。可以看到,repeat_by非常直观地完成了“按次数重复”的语义,而且完全在引擎内部完成,没有Python层循环。
用flatten将嵌套列表展开为多行
repeat_by生成的列表仍然停留在单列中,要想真正“复制行”,还需要把列表拆开成独立的行。这时就要用到flatten。flatten是DataFrame或LazyFrame的方法,也可以作为表达式使用,它的功能是消除一层嵌套,将列表中的每个元素变为单独的行,同时保留其他列的对应值。
我们将上一步的结果继续处理:
# 接上面res
expanded = res.select(
pl.col('item_repeated').flatten(),
pl.col('qty')
)
print(expanded)
此时输出已经是一个普通的DataFrame,item_repeated列变成了六行,每行一个水果名称,qty列的值也跟着原本的行对齐保留。如果我们在最开始就用with_columns生成重复列表,然后对整个DataFrame调用flatten,就能一步得到展开后的明细表。实际编码中常写成链式:
detail = df.with_columns(
pl.col('item').repeat_by('qty').alias('item')
).flatten()
print(detail)
这样得到的detail里,item列直接是展开后的多行字符串,原qty列完整保留。相比用df.filter加循环拼接,这种写法既简洁又高效。
处理缺失值与复杂字段
在真实数据里,repeat_by依赖的整数表达式可能包含空值。如果qty列有null,repeat_by会报错或产生未知行为,因此要先处理缺失。通常可以用fill_null赋予默认值,比如填0表示不复制,或填1保证至少一行。
示例代码如下:
df2 = pl.DataFrame({
'item': ['apple', 'banana', None],
'qty': [2, None, 3]
})
safe = df2.with_columns(
pl.col('qty').fill_null(0).alias('qty_safe')
).with_columns(
pl.col('item').repeat_by('qty_safe').alias('item')
).flatten()
print(safe)
这里把qty的null填为0,banana就不会产生复制行。如果原表还有其他字段,如价格、分类,它们都会随着repeat_by生成的列表在flatten时自动广播对齐,不需要我们手动做笛卡尔积或者merge。这是Polars列式存储模型带来的便利:所有列在同一行号上保持长度一致。
性能对比与适用场景
很多初学者会用Python的for循环遍历DataFrame,把每一行append多次,这种写法在十万级以上数据就会非常慢,而且内存占用高。repeat_by加flatten完全在Rust端向量化执行,不仅代码短,而且速度通常快几十倍。我们可以用简单的时间测试感受差异:
import time
big = pl.DataFrame({
'id': pl.arange(0, 100000, dtype=pl.Int32),
'n': pl.Series([1,2,3,4,5]*20000)
})
t0 = time.time()
out = big.with_columns(
pl.col('id').repeat_by('n').alias('id')
).flatten()
print('polars repeat_by:', time.time() - t0)在普通笔记本上,这段展开二十多万行的操作一般只需几十毫秒。它特别适合做数据增强、日志展开、订单明细生成、模拟用户行为等任务。需要注意的是,如果重复次数极大(如某些n为十万),展开后行数会暴涨,要提前估算内存;此时可考虑分块处理或使用LazyFrame的流式执行。
小结
通过repeat_by我们先按指定字段把元素变成列表,再用flatten压平,就完成了高效的行复制。核心要点是:repeat_by接收整数表达式、flatten消除嵌套、缺失值提前fill_null、其他列自动对齐。掌握这套组合,能让你在用Polars做数据变形时避开低效循环,写出更地道的向量化代码。