web-dev-qa-db-ja.com

Spark SQL DataFrameの列タイプを変更する方法

次のようなことをしているとします。

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

しかし、私は本当にyearIntとして(そしておそらく他のいくつかの列を変換して)欲しいと思いました。

私が思い付くことができる最高のものは

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)

スパーク/スカラでこれを実行するためのより良い方法があるはずなので、私はおそらく何かを見逃しています...

130
kevinykuo

編集:最新バージョン

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。コラム):org.Apache.spark.sql.DataFrame

最も古い答え

Sparkバージョン1.4以降、列にDataTypeを指定してcastメソッドを適用できます。

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

121
msemelman

[編集:2016年3月:投票ありがとうございます!本当に、これは最良の答えではありませんが、msemelman、Martin Senneなどによって提唱されたwithColumnwithColumnRenamedおよびcastに基づくソリューションはよりシンプルでクリーンだと思います。

あなたのアプローチは大丈夫だと思います、Spark DataFrameは行の(不変の)RDDであるため、実際には置換列ではなく、 new 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)

そして、1つまたは複数の列で定義された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 valsとして保持すると、コードがより読みやすく、再利用可能になります。

87
Svend

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 )
58
Martin Senne

まず 、型をキャストしたいのであれば、これ:

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

同じ列名で、列は新しいものと置き換えられます。追加や削除の手順は不要です。

第二 、ScalaとRについて.
これは私が思い付くことができるRに最も似たコードです:

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ではmutateはRデータフレームのための特別な関数ですが、Scalaではあなたはその表現力を容易に与えることができます。 Wordでは、自分のドメイン言語機能を簡単に構築するのに十分な基盤があれば、それほど多くのことをやりたくありません。


サイドノート:df.columnsは驚くべきことにArray[String]の代わりにArray[Column]です、彼らはそれがPythonパンダのデータフレームのように見えたいと思うかもしれません。

38
WeiChing Lin

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

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

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

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

単に既存の(Stringデータ型)をIntegerにキャストします。

9
manishbelsare

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

8
Peter Rose

したがって、これは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)
6
ben jarman

5つの値を含む単純なデータセットを生成し、intstring型に変換します。

val df = spark.range(5).select( col("id").cast("string") )
6
user8106134
df.select($"long_col".cast(IntegerType).as("int_col"))
5
soulmachine

cast、FYI、spark 1.4.1のcastメソッドの使用を示唆する回答は壊れています。

たとえば、bigintにキャストしたときに値 "8182175552014127960"を持つ文字列列を持つデータフレームは、値 "8182175552014128100"を持ちます。

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

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

プロダクションにbigintカラムがあるため、このバグを見つける前に多くの問題に直面しなければなりませんでした。

4
sauraI3h

Spark Sql 2.4.0を使うとそれができます:

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

あなたは以下のコードを使用することができます。

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

これは year columnをIntegerType列に変換します。

2
adarsh

このメソッドは古い列を削除し、同じ値と新しいデータ型を持つ新しい列を作成します。 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)
2
PirateJack

名前で指定された数十の列の名前を変更する必要がある場合、次の例では@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
cubic lettuce
Another solution is as follows:
1) Keep "inferSchema" as False
2) While running 'Map' functions on the row, you can read 'asString' (row.getString...)

<Code>
        //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));
                        }
                    });
</Code>
0
Vibha

Spark sqlでキャストを使用して列のデータ型を変更できます。テーブル名はtableで、column1とcolumn2の2つの列のみがあり、column1のデータ型は変更されます。 ex-spark.sql( "select cast(column1 as Double)column1NewName、table2からcolumn2")ダブルの代わりにあなたのデータ型を書いてください。

0
Tejasvi Sharma