Spark SQL의 DataFrame에서 열 유형을 어떻게 변경합니까?


152

내가 다음과 같은 일을한다고 가정 해보십시오.

val df = sqlContext.load("com.databricks.spark.csv", Map("path" -> "cars.csv", "header" -> "true"))
df.printSchema()

root
 |-- year: string (nullable = true)
 |-- make: string (nullable = true)
 |-- model: string (nullable = true)
 |-- comment: string (nullable = true)
 |-- blank: string (nullable = true)

df.show()
year make  model comment              blank
2012 Tesla S     No comment
1997 Ford  E350  Go get one now th...

그러나 나는 정말로 yearas를 원 했습니다 Int(그리고 아마도 다른 열을 변형시킬 수도 있습니다).

내가 생각해 낼 수 있었던 최선은

df.withColumn("year2", 'year.cast("Int")).select('year2 as 'year, 'make, 'model, 'comment, 'blank)
org.apache.spark.sql.DataFrame = [year: int, make: string, model: string, comment: string, blank: string]

조금 복잡합니다.

저는 R에서 왔고 글을 쓸 수있었습니다. 예 :

df2 <- df %>%
   mutate(year = year %>% as.integer,
          make = make %>% toupper)

Spark / Scala 에서이 작업을 수행하는 더 좋은 방법이 있어야하기 때문에 뭔가 빠진 것 같습니다.


나는 이런 식으로 spark.sql ( "SELECT STRING (NULLIF (column, '')) as column_string")
Eric Bellet

답변:


141

편집 : 최신 버전

spark 2.x부터 사용할 수 있습니다 .withColumn. 여기에서 문서를 확인하십시오.

https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.Dataset@withColumn(colName:String,col:col:org.apache.spark.sql.Column) : org.apache.spark.sql.DataFrame

가장 오래된 답변

Spark 버전 1.4부터 열에 DataType을 사용하여 캐스트 메소드를 적용 할 수 있습니다.

import org.apache.spark.sql.types.IntegerType
val df2 = df.withColumn("yearTmp", df.year.cast(IntegerType))
    .drop("year")
    .withColumnRenamed("yearTmp", "year")

SQL 표현식을 사용하는 경우 다음을 수행 할 수도 있습니다.

val df2 = df.selectExpr("cast(year as int) year", 
                        "make", 
                        "model", 
                        "comment", 
                        "blank")

자세한 내용은 문서를 확인하십시오 : http://spark.apache.org/docs/1.6.0/api/scala/#org.apache.spark.sql.DataFrame


4
왜 Column과 함께 사용한 다음 삭제 했습니까? 원래 열 이름으로 열과 함께 사용하는 것이 쉽지 않습니까?
Ameba Spugnosa

@AmebaSpugnosa 나는 그것을 사용할 때 Spark 이름이 열 이름을 반복하면 충돌이 발생했다고 생각합니다. 만들 때가 아니라 사용할 때.
msemelman

5
열을 삭제하고 이름을 바꿀 필요가 없습니다. 한 줄로 할 수 있습니다df.withColumn("ctr", temp("ctr").cast(DecimalType(decimalPrecision, decimalScale)))
ruhong

1
이 경우 열을 다시 캐스트하기 위해 완전히 새로운 데이터 프레임 사본이 작성됩니까? 뭔가 빠졌습니까? 아니면 무대 뒤에서 최적화가 있습니까?
user1814008

5
에 의해가는 문서Spark 2.x, df.withColumn(..)추가 또는 교체 에 따라 열 colName인수
Y2K-하기 Shubham

89

[편집 : 2016 년 3 월 : 투표 해 주셔서 감사합니다! 정말로,이 내가 기반 솔루션을 생각, 최선의 답변을하지 않습니다 withColumn, withColumnRenamed그리고 castmsemelman에 의해 제시, 마틴 Senne 및 기타] 간단하고 깨끗합니다.

나는 당신의 접근법이 괜찮다고 생각합니다 .Spark DataFrame는 (불변의) 행의 RDD라는 것을 기억하십시오 . 그래서 우리는 열을 실제로 대체 하지 않으며 DataFrame매번 새로운 스키마로 새로운 것을 생성 합니다.

다음 스키마가 포함 된 원본 df가 있다고 가정합니다.

scala> df.printSchema
root
 |-- Year: string (nullable = true)
 |-- Month: string (nullable = true)
 |-- DayofMonth: string (nullable = true)
 |-- DayOfWeek: string (nullable = true)
 |-- DepDelay: string (nullable = true)
 |-- Distance: string (nullable = true)
 |-- CRSDepTime: string (nullable = true)

그리고 일부 UDF는 하나 이상의 열에 정의되어 있습니다.

import org.apache.spark.sql.functions._

val toInt    = udf[Int, String]( _.toInt)
val toDouble = udf[Double, String]( _.toDouble)
val toHour   = udf((t: String) => "%04d".format(t.toInt).take(2).toInt ) 
val days_since_nearest_holidays = udf( 
  (year:String, month:String, dayOfMonth:String) => year.toInt + 27 + month.toInt-12
 )

열 유형을 변경하거나 다른 데이터 프레임에서 새 DataFrame을 작성하면 다음과 같이 작성할 수 있습니다.

val featureDf = df
.withColumn("departureDelay", toDouble(df("DepDelay")))
.withColumn("departureHour",  toHour(df("CRSDepTime")))
.withColumn("dayOfWeek",      toInt(df("DayOfWeek")))              
.withColumn("dayOfMonth",     toInt(df("DayofMonth")))              
.withColumn("month",          toInt(df("Month")))              
.withColumn("distance",       toDouble(df("Distance")))              
.withColumn("nearestHoliday", days_since_nearest_holidays(
              df("Year"), df("Month"), df("DayofMonth"))
            )              
.select("departureDelay", "departureHour", "dayOfWeek", "dayOfMonth", 
        "month", "distance", "nearestHoliday")            

결과는 다음과 같습니다.

scala> df.printSchema
root
 |-- departureDelay: double (nullable = true)
 |-- departureHour: integer (nullable = true)
 |-- dayOfWeek: integer (nullable = true)
 |-- dayOfMonth: integer (nullable = true)
 |-- month: integer (nullable = true)
 |-- distance: double (nullable = true)
 |-- nearestHoliday: integer (nullable = true)

이것은 자신의 솔루션에 매우 가깝습니다. 간단히, 유형 변경 및 기타 변환을 개별적으로 udf val유지하면 코드를 더 읽기 쉽고 재사용 할 수 있습니다.


26
이것은 안전하거나 효율적이지 않습니다. 단일 또는 잘못된 항목이 전체 작업을 중단 하므로 안전하지 않습니다NULL . UDF가 Catalyst에 투명하지 않기 때문에 비효율적 입니다. 복잡한 작업에 UDF를 사용하는 것은 좋지만 기본 유형 캐스팅에 이러한 이유를 사용할 이유는 없습니다. 이것이 우리에게 cast방법 이있는 이유입니다 ( Martin Senne의 답변 참조 ). Catalyst에 투명성을 부여하려면 더 많은 작업이 필요하지만 기본 안전은 단순히 배치 Try하고 Option작동 하는 문제입니다 .
zero323

예를 들어 "05-APR-2015"
dbspace

3
withColumn()섹션을 모든 열을 반복하는 일반 섹션 으로 줄이는 방법이 있습니까?
Boern

고맙습니다 zero323, 이것을 읽었을 때 여기에 udf 솔루션이 충돌하는 이유를 알았습니다. 일부 의견은 SO에 대한 답변보다 낫습니다 :)
Simon Dirmeier

