Skip to content

PySpark Null과 NA 다루기: 실용적인 클리닝 레시피

Updated on

Null을 방치하면 지표 계산, 조인, 내보내기(export)가 모두 깨진다. 반대로 무심코 dropna를 호출하면 중요한 행이 조용히 사라질 수 있다. 팀은 PySpark에서 null을 점검하고, 채우고, 삭제하고, 안전하게 비교하기 위한 명확한 패턴이 필요하다.

PySpark가 제공하는 기본 null 처리 도구들—fillna, dropna, na.replace, coalesce, 그리고 null-safe 비교—을 활용하면 파이프라인을 예측 가능하고 투명하게 유지할 수 있다.

Python Pandas Dataframe에서 No code로 빠르게 Data Visualization을 만들고 싶나요?

PyGWalker는 Visualization 기반 Exploratory Data Analysis를 위한 Python 라이브러리입니다. PyGWalker (opens in a new tab)는 pandas dataframe(및 polars dataframe)을 시각적 탐색을 위한 tableau 대체 User Interface로 전환하여, Jupyter Notebook에서의 데이터 분석과 시각화 워크플로를 단순화해 줍니다.

PyGWalker for Data visualization (opens in a new tab)

빠른 의사결정 가이드

목표API메모
기본값으로 채우기fillna컬럼별 dict 사용; 타입을 맞출 것
듬성듬성한 행 제거dropnahow, thresh, subset 제어
특수 표식(sentinel) 값 치환na.replace플레이스홀더 문자열·숫자 교체
첫 번째 non-null 선택coalesce우선순위에 따라 컬럼 결합
Null-safe 동등 비교<=> 또는 eqNullSafenull을 서로 같은 값으로 취급

준비

from pyspark.sql import SparkSession, functions as F
 
spark = SparkSession.builder.appName("null-handling").getOrCreate()
 
df = spark.createDataFrame(
    [
        (1, "Alice", None, None),
        (2, "Bob", "basic", 0),
        (3, None, "premium", None),
    ],
    "id INT, name STRING, tier STRING, score INT",
)

Null 패턴 점검

null_stats = df.select(
    *[
        F.sum(F.col(c).isNull().cast("int")).alias(f"{c}_nulls")
        for c in df.columns
    ]
)
  • 채우거나(drop/fill) 삭제하기 전에 컬럼별 null 개수를 먼저 확인한다.

결측값 채우기

filled = df.fillna({"name": "unknown", "tier": "basic", "score": 0})
  • dict를 사용해 컬럼별로 지정하면 타입을 정합하게 유지할 수 있다. 숫자 컬럼을 문자열로 채우는 것은 피할 것.
  • 날짜/타임스탬프 컬럼은 도메인에 맞는 기본값(to_date('1970-01-01') 등)을 사용한다.

선택적으로 행 삭제하기

drop_sparse = df.dropna(thresh=3)          # non-null 값이 >=3개인 행만 유지
drop_missing_tier = df.dropna(subset=["tier"])
  • thresh를 사용하면 과도한 삭제를 방지할 수 있다.
  • 유용한 행을 보존하기 위해, 무차별 dropna()보다는 필요한 컬럼을 subset으로 지정하는 것이 좋다.

Sentinel 값(플레이스홀더) 치환

clean_sent = df.na.replace({"N/A": None, "": None}, subset=["tier", "name"])
  • 채우기나 삭제를 수행하기 전에, 플레이스홀더 문자열을 올바른 null로 변환해 둔다.

Coalesce로 첫 번째 non-null 선택

with_fallback = df.withColumn("primary_tier", F.coalesce("tier", F.lit("basic")))
  • coalesce는 인자 목록에서 첫 번째 non-null 값을 반환한다.

Null-safe 비교

matches = df.where(F.expr("tier <=> 'premium'"))
  • <=>(또는 eqNullSafe)는 두 값이 모두 null인 경우를 “같다”로 본다. 일반적인 = 비교는 null 비교 시 null을 반환한다.

이후 단계의 조인 보호하기

  • 조인 키를 채워 넣을지는 비즈니스 규칙에 따라 신중히 결정하고, 그렇지 않다면 null 키는 명시적으로 필터링한다.
  • fact-to-dimension 조인의 경우, 조인 전에 null 키를 삭제할지, 아니면 유지한 채 이후 단계에서 처리할지 사전에 결정해 둔다.

최소한의 클리닝 파이프라인

clean = (
    df
    .na.replace({"N/A": None, "": None})
    .dropna(subset=["name"])  # name은 필수
    .fillna({"tier": "basic", "score": 0})
    .withColumn("tier_clean", F.coalesce("tier", F.lit("basic")))
)

이 순서는 플레이스홀더를 표준화하고, 필수 필드를 보존하며, 비즈니스 기본값으로 채우고, 리포팅을 위한 명확한 fallback 컬럼을 남긴다.