문자열
문자열 (Strings)
데이터프레임을 다룰 때 가장 흔하게 만나는 타입 중 하나가 문자열이에요. 이 섹션에서는 Polars의 문자열 데이터를 다루는 함수들을 살펴볼게요. 문자열 처리 함수는 str 네임스페이스에 모여 있습니다.
출처: 공식문서
다른 데이터프레임 라이브러리에서 문자열 작업이 비효율적인 이유는 문자열 길이를 예측할 수 없기 때문이에요. Polars는 Arrow Columnar Format 스펙을 따르는 방식으로 이런 비효율을 줄여서, 문자열 데이터에도 빠른 쿼리를 쓸 수 있게 해 줍니다.
문자열 네임스페이스 (The string namespace)
문자열 데이터를 다룰 때는 str 네임스페이스에 접근할 일이 많을 텐데, 이 네임스페이스에는 문자열을 다루는 40개 이상의 함수가 모여 있어요. 네임스페이스 안의 함수에 접근하는 방법의 예로, 아래 코드는 열 안의 문자열 길이를 바이트 수와 문자 수 두 가지 기준으로 계산해 보여 줍니다.
import polars as pl
df = pl.DataFrame(
{
"language": ["English", "Dutch", "Portuguese", "Finish"],
"fruit": ["pear", "peer", "pêra", "päärynä"],
}
)
result = df.with_columns(
pl.col("fruit").str.len_bytes().alias("byte_count"),
pl.col("fruit").str.len_chars().alias("letter_count"),
)
print(result)
ASCII 문자만 다룬다면 두 계산 결과는 같아요. 그 경우 더 빠른 len_bytes를 쓰는 걸 권장합니다.
문자열 파싱 (Parsing strings)
Polars는 문자열 열의 요소를 확인하고 파싱하는 여러 방법을 제공해요. 주어진 부분 문자열이나 패턴이 존재하는지 확인하고, 그것을 세거나 추출하거나 치환하는 작업이죠. 이제 그중 몇 가지를 살펴볼게요.
패턴 존재 확인 (Check for the existence of a pattern)
문자열 안에 패턴이 있는지 확인하려면 함수 contains를 써요. 기본적으로 contains의 인자는 정규 표현식으로 해석됩니다. 문자 그대로의 부분 문자열을 지정하고 싶다면 literal 파라미터를 True로 설정하세요. 문자열이 어떤 고정 부분 문자열로 시작하는지 또는 끝나는지를 확인하는 특별한 경우에는 각각 starts_with, ends_with 함수를 쓸 수 있어요.
result = df.select(
pl.col("fruit"),
pl.col("fruit").str.starts_with("p").alias("starts_with_p"),
pl.col("fruit").str.contains("p..r").alias("p..r"),
pl.col("fruit").str.contains("e+").alias("e+"),
pl.col("fruit").str.ends_with("r").alias("ends_with_r"),
)
print(result)
정규 표현식 규격 (Regex specification)
Polars는 정규 표현식을 처리할 때 Rust 크레이트 regex에 의존해요. 어떤 기능과 플래그를 지원하는지 보려면 syntax 문서를 참조해야 할 수 있습니다. 특히 Polars가 지원하는 regex의 방식은 Python의 re 모듈과는 다르다는 점을 기억해 두세요.
패턴 추출 (Extract a pattern)
함수 extract는 열의 문자열 값에서 패턴을 추출하게 해 줍니다. extract는 하나 이상의 캡처 그룹을 가진 regex 패턴을 받고, 두 번째 인자로 지정된 캡처 그룹을 추출해요.
df = pl.DataFrame(
{
"urls": [
"http://vote.com/ballon_dor?candidate=messi&ref=polars",
"http://vote.com/ballon_dor?candidat=jorginho&ref=polars",
"http://vote.com/ballon_dor?candidate=ronaldo&ref=polars",
]
}
)
result = df.select(
pl.col("urls").str.extract(r"candidate=(\w+)", group_index=1),
)
print(result)
문자열 안에서 패턴의 모든 등장을 추출하려면 함수 extract_all을 써요. 아래 예시는 한 자리 이상의 숫자와 매칭되는 regex 패턴 (\d+)로 문자열에서 모든 숫자를 추출합니다. extract_all의 결과는 문자열 안에서 매칭된 패턴의 모든 인스턴스를 담은 리스트예요.
df = pl.DataFrame({"text": ["123 bla 45 asd", "xyz 678 910t"]})
result = df.select(
pl.col("text").str.extract_all(r"(\d+)").alias("extracted_nrs"),
)
print(result)
shape: (2, 1)
┌────────────────┐
│ extracted_nrs │
│ --- │
│ list[str] │
╞════════════════╡
│ ["123", "45"] │
│ ["678", "910"] │
└────────────────┘
패턴 치환 (Replace a pattern)
함수 extract와 extract_all과 유사하게, Polars는 replace와 replace_all 함수를 제공해요. 이 함수들은 regex 패턴이나(literal 파라미터를 True로 설정하면) 문자 그대로의 부분 문자열을 받아 지정된 치환을 수행합니다. replace는 최대 한 번만 치환하는 반면, replace_all은 찾은 모든 겹치지 않는 등장을 치환해요.
df = pl.DataFrame({"text": ["123abc", "abc456"]})
result = df.with_columns(
pl.col("text").str.replace(r"\d", "-"),
pl.col("text").str.replace_all(r"\d", "-").alias("text_replace_all"),
)
print(result)
문자열 수정 (Modifying strings)
문자열 연결 (Concatenating strings)
문자열 표현식은 + 연산자로 가로로 연결할 수 있어요. 이는 Expr.add와 같습니다.
df = pl.DataFrame(
{
"first_name": ["Ada", "Grace", "Edsger"],
"last_name": ["Lovelace", "Hopper", "Dijkstra"],
}
)
result = df.with_columns(
full_name=pl.col("first_name") + pl.lit(" ") + pl.col("last_name"),
)
print(result)
대소문자 변환 (Case conversion)
문자열의 대소문자를 바꾸는 것은 흔한 작업인데, Polars는 to_lowercase, to_titlecase, to_uppercase 함수로 이를 바로 지원합니다.
addresses = pl.DataFrame(
{
"addresses": [
"128 PERF st",
"Rust blVD, 158",
"PoLaRs Av, 12",
"1042 Query sq",
]
}
)
addresses = addresses.select(
pl.col("addresses").alias("originals"),
pl.col("addresses").str.to_titlecase(),
pl.col("addresses").str.to_lowercase().alias("lower"),
pl.col("addresses").str.to_uppercase().alias("upper"),
)
print(addresses)
문자열 양끝 문자 제거 (Stripping characters from the ends)
Polars는 str 네임스페이스에 문자열 양끝의 문자를 제거하는 다섯 가지 함수를 제공해요.
| 함수 | 동작 |
|---|---|
strip_chars |
지정된 문자들의 앞·뒤 등장을 제거한다. |
strip_chars_end |
지정된 문자들의 뒤쪽 등장을 제거한다. |
strip_chars_start |
지정된 문자들의 앞쪽 등장을 제거한다. |
strip_prefix |
정확한 부분 문자열 접두사가 있으면 제거한다. |
strip_suffix |
정확한 부분 문자열 접미사가 있으면 제거한다. |
첫 세 함수는 문자열 인자를 **문자들의 집합(set)**으로 해석하는 반면, strip_prefix와 strip_suffix는 문자열 인자를 문자 그대로의 문자열로 해석한다는 차이를 이해하는 게 중요해요.
addr = pl.col("addresses")
chars = ", 0123456789"
result = addresses.select(
addr.str.strip_chars(chars).alias("strip"),
addr.str.strip_chars_end(chars).alias("end"),
addr.str.strip_chars_start(chars).alias("start"),
addr.str.strip_prefix("128 ").alias("prefix"),
addr.str.strip_suffix(", 158").alias("suffix"),
)
print(result)
참고로 strip_chars는 Python의 문자열 메서드 strip과 비슷하고, strip_prefix/strip_suffix는 각각 Python의 removeprefix/removesuffix와 비슷합니다. 인자를 주지 않으면 세 함수(strip_chars, strip_chars_end, strip_chars_start)는 기본적으로 공백을 제거해요.
슬라이싱 (Slicing)
패턴으로 부분 문자열을 추출하는 것 외에도, 지정된 오프셋에서 문자열을 잘라 부분 문자열을 만들 수 있어요. 일반 목적의 슬라이싱 함수는 slice로, 시작 오프셋과 선택적인 길이(length)를 받습니다. 슬라이스 길이를 지정하지 않거나 문자열 끝을 넘어가면 Polars는 문자열을 끝까지 자릅니다. head와 tail은 각각 문자열의 시작과 끝을 자르는 특화 버전이에요.
df = pl.DataFrame(
{
"fruits": ["pear", "mango", "dragonfruit", "passionfruit"],
"n": [1, -1, 4, -4],
}
)
result = df.with_columns(
pl.col("fruits").str.slice(pl.col("n")).alias("slice"),
pl.col("fruits").str.head(pl.col("n")).alias("head"),
pl.col("fruits").str.tail(pl.col("n")).alias("tail"),
)
print(result)
API 문서 (API documentation)
위에서 다룬 예시 외에도 Polars는 다양한 문자열 조작 함수를 제공합니다. 추가 메서드를 살펴보려면 선택한 언어의 Polars API 문서를 참고하면 돼요.