열, 그룹과 스키마의 기대 조건 참조
아래 연산은 from transforms import expectations as E로 가져온 API를 사용합니다. expression을 만든 뒤 Check에 연결합니다.
논리 연산자
| 표현식 | 통과 조건 |
|---|---|
E.true() | 항상 통과 |
E.false() | 항상 실패 |
E.all(e1, e2, ...) | 모든 하위 조건 통과 |
E.any(e1, e2, ...) | 하위 조건 중 하나 이상 통과 |
E.negate(e1) | 해당 하위 조건 실패 |
열 조건을 E.any()로 묶으면 행마다 조건 중 하나를 만족하는지 검사합니다. 다음은 각 행에서 0 < a < 100이고, b > 100 또는 b < 0인 조건입니다.
from transforms import expectations as E
condition = E.all(
E.col("a").gt(0),
E.col("a").lt(100),
E.any(E.col("b").gt(100), E.col("b").lt(0)),
)
예를 들어 E.any(E.col("c1").lt(0), E.col("c1").gt(10))는 서로 다른 행이 각각 다른 조건을 만족하는 것을 허용합니다.
열과 상수 비교
열 조건은 E.col("열_이름")으로 시작합니다.
| 메서드 | 의미 |
|---|---|
.gt(number_or_string) | 초과 |
.gte(number_or_string) | 이상 |
.lt(number_or_string) | 미만 |
.lte(number_or_string) | 이하 |
.equals(value) | 같은 값 |
.not_equals(value) | 다른 값 |
.non_null() | NULL이 아님 |
.is_null() | NULL임 |
.is_in(a, b, ...) | 허용한 값 중 하나 |
.rlike(pattern) | 정규식 부분 일치 |
.has_type(data_type) | 지정한 자료형 |
.exists() | 해당 이름의 열이 존재, 자료형은 제한하지 않음 |
크기 비교, equals, not_equals, rlike는 NULL 값을 무시하여 통과시킵니다. NULL을 금지하려면 non_null()을 함께 사용합니다. 반면 is_in()은 허용값 목록에 None을 넣지 않으면 NULL에서 실패합니다.
from transforms import expectations as E
age_limit = E.col("age").lt(120)
success_value = E.col("test_column").equals("success")
not_failure = E.col("test_column").not_equals("failure")
nullable_status = E.col("status").is_in("active", "inactive", None)
flight_number = E.col("flight_number").rlike(r"^\D{2}\d{2,4}$")
rlike는 기본적으로 부분 일치이며, 비행 번호 예제는 ^와 $로 전체 문자열을 제한합니다. 조건은 숫자가 아닌 문자 두 개 뒤에 숫자 2~4개입니다.
다른 열과 비교
| 메서드 | 같은 행에서 검사하는 관계 |
|---|---|
E.col("c1").equals_col("c2") | c1 = c2 |
E.col("c1").not_equals_col("c2") | c1 ≠ c2 |
E.col("c1").gt_col("c2") | c1 > c2 |
E.col("c1").gte_col("c2") | c1 ≥ c2 |
E.col("c1").lt_col("c2") | c1 < c2 |
E.col("c1").lte_col("c2") | c1 ≤ c2 |
열 전체의 통계 속성
속성을 만든 뒤 .lt(), .gt(), .equals() 같은 비교를 연결합니다.
| 속성 | 의미 | 예시 |
|---|---|---|
.null_percentage() | NULL 비율 | .lt(0.1)이면 10% 미만 |
.null_count() | NULL 개수 | .gt(100)이면 100개 초과 |
.distinct_count() | 서로 다른 값의 개수 | .equals(5)이면 5개. 0.11.0부터 제공 |
.approx_distinct_count() | 근사 distinct 개수 | .equals(5)로 추정값 비교 |
.sum() | 합계, 숫자 열만 지원 | .equals(5) |
.standard_deviation_sample() | 표본 표준편차, 숫자 열만 지원 | .gt(5) |
.standard_deviation_population() | 모집단 표준편차, 숫자 열만 지원 | .gt(5) |
approx_distinct_count()는 빠른 근사 계산이며 오차의 상대 표준편차가 최대 5%입니다. 각 개별 추정 오차가 반드시 ±5% 안에 있다는 뜻으로 해석하지 않습니다. 정확한 개수와의 일치가 필요하면 distinct_count()를 사용합니다.
from transforms import expectations as E
few_nulls = E.col("myCol").null_percentage().lt(0.1)
many_nulls = E.col("myCol").null_count().gt(100)
five_values = E.col("myCol").distinct_count().equals(5)
approx_five = E.col("myCol").approx_distinct_count().equals(5)
sum_five = E.col("myCol").sum().equals(5)
sample_deviation = E.col("myCol").standard_deviation_sample().gt(5)
population_deviation = E.col("myCol").standard_deviation_population().gt(5)
Timestamp의 최소/최대 속성과 시간 비교는 Timestamp 조건에서 설명합니다.
배열 열
배열에는 아래 배열 전용 조건을 사용합니다. 일반 scalar 조건이 모두 배열에도 적용된다고 가정하지 않습니다.
| 조건 | 의미 |
|---|---|
E.col("array_col").is_in("a", "b", "c") | 각 배열의 원소가 허용한 값으로만 구성 |
E.col("array_col").array_contains("a") | 각 행의 배열에 a가 포함 |
E.col("array_col").size().gt(1) | 배열 길이가 1 초과 |
E.col("array_col").size().equals(2) | 배열 길이가 2 |
그룹별 검사와 전체 행 수
E.group_by("col1", "col2", ...)는 열 조합으로 그룹을 만듭니다. 빈 group_by는 전체 데이터셋 하나를 대상으로 합니다.
from transforms import expectations as E
unique_pairs = E.group_by("col1", "col2").is_unique()
large_groups = E.group_by("col1", "col2").count().gt(100)
small_dataset = E.group_by().count().lt(100)
empty_dataset = E.count().equals(0)
three_values_per_group = E.group_by("col1").col("value_col").distinct_count().equals(3)
few_nulls_per_group = E.group_by("col1").col("value_col").null_percentage().lt(0.5)
is_unique()는 열 조합의 유일성을 검사합니다. 그룹별 속성 검사에는 앞서 설명한 열 통계 속성을 사용할 수 있습니다. 마지막 예제는 각 그룹에서 NULL 비율이 50% 미만인지 검사합니다.
기본 키
E.primary_key("c1")는 해당 열이 NULL이 아니며 유일한지 확인합니다. 여러 열을 전달하면 각 열이 NULL이 아니고 열 조합이 유일해야 합니다.
from transforms import expectations as E
object_key = E.primary_key("object_id")
event_key = E.primary_key("time", "event")
여러 열을 각각 독립적으로 유일하게 만드는 조건은 아닙니다. 예를 들어 같은 time에 여러 event가 있어도 조합이 유일하면 됩니다.
열 자료형과 전체 schema
단일 노드는 계산 엔진과 무관하게 Polars 자료형을, Spark는 Spark SQL 자료형을 사용합니다.
import polars as pl
from transforms import expectations as E
age_type = E.col("age").has_type(pl.Int32)
required_columns = E.schema().contains({
"name": pl.String(),
"int_list": pl.List(pl.Int32),
})
from pyspark.sql import types as T
from transforms import expectations as E
age_type = E.col("age").has_type(T.LongType())
required_columns = E.schema().contains({
"id": T.IntegerType(),
"name": T.StringType(),
})
| Schema 메서드 | 조건 |
|---|---|
.contains(mapping) | 지정한 열과 자료형을 포함. 추가 열은 허용 |
.equals(mapping) | 지정한 열과 자료형에 정확히 일치. 추가 열은 허용하지 않음 |
.is_subset_of(mapping) | 실제 모든 열이 지정한 목록 안에 있어야 함. 지정한 열이 실제로 모두 있을 필요는 없음 |
조건부 검사
E.when(condition, then_condition).otherwise(otherwise_condition)은 첫 조건을 통과한 행에는 then을, 실패한 행에는 otherwise를 적용합니다.
from transforms import expectations as E
conditional = E.when(
E.col("myCol").gt(0),
E.col("myOtherCol").is_in("a"),
).otherwise(
E.col("myOtherCol").is_in("b"),
)
otherwise를 무조건 통과 또는 실패로 두려면 E.true()나 E.false()를 사용합니다. NULL을 무시하는 열 조건을 when에 사용할 때는 그 조건의 NULL 통과 규칙도 함께 고려합니다.
다른 입력에 값이 존재하는지 검사하거나 그 입력과 행 수를 비교하려면 데이터셋 간 조건을 사용합니다.