손상된 행을 알 수있는 방법이 있습니까? 캐스팅 중에 잘못된 데이터 유형의 열이있는 레코드를 의미합니다. 캐스트 함수는 이러한 필드를 null로 만듭니다
Etisha

65

는 AS cast작업이 불꽃 사용할 수 Column의 (내가 개인적으로 선호하지 않는 udf'@ 제안한이야 Svend이 시점에서) 방법 :

df.select( df("year").cast(IntegerType).as("year"), ... )

요청 된 유형으로 캐스트 하시겠습니까? 깔끔한 부작용으로, 그런 의미에서 캐스트 / 변환 할 수없는 값이 null됩니다.

이것을 도우미 메소드 로 필요로 하는 경우 다음을 사용하십시오.

object DFHelper{
  def castColumnTo( df: DataFrame, cn: String, tpe: DataType ) : DataFrame = {
    df.withColumn( cn, df(cn).cast(tpe) )
  }
}

다음과 같이 사용됩니다.

import DFHelper._
val df2 = castColumnTo( df, "year", IntegerType )

2
전체 열 무리를 캐스팅하고 이름을 바꿔야하는 경우 진행 방법에 대해 조언 해 주시겠습니까? (50 개의 열이 있고 스칼라에 상당히 익숙하지만, 대량 복제없이 열에 접근하는 가장 좋은 방법이 무엇인지 확실하지 않습니다)? 일부 열은 문자열을 유지하고 일부 열은 Float으로 캐스트해야합니다.
Dmitry Smirnov

문자열을 날짜로 변환하는 방법 (예 : 열에서 "25-APR-2016"및 "20160302")
dbspace

@DmitrySmirnov 답변을 받으셨습니까? 나도 같은 질문이 있습니다. ;)
Evan Zamir

