폴드
폴드 (Folds)
Polars는 sum_horizontal, mean_horizontal, min_horizontal처럼 열들에 걸쳐 계산을 수행하는 많은 표현식을 제공해요. 하지만 이들은 모두 폴드(fold)라고 불리는 일반 알고리즘의 특수한 경우일 뿐이고, Polars는 특화 버전으로 부족할 때 사용자가 직접 폴드를 계산할 수 있는 일반 메커니즘을 제공합니다.
출처: 공식문서
함수 fold로 계산한 폴드는 최대 속도를 위해 전체 열에 대해 연산해요. 데이터 레이아웃을 매우 효율적으로 활용하고 종종 벡터화된 실행을 합니다.
기본 예시 (Basic example)
첫 예시로 함수 fold로 sum_horizontal을 다시 구현해 볼게요.
import operator
import polars as pl
df = pl.DataFrame(
{
"label": ["foo", "bar", "spam"],
"a": [1, 2, 3],
"b": [10, 20, 30],
}
)
result = df.select(
pl.fold(
acc=pl.lit(0),
function=operator.add,
exprs=pl.col("a", "b"),
).alias("sum_fold"),
pl.sum_horizontal(pl.col("a", "b")).alias("sum_horz"),
)
print(result)
shape: (3, 2)
┌──────────┬──────────┐
│ sum_fold ┆ sum_horz │
│ --- ┆ --- │
│ i32 ┆ i64 │
╞══════════╪══════════╡
│ 11 ┆ 11 │
│ 22 ┆ 22 │
│ 33 ┆ 33 │
└──────────┴──────────┘
함수 fold는 function 파라미터로 함수 f를 기대하고, f는 두 인자를 받아야 해요. 첫 번째 인자는 누적 결과(accumulator)로 우리가 0으로 초기화했고, 두 번째 인자는 exprs 파라미터에 나열된 표현식들의 연속적인 값들을 받습니다. 우리 경우에는 두 열 "a"와 "b"이지요.
아래 코드에는 함수 fold가 위에서 하는 일을 나타내는, 명시적인 세 번째 표현식이 포함되어 있어요.
acc = pl.lit(0)
f = operator.add
result = df.select(
f(f(acc, pl.col("a")), pl.col("b")),
pl.fold(acc=acc, function=f, exprs=pl.col("a", "b")).alias("sum_fold"),
)
print(result)
shape: (3, 2)
┌─────────┬──────────┐
│ literal ┆ sum_fold │
│ --- ┆ --- │
│ i64 ┆ i32 │
╞═════════╪══════════╡
│ 11 ┆ 11 │
│ 22 ┆ 22 │
│ 33 ┆ 33 │
└─────────┴──────────┘
Python에서의
fold: 대부분의 프로그래밍 언어에는 Polars의 함수fold가 구현하는 알고리즘을 구현한 고차 함수가 있어요. Polarsfold는 Python의functools.reduce와 매우 비슷합니다.functools.reduce의 강력함에 대해 이 글에서 더 배울 수 있어요.
초기값 acc (The initial value acc)
누적기 acc에 고르는 초기값은, 항상 그런 건 아니지만 보통 적용하려는 연산의 항등원(identity element)이에요. 예를 들어 열들에 걸쳐 곱셈을 하고 싶다면 누적기를 0으로 설정하면 올바른 결과를 얻지 못할 거예요.
result = df.select(
pl.fold(
acc=pl.lit(0),
function=operator.mul,
exprs=pl.col("a", "b"),
).alias("prod"),
)
print(result)
shape: (3, 1)
┌──────┐
│ prod │
│ --- │
│ i32 │
╞══════╡
│ 0 │
│ 0 │
│ 0 │
└──────┘
이를 고치려면 누적기 acc를 1로 설정해야 해요.
result = df.select(
pl.fold(
acc=pl.lit(1),
function=operator.mul,
exprs=pl.col("a", "b"),
).alias("prod"),
)
print(result)
shape: (3, 1)
┌──────┐
│ prod │
│ --- │
│ i32 │
╞══════╡
│ 10 │
│ 40 │
│ 90 │
└──────┘
조건부 (Conditional)
데이터프레임의 모든 열에 조건/조건자(predicate)를 적용하고 싶은 경우, 폴드는 이걸 아주 간결하게 표현하는 방법이에요.
df = pl.DataFrame(
{
"a": [1, 2, 3],
"b": [0, 1, 2],
}
)
result = df.filter(
pl.fold(
acc=pl.lit(True),
function=lambda acc, x: acc & x,
exprs=pl.all() > 1,
)
)
print(result)
shape: (1, 2)
┌─────┬─────┐
│ a ┆ b │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════╪═════╡
│ 3 ┆ 2 │
└─────┴─────┘
위 코드는 모든 열이 1보다 큰 행들을 필터링합니다.
폴드와 문자열 데이터 (Folds and string data)
폴드는 문자열 데이터를 연결하는 데 쓸 수 있어요. 하지만 중간 열의 구체화(materialization) 때문에 이 연산은 제곱 복잡도를 갖습니다. 그래서 이 용도에는 함수 concat_str를 쓰는 것을 권장해요.
df = pl.DataFrame(
{
"a": ["a", "b", "c"],
"b": [1, 2, 3],
}
)
result = df.select(pl.concat_str(["a", "b"]))
print(result)
shape: (3, 1)
┌─────┐
│ a │
│ --- │
│ str │
╞═════╡
│ a1 │
│ b2 │
│ c3 │
└─────┘