DataFrame에서 Column 체크

DataFrame에서 Column 체크하는 방법을 설명합니다.

개요

Spark DataFrame에서 컬럼 존재 여부를 확인하는 일은 스키마 검증, 조건 분기, 데이터 품질 체크에서 자주 필요합니다. 이 문서는 일반 컬럼과 중첩 구조 컬럼을 각각 어떻게 확인할 수 있는지 예제 중심으로 정리합니다.

핵심 내용

DataFrame에서 Column 유무 검사

//  Example 1
val schema = StructType( Array(
                 StructField("language", StringType,true),
                 StructField("users", IntegerType,true)
             ))

val rowData= Seq(Row("Java", 20000), 
Row("Python", 100000), 
Row("Scala", 3000))
var df = spark.createDataFrame(rowData,schema)
df.printSchema()

// root
// |-- language: string (nullable = true)
// |-- users: integer (nullable = true)  

Spark DataFrame의 columns attribute는 모든 column 이름을 Array[String] 타입으로 반환합니다. Column의 유무를 check하기 위해 contains() 함수를 이용해서 column의 유무를 확인 할 수 있습니다.

val columnName = "users"
if(df.columns.contains(columnName))
    println("column exists")
else
    println("column not exists")

중첩된 구조의 DataFrame에서 Column 유무 검사

// Example2
[{
    "manufacturer": "hyundai",
    "model":[
        {
            "name" : "2022 Genesis GV70 ",
            "release-date" : "2021. 12. 8",
            "price" : "6,000"
        },
        {
            "name" : "2022 Genesis G90 ",
            "release-date" : "2021. 12. 19",
            "price" : "9,300"
        }
    ]
},
{
   "manufacturer": "bmw",
    "model":[
        {
            "name" : "2023 i7 ",
            "release-date" : "-",
            "price" : "-"
        },
        {
            "name" : "2023 x5 ",
            "release-date" : "2022",
            "price" : "12,420"
        }
        
    ] 
},
{
   "manufacturer": "benz",
    "model":[
        {
            "name" : "2022 AMG GT 4-door coupe ",
            "release-date" : "2022. 3. 25",
            "price" : "16,960"
        },
        {
            "name" : "2022 C Class ",
            "release-date" : "2022. 4. 01",
            "price" : "6,800"
        }
        
    ] 
}]

위와 같은 중첩된 구조의 Json 형식의 데이터를 DataFrame으로 구성했을 경우 Column의 유무를 검색하는 방법을 알아봅니다.

val jsonStr =
    """
      |[{
      |    "manufacturer": "hyundai",
      |    "model":[
      |        {
      |            "name" : "2022 Genesis GV70 ",
      |            "release-date" : "2021. 12. 8",
      |            "price" : "6,000"
      |        },
      |        {
      |            "name" : "2022 Genesis G90 ",
      |            "release-date" : "2021. 12. 19",
      |            "price" : "9,300"
      |        }
      |    ]
      |},
      |{
      |   "manufacturer": "bmw",
      |    "model":[
      |        {
      |            "name" : "2023 i7 ",
      |            "release-date" : "-",
      |            "price" : "-"
      |        },
      |        {
      |            "name" : "2023 x5 ",
      |            "release-date" : "2022",
      |            "price" : "12,420"
      |        }
      |        
      |    ] 
      |},
      |{
      |   "manufacturer": "benz",
      |    "model":[
      |        {
      |            "name" : "2022 AMG GT 4-door coupe ",
      |            "release-date" : "2022. 3. 25",
      |            "price" : "16,960"
      |        },
      |        {
      |            "name" : "2022 C Class ",
      |            "release-date" : "2022. 4. 01",
      |            "price" : "6,800"
      |        }
      |        
      |    ] 
      |}]
      |""".stripMargin
    

val df = sqlContext.read.json(Seq(jsonStr).toDS())
df.printSchema()
df.show(false)
root
 |-- manufacturer: string (nullable = true)
 |-- model: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- name: string (nullable = true)
 |    |    |-- price: string (nullable = true)
 |    |    |-- release-date: string (nullable = true)

+------------+---------------------------------------------------------------------------------------+
|manufacturer|model                                                                                  |
+------------+---------------------------------------------------------------------------------------+
|hyundai     |[[2022 Genesis GV70 , 6,000, 2021. 12. 8], [2022 Genesis G90 , 9,300, 2021. 12. 19]]   |
|bmw         |[[2023 i7 , -, -], [2023 x5 , 12,420, 2022]]                                           |
|benz        |[[2022 AMG GT 4-door coupe , 16,960, 2022. 3. 25], [2022 C Class , 6,800, 2022. 4. 01]]|
+------------+---------------------------------------------------------------------------------------+

df.columns은 중첩된 구조에서 내부 컬럼을 반환하지 않습니다. 그렇기 때문에 DataFrame이 중첩된 구조를 가질 경우 df.schema.simpleString()을 이용하여 중첩 구조의 스키마 컬럼을 검사할 수 있습니다.

df.columns
// Array[String] = Array(manufacturer, model)

df.schema.simpleString()
// String = struct<manufacturer:string,model:array<struct<name:string,price:string,release-date:string>>>

df.schema.simpleString.contains("release-date:")
// true

DataFrame에서 Field 유무 검사

동일한 데이터 유형을 가진 열이 있는지 확인하려면 스파크 스키마 함수 df.schema.fieldNames 또는 df.schema.contain() 함수를 사용합니다.

import org.apache.spark.sql.types.{StringType, StructField}
df.schema.fieldNames.contains("manufacturer")
df.schema.contains(StructField("manufacturer",StringType,true))

실무에서는 입력 스키마가 환경마다 다르거나, 일부 컬럼이 선택적으로 존재하는 경우가 많습니다. 이때 컬럼 존재 여부를 먼저 검사하지 않고 바로 변환 로직을 적용하면 런타임 오류가 발생하기 쉽습니다. 따라서 컬럼 체크는 단순한 편의 기능이 아니라 안정적인 ETL 파이프라인을 만들기 위한 방어 코드로 보는 것이 맞습니다.

특히 중첩 JSON이나 배열 안의 구조체를 다룰 때는 df.columns만 믿고 처리하면 내부 필드를 놓치기 쉽습니다. 이 경우에는 schema 기반 접근으로 실제 스키마 구조를 확인하고, 필요한 경우 재귀적으로 필드를 탐색하는 방식까지 고려해야 합니다. 문서의 예시는 단순 문자열 검사 수준이지만, 운영 코드에서는 공통 유틸 함수로 묶어 두면 반복 작업을 줄일 수 있습니다.

또한 컬럼 존재 여부를 확인할 때는 “컬럼이 있는가"만 볼 것이 아니라 “자료형이 기대와 같은가"도 함께 점검하는 편이 안전합니다. 예를 들어 같은 이름의 컬럼이 있어도 타입이 달라지면 후속 변환 로직이 실패할 수 있기 때문입니다. 따라서 스키마 검사는 존재 여부와 타입 검사를 같이 가져가는 것이 안정적인 패턴입니다.

정리

일반적인 DataFrame에서는 df.columns.contains()로 충분하지만, 중첩 구조에서는 내부 필드가 columns에 직접 드러나지 않기 때문에 schema 기반 접근이 필요합니다. 따라서 단순 컬럼인지 중첩 필드인지 먼저 구분하고 확인 방식을 선택하는 것이 핵심입니다.

작성 수정