次のようなことをしているとします。
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 2.x以降、.withColumn
を使用できます。こちらのドキュメントをチェックしてください。
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
[編集:2016年3月:投票ありがとうございます!本当に、これは最良の答えではありませんが、msemelman、Martin Senneなどによって提唱されたwithColumn
、withColumnRenamed
および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 val
sとして保持すると、コードがより読みやすく、再利用可能になります。
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 )
まず 、型をキャストしたいのであれば、これ:
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パンダのデータフレームのように見えたいと思うかもしれません。
あなたはそれを少しきれいにするためにselectExpr
を使うことができます:
df.selectExpr("cast(year as int) as year", "upper(make) as make",
"model", "comment", "blank")
DataFrameのデータ型をStringからIntegerに変更するためのJavaコード
df.withColumn("col_name", df.col("col_name").cast(DataTypes.IntegerType))
単に既存の(Stringデータ型)をIntegerにキャストします。
年を文字列からint型に変換するには、csvリーダーに次のオプションを追加します。 "inferSchema" - > "true"、 DataBricksのドキュメントを参照
したがって、これは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)
5つの値を含む単純なデータセットを生成し、int
をstring
型に変換します。
val df = spark.range(5).select( col("id").cast("string") )
df.select($"long_col".cast(IntegerType).as("int_col"))
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カラムがあるため、このバグを見つける前に多くの問題に直面しなければなりませんでした。
Spark Sql 2.4.0を使うとそれができます:
spark.sql("SELECT STRING(NULLIF(column,'')) as column_string")
あなたは以下のコードを使用することができます。
df.withColumn("year", df("year").cast(IntegerType))
これは year columnをIntegerType
列に変換します。
このメソッドは古い列を削除し、同じ値と新しいデータ型を持つ新しい列を作成します。 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)
名前で指定された数十の列の名前を変更する必要がある場合、次の例では@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
}):_*)
キャストされていない列は変更されません。すべての列は元の順序のままです。
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>
Spark sqlでキャストを使用して列のデータ型を変更できます。テーブル名はtableで、column1とcolumn2の2つの列のみがあり、column1のデータ型は変更されます。 ex-spark.sql( "select cast(column1 as Double)column1NewName、table2からcolumn2")ダブルの代わりにあなたのデータ型を書いてください。