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...

しかし、私は本当に望んでいたyearとして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: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
なぜwithColumnの後にドロップを使用したのですか?元の列名でwithColumnを使用する方が簡単ではありませんか?
Ameba Spugnosa

@AmebaSpugnosa使用する頃には、列名が繰り返されているとSparkがクラッシュしました。それらを作成するときではなく、使用するとき。
msemelman

5
列を削除してから名前を変更する必要はありません。1行で実行できますdf.withColumn("ctr", temp("ctr").cast(DecimalType(decimalPrecision, decimalScale)))
ruhong 2017

1
この場合、列を再キャストするためだけに新しいデータフレームコピー全体が作成されますか?何か不足していますか?あるいは、舞台裏でいくつかの最適化がありますか?
user1814008 2017

5
ドキュメントで行くSpark 2.xdf.withColumn(..)することができます追加または交換に応じて、列colNameの引数を
Y2K-shubham

89

[編集:2016年3月:投票をありがとう!しかし、本当に、これは私がベースのソリューションを考えて、最善の答えではないwithColumnwithColumnRenamedcastmsemelmanにより前方に置く、マーティンセンヌなどが]簡単かつきれいです。

あなたのアプローチは大丈夫だと思いますDataFrame。Sparkは行の(不変の)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は1つまたは複数の列で定義されています。

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による回答を参照)。触媒に物事を透明にすると、より多くの作業が必要ですが、基本的な安全性は置くだけの問題であるTryOption仕事に。
zero323 2016年

たとえば「05-APR-2015」のように文字列を日付に変換することに関連するものは何もありませんでした
DB領域

3
withColumn()セクションをすべての列を反復する一般的なセクションに減らす方法はありますか?
Boern、2016年

これを読んでzero323に感謝します。これを読んだとき、ここでudfソリューションがクラッシュする理由を理解しました。いくつかのコメントはSOのいくつかの回答よりも優れています:)
Simon Dirmeier

破損した行、つまりキャスト中に誤ったデータ型の列を含むレコードを知る方法はありますか?キャスト関数がこれらのフィールドをnullにするため
Etisha '24年

65

このcast操作はSparkで使用できるため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列あり、Scalaはかなり新しく、大量の複製を作成せずにそれに取り組む最善の方法はわからない)。一部の列はStringのままであり、一部はFloatにキャストされます。
Dmitry Smirnov

列の「25-APR-2016」や「20160302」などの文字列を日付に変換する方法
DB領域

@DmitrySmirnovあなたは答えを得たことがありますか?同じ質問があります。;)
エヴァンザミール2017年

@EvanZamir残念ながら、そうではありません。他のステップでデータをrddとして使用できるようにするために、さまざまな操作を行いました。これは最近簡単になったのかと思います:)
ドミトリー・スミルノフ2017年

60

まず、型をキャストする場合は、次のようにします。

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

同じ列名の場合、列は新しい列に置き換えられます。ステップを追加および削除する必要はありません。

第二にScalaRについて。
これは、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ではその表現力のおかげで簡単にアドホックにできます。
つまり、特定のソリューションを回避できます。言語設計は、独自のドメイン言語をすばやく簡単に構築するのに十分なためです。


補足:df.columns意外にもArray[String]代わりにArray[Column]、Pythonパンダのデータフレームのように見せたいと思うかもしれません。


1
pysparkに相当するものを教えてもらえますか?
Harit Vishwakarma、2015年

「age」フィールドに「不正な定義の開始」.withColumn( "age"、$ "age" .cast(sql.types.DoubleType))が表示されます。なにか提案を?
BlueDolphin 2017年

パフォーマンス上の理由から多くの列でこれらの変換を行っている場合、データフレームを.cache()する必要がありますか、それともSparkがそれらを最適化するので必要ありませんか?
skjagini

インポートはimport org.apache.spark.sql.types._sql.types.IntegerType単にの代わりに行うことができますIntegerType
nessa.gp

17

あなたはselectExprそれを少しきれいにするために使うことができます:

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

14

DataFrameのデータ型をStringからIntegerに変更するJavaコード

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

それは単に、existing(String datatype)をIntegerにキャストします。


1
何もありませんDataTypesではsql.types!それはですDataType。さらに、インポートIntegerTypeしてキャストするだけです。
Ehsan M. Kermani

@ EhsanM.Kermaniは実際にはDatyaTypes.IntegerTypeが正当な参照です。
キューピトール2017年

1
@Cupitor DataTypes.IntegerTypeにあったDeveloperAPIモードとのv.2.1.0で安定
エサンM. Kermani

これが最善の解決策です!
Simon Dirmeier、2018年

8

年を文字列からintに変換するには、csvリーダーに次のオプションを追加できます: "inferSchema"-> "true"、DataBricksのドキュメントを参照


5
これはうまく機能しますが、問題はリーダーがファイルの2番目のパスを実行する必要があることです
beefyhalo

@beefyhaloは絶対に見つけます、それを回避する方法はありますか?
アユッシュ

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でも同じことをしましたが、spark 2.1以降です。JDbcUtilでは、必要な特定のデータ型のみを実装する必要があります。dialect.getJDBCType(dt).orElse(getCommonJDBCType(dt))。getOrElse(throw new IllegalArgumentException(s "Ca n't get JDBC type for $ {dt.simpleString}"))
Arnon Rodman

6

5つの値を含む単純なデータセットを生成し、タイプに変換intしますstring

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

キャストを使用することを示唆する答え、FYI、spark 1.4.1のキャストメソッドは壊れています。

たとえば、bigintにキャストしたときに「8182175552014127960」という値の文字列列を持つデータフレームの値は「8182175552014128100」になります。

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

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

本番環境にはbigint列があったため、このバグを見つける前に多くの問題に直面する必要がありました。


4
psst、スパークをアップグレード
msemelman

2
@msemelman小さなバグのために、本番環境でSparkの新しいバージョンにアップグレードしなければならないのはばかげています。
sauraI3h

小さなバグのために常にすべてをアップグレードしませんか?:)
シーザーソル



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

スパークSQLでキャストを使用して、列のデータ型を変更できます。テーブル名はtableで、column1とcolumn2の2つの列のみがあり、column1のデータ型が変更されます。ex-spark.sql( "select cast(column1 as Double)column1NewName、column2 from table")doubleの代わりにデータ型を記述します。


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"))

あなたが持っているエントリーフォーマット(私の場合は「MM-dd-yyyy」)を指定する必要があり、to_dateはspark sql関数なのでインポートは必須であることに注意してください

また、この構文を試しましたが、適切なキャストの代わりにnullを取得しました:

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

(ただし、構文的に正しくするには括弧と引用符を使用する必要があったことに注意してください)


PS:これは構文ジャングルのようなものであることを認めなければなりません。エントリポイントには多くの可能な方法があり、公式のAPI参照には適切な例がありません。


1
構文ジャングル。はい。これが現在のSparkの世界です。
conner.xyz

1

別の解決策は次のとおりです。

1) "inferSchema"をFalseのままにします

2)行で「マップ」関数を実行している間、「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.