@EvanZamir 불행히도, 나는 다른 단계에서 데이터를 rdd로 사용할 수 있도록 많은 작업을 마쳤습니다. 나는 이것이 요즘 쉬워 졌는지 궁금하다 :)
Dmitry Smirnov

60

먼저 유형을 캐스팅하려면 다음을 수행하십시오.

import org.apache.spark.sql
df.withColumn("year", $"year".cast(sql.types.IntegerType))

동일한 열 이름으로 열이 새 열로 바뀝니다. 단계를 추가하고 삭제할 필요가 없습니다.

둘째 대해, 스칼라R .
RI와 가장 유사한 코드는 다음과 같습니다.

val df2 = df.select(
   df.columns.map {
     case year @ "year" => df(year).cast(IntegerType).as(year)
     case make @ "make" => functions.upper(df(make)).as(make)
     case other         => df(other)
   }: _*
)

코드 길이는 R보다 약간 길지만 그것은 언어의 장황함과 관련이 없습니다. R에서 R은 mutateR 데이터 프레임을위한 특별한 기능이며 Scala에서는 표현력 덕분에 쉽게 ad-hoc 할 수 있습니다.
즉, 언어 디자인은 도메인 언어를 빠르고 쉽게 구축 할 수있을만큼 충분하기 때문에 특정 솔루션을 피해야합니다.


참고 : df.columns놀랍게도 Array[String]대신에 Array[Column], 파이썬 팬더의 데이터 프레임처럼 보이기를 원할 수도 있습니다.


1
pyspark에 해당하는 것을 제공해 주시겠습니까?
Harit Vishwakarma

내 "나이"필드에 대해 "정의 된 시작 시작".withColumn ( "age", $ "age".cast (sql.types.DoubleType))을 받고 있습니다. 어떠한 제안?
BlueDolphin

성능상의 이유로 여러 열에서 이러한 변환을 수행하는 경우 데이터 프레임을 .cache ()해야합니까, 아니면 Spark가이를 최적화하는 데 필요하지 않습니까?
skjagini

가져 오기는 그냥 import org.apache.spark.sql.types._대신 할 수 있습니다 . sql.types.IntegerTypeIntegerType
nessa.gp

17

selectExpr좀 더 깔끔하게 만들 수 있습니다 .

df.selectExpr("cast(year as int) as year", "upper(make) as make",
    "model", "comment", "blank")

14

String에서 Integer로 DataFrame의 데이터 유형을 수정하기위한 Java 코드

df.withColumn("col_name", df.col("col_name").cast(DataTypes.IntegerType))

기존 (문자열 데이터 유형)을 정수로 캐스트합니다.


1
더 없습니다 DataTypes에가 sql.types! 그것은이다 DataType. 또한 가져 오기 IntegerType및 캐스트 할 수 있습니다 .
Ehsan M. Kermani

@ EhsanM.Kermani 실제로 DatyaTypes.IntegerType은 합법적 인 참조입니다.
Cupitor

1
@ 커피 터 DataTypes.IntegerTypeDeveloperAPI 모드 에 있었고 v.2.1.0
Ehsan M. Kermani

이것이 최고의 솔루션입니다!
Simon Dirmeier

8

연도를 문자열에서 int로 변환하려면 CSV 판독기에 다음 옵션을 추가 할 수 있습니다. "inferSchema"-> "true", DataBricks 설명서를 참조하십시오.


5
이 방법은 훌륭하게 작동하지만 독자는 파일의 두 번째 패스를 수행해야합니다.
beefyhalo

@beefyhalo 절대적으로 발견, 그 주위에 방법이 있습니까?
Ayush

6

따라서 이것은 sqlserver와 같은 jdbc 드라이버에 저장하는 데 문제가있는 경우에만 실제로 작동하지만 구문 및 유형과 관련된 오류에 실제로 도움이됩니다.

import org.apache.spark.sql.jdbc.{JdbcDialects, JdbcType, JdbcDialect}
import org.apache.spark.sql.jdbc.JdbcType
val SQLServerDialect = new JdbcDialect {
  override def canHandle(url: String): Boolean = url.startsWith("jdbc:jtds:sqlserver") || url.contains("sqlserver")

  override def getJDBCType(dt: DataType): Option[JdbcType] = dt match {
    case StringType => Some(JdbcType("VARCHAR(5000)", java.sql.Types.VARCHAR))
    case BooleanType => Some(JdbcType("BIT(1)", java.sql.Types.BIT))
    case IntegerType => Some(JdbcType("INTEGER", java.sql.Types.INTEGER))
    case LongType => Some(JdbcType("BIGINT", java.sql.Types.BIGINT))
    case DoubleType => Some(JdbcType("DOUBLE PRECISION", java.sql.Types.DOUBLE))
    case FloatType => Some(JdbcType("REAL", java.sql.Types.REAL))
    case ShortType => Some(JdbcType("INTEGER", java.sql.Types.INTEGER))
    case ByteType => Some(JdbcType("INTEGER", java.sql.Types.INTEGER))
    case BinaryType => Some(JdbcType("BINARY", java.sql.Types.BINARY))
    case TimestampType => Some(JdbcType("DATE", java.sql.Types.DATE))
    case DateType => Some(JdbcType("DATE", java.sql.Types.DATE))
    //      case DecimalType.Fixed(precision, scale) => Some(JdbcType("NUMBER(" + precision + "," + scale + ")", java.sql.Types.NUMERIC))
    case t: DecimalType => Some(JdbcType(s"DECIMAL(${t.precision},${t.scale})", java.sql.Types.DECIMAL))
    case _ => throw new IllegalArgumentException(s"Don't know how to save ${dt.json} to JDBC")
  }
}

JdbcDialects.registerDialect(SQLServerDialect)

Java에서 동일한 코드를 구현하도록 도와 줄 수 있습니까? 및 customJdbcDialect를 DataFrame에 등록하는 방법
abhijitcaps

좋은 점은 Vertica와 동일하지만 스파크 2.1 이후입니다. JDbcUtil 필요한 특정 데이터 유형 만 구현하면됩니다. dialect.getJDBCType (dt) .orElse (getCommonJDBCType (dt)). getOrElse (새로운 IllegalArgumentException 던지기 ($ "dt.simpleString}에 대한 JDBC 유형을 얻을 수 없음"))
Arnon Rodman

6

다섯 개 값과 변환이 포함 된 간단한 데이터 세트 생성 intstring유형 :

val df = spark.range(5).select( col("id").cast("string") )

6

나는 이것이 훨씬 더 읽기 쉽다고 생각한다.

import org.apache.spark.sql.types._
df.withColumn("year", df("year").cast(IntegerType))

그러면 연도 열이 IntegerType임시 열을 만들고 해당 열을 삭제하여으로 변환합니다 . 다른 데이터 유형으로 변환하려는 경우 org.apache.spark.sql.types패키지 내부의 유형을 확인할 수 있습니다 .


5

스파크 1.4.1의 캐스트 방법 인 캐스트 FYI를 사용하도록 제안 된 답변이 깨졌습니다.

예를 들어, bigint로 캐스트 될 때 값이 "8182175552014127960"인 문자열 열이있는 데이터 프레임의 값은 "8182175552014128100"입니다.

    df.show
+-------------------+
|                  a|
+-------------------+
|8182175552014127960|
+-------------------+

    df.selectExpr("cast(a as bigint) a").show
+-------------------+
|                  a|
+-------------------+
|8182175552014128100|
+-------------------+

프로덕션에 bigint 열이 있기 때문에이 버그를 찾기 전에 많은 문제에 직면해야했습니다.


4
psst

2
@msemelman 작은 버그로 인해 프로덕션 환경에서 새로운 스파크 버전으로 업그레이드해야한다는 것은 우스운 일입니다.
sauraI3h

작은 버그로 항상 모든 것을 업그레이드하지 않습니까? :)
시저 솔


4

Spark Sql 2.4.0을 사용하면 다음과 같이 할 수 있습니다.

spark.sql("SELECT STRING(NULLIF(column,'')) as column_string")

3

아래 코드를 사용할 수 있습니다.

df.withColumn("year", df("year").cast(IntegerType))

연도 열을 IntegerType열로 변환 합니다.


2

이 방법은 이전 열을 삭제하고 동일한 값과 새 데이터 유형으로 새 열을 만듭니다. DataFrame을 만들 때 내 원래 데이터 형식은 다음과 같습니다.

root
 |-- id: integer (nullable = true)
 |-- flag1: string (nullable = true)
 |-- flag2: string (nullable = true)
 |-- name: string (nullable = true)
 |-- flag3: string (nullable = true)

이 후 데이터 유형을 변경하기 위해 다음 코드를 실행했습니다.

df=df.withColumnRenamed(<old column name>,<dummy column>) // This was done for both flag1 and flag3
df=df.withColumn(<old column name>,df.col(<dummy column>).cast(<datatype>)).drop(<dummy column>)

이 후 내 결과는 다음과 같습니다.

root
 |-- id: integer (nullable = true)
 |-- flag2: string (nullable = true)
 |-- name: string (nullable = true)
 |-- flag1: boolean (nullable = true)
 |-- flag3: boolean (nullable = true)

여기에 솔루션을 제공해 주시겠습니까?
Ajay Kharade

1

spark sql에서 cast를 사용하여 열의 데이터 유형을 변경할 수 있습니다. table name은 table이며 column1과 column2의 두 열만 있으며 column1 데이터 유형을 변경합니다. ex-spark.sql ( "테이블에서 캐스트 (열 1을 열로 column1 column1NewName, 열 2를 선택하십시오") 대신 데이터 유형을 쓰십시오.


1

이름으로 주어진 수십 개의 열의 이름을 바꾸어야하는 경우 다음 예제는 @dnlbrky의 접근 방식을 사용하여 한 번에 여러 열에 적용합니다.

df.selectExpr(df.columns.map(cn => {
    if (Set("speed", "weight", "height").contains(cn)) s"cast($cn as double) as $cn"
    else if (Set("isActive", "hasDevice").contains(cn)) s"cast($cn as boolean) as $cn"
    else cn
}):_*)

캐스트되지 않은 열은 변경되지 않습니다. 모든 열은 원래 순서대로 유지됩니다.


1

너무 많은 답변과 그다지 철저한 설명

다음 구문은 Spark 2.4에서 Databricks Notebook 사용

from pyspark.sql.functions import *
df = df.withColumn("COL_NAME", to_date(BLDFm["LOAD_DATE"], "MM-dd-yyyy"))

to_date가 spark sql 함수이므로 입력 형식 (내 경우에는 "MM-dd-yyyy")을 지정해야하며 가져 오기는 필수입니다.

또한이 구문을 시도했지만 적절한 캐스트 대신 null이 발생했습니다.

df = df.withColumn("COL_NAME", df["COL_NAME"].cast("Date"))

(참고로 구문 적으로 정확하려면 대괄호와 따옴표를 사용해야했습니다


. ) PS : 구문 정글과 같다는 것을 인정해야하며, 진입 점이 많으며 공식 API 참조에는 적절한 예가 없습니다.


1
구문 정글. 예. 이것이 바로 스파크의 세계입니다.
conner.xyz

1

다른 해결책은 다음과 같습니다.

1) "inferSchema"를 False로 유지

2) 행에서 'Map'기능을 실행하는 동안 'asString'(row.getString ...)을 읽을 수 있습니다.

//Read CSV and create dataset
Dataset<Row> enginesDataSet = sparkSession
            .read()
            .format("com.databricks.spark.csv")
            .option("header", "true")
            .option("inferSchema","false")
            .load(args[0]);

JavaRDD<Box> vertices = enginesDataSet
            .select("BOX","BOX_CD")
            .toJavaRDD()
            .map(new Function<Row, Box>() {
                @Override
                public Box call(Row row) throws Exception {
                    return new Box((String)row.getString(0),(String)row.get(1));
                }
            });


0
    val fact_df = df.select($"data"(30) as "TopicTypeId", $"data"(31) as "TopicId",$"data"(21).cast(FloatType).as( "Data_Value_Std_Err")).rdd
    //Schema to be applied to the table
    val fact_schema = (new StructType).add("TopicTypeId", StringType).add("TopicId", StringType).add("Data_Value_Std_Err", FloatType)

    val fact_table = sqlContext.createDataFrame(fact_df, fact_schema).dropDuplicates()

0

또 다른 방법:

// Generate a simple dataset containing five values and convert int to string type

val df = spark.range(5).select( col("id").cast("string")).withColumnRenamed("id","value")

0

개별 열 이름을 지정하지 않고 특정 유형의 여러 열을 다른 열로 변경하려는 경우

/* Get names of all columns that you want to change type. 
In this example I want to change all columns of type Array to String*/
    val arrColsNames = originalDataFrame.schema.fields.filter(f => f.dataType.isInstanceOf[ArrayType]).map(_.name)

//iterate columns you want to change type and cast to the required type
val updatedDataFrame = arrColsNames.foldLeft(originalDataFrame){(tempDF, colName) => tempDF.withColumn(colName, tempDF.col(colName).cast(DataTypes.StringType))}

//display

updatedDataFrame.show(truncate = false)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.