← 학습 카테고리

Learn

Spark

38개 모듈 · 현재 20번째

Spark 모듈 20/38 spark-learn-20

null 다루기와 복합 타입(구조체·배열·맵)

Spark: The Definitive Guide (Excerpts, Databricks Preview, 2017) — Chapters 2-6 — Bill Chambers & Matei Zaharia Chapter 6: Working with Nulls in Data, Working with Complex Types 절 (PDF pp.111-119)

이 모듈을 다 읽으면

  • na.drop/na.fill/na.replace의 동작 차이와 drop("any")/drop("all")의 차이를 설명할 수 있다
  • struct, array(split/explode), map 세 복합 타입의 용도와 조작 방법을 구분해 설명할 수 있다
  • explode가 데이터 행 수와 구조에 미치는 영향을 설명할 수 있다

Spark는 빈 데이터를 표현할 때 항상 null을 쓰는 것을 권장하며, na 서브패키지로 null을 명시적으로 drop(제거)하거나 fill(채우기)할 수 있고 replace로 특정 값을 다른 값으로 치환할 수도 있다. 복합 타입은 구조체(struct, DataFrame 안의 DataFrame), 배열(array, split으로 만들고 explode로 행 단위로 펼침), 맵(map, 키-값 쌍)의 세 가지가 있으며 각각 dot 문법이나 인덱싱으로 값에 접근한다.

null을 다루는 이유와 na.drop

빈 값이나 누락된 데이터는 빈 문자열 같은 임의의 값 대신 항상 null로 표현하는 것이 모범 사례다. Spark는 null 값을 다룰 때 더 많은 최적화를 적용할 수 있기 때문이다. DataFrame 스케일에서 null을 다루는 주된 창구는 .na 서브패키지다.

df.na.drop() 또는 df.na.drop("any")는 어느 컬럼이든 하나라도 null인 행을 제거한다. df.na.drop("all")은 모든 값이 null(또는 NaN)인 행만 제거한다. 컬럼을 지정한 서브셋(예: Seq("StockCode", "InvoiceNo"))에 대해서만 이 규칙을 적용할 수도 있다. SQL에서는 컬럼별로 IS NOT NULL 조건을 일일이 걸어야 한다.

핵심 포인트

  • null은 빈 문자열 등 임의 값 대신 사용하는 것이 모범 사례이며 Spark가 이를 최적화에 활용할 수 있다
  • na.drop("any")는 하나라도 null인 행을, na.drop("all")은 전부 null/NaN인 행만 제거하며, 컬럼 서브셋을 지정해 범위를 좁힐 수 있다

na.fill과 na.replace

na.fill은 하나 이상의 컬럼을 특정 값으로 채운다. 문자열 컬럼 전체를 하나의 문자열로 채우거나(df.na.fill("All Null values become this string")), 정수/실수 값으로 채우거나, 컬럼명 배열을 지정해 특정 컬럼들만 채울 수 있다. 컬럼마다 다른 채움 값을 쓰고 싶다면 컬럼명→값 맵(Map/dict)을 na.fill에 전달하면 된다.

na.replace는 null이 아닌 값도 포함해 더 유연하게 값을 치환한다. 가장 흔한 용례는 컬럼 내 특정 값을 현재 값 기준으로 다른 값으로 바꾸는 것이며(예: 빈 문자열을 "UNKNOWN"으로), 유일한 제약은 치환할 값이 원래 값과 같은 타입이어야 한다는 점이다.

핵심 포인트

  • na.fill은 값 하나 또는 컬럼명→값 맵으로 null을 채우며, 컬럼 서브셋을 지정해 범위를 한정할 수 있다
  • na.replace는 null 외의 값도 치환 가능한 더 유연한 기능이며, 치환값은 원래 값과 타입이 같아야 한다

구조체(struct): DataFrame 안의 DataFrame

struct는 여러 컬럼을 괄호로 묶어 하나의 컬럼처럼 취급하는 복합 타입으로, DataFrame 안에 중첩된 또 다른 DataFrame이라고 이해하면 된다. struct("Description", "InvoiceNo").alias("complex")처럼 만들 수 있고, SQL에서는 selectExpr("struct(Description, InvoiceNo) as complex")로 동일하게 만든다.

생성된 struct 컬럼 내부 필드에 접근할 때는 complex.Description처럼 dot 문법을 쓰며, complex.*로 struct 내부의 모든 필드를 최상위 컬럼으로 끌어올릴 수 있다.

핵심 포인트

  • struct는 여러 컬럼을 하나로 묶은 중첩 DataFrame 같은 존재이며, dot 문법(complex.필드명)으로 내부에 접근한다
  • complex.* 로 struct 내부 필드 전체를 상위 레벨 컬럼으로 끌어올릴 수 있다

배열(array): split과 explode

배열을 만드는 대표적 방법은 split(컬럼, 구분자)로, 문자열 컬럼을 구분자 기준으로 쪼개 배열 컬럼을 만든다. 배열 원소는 array_col[0]처럼 파이썬 유사 인덱싱으로 접근할 수 있고, array_contains(배열컬럼, 값)로 특정 값의 포함 여부를 확인할 수 있다.

배열을 행 단위로 펼쳐야 할 때는 explode 함수를 쓴다. explode는 배열을 담은 컬럼을 받아, 배열의 원소 개수만큼 행을 복제하면서(나머지 컬럼 값은 그대로 복제됨) 각 행에 원소 하나씩을 담은 새 컬럼을 만든다. 즉 배열 원소 하나당 행이 하나씩 생기는 구조다.

핵심 포인트

  • split(컬럼, 구분자)로 문자열을 배열 컬럼으로 만들고, array_col[0] 인덱싱이나 array_contains로 원소를 조회/확인한다
  • explode는 배열 컬럼을 원소 개수만큼의 행으로 펼치며, 원래 행의 다른 컬럼 값은 각 새 행에 복제된다

맵(map)

맵은 배열이나 구조체보다 덜 흔하게 쓰이지만 여전히 중요한 복합 타입이다. map 함수로 두 컬럼을 키-값 쌍으로 묶어 맵 컬럼을 만들며(map(col("Description"), col("InvoiceNo"))), 생성된 맵은 complex_map['Description']처럼 키로 값을 조회할 수 있다.

맵도 explode로 펼칠 수 있는데, 이 경우 explode는 맵을 key, value 두 개의 컬럼으로 풀어낸다.

핵심 포인트

  • map(키컬럼, 값컬럼)으로 맵 컬럼을 만들고 map_col['키']로 값을 조회한다
  • 맵에 explode를 적용하면 key, value 두 컬럼으로 펼쳐진다