web-dev-qa-db-ja.com

Spark SQLがネストされたwithColumn

複数の列を持つDataFrameがあり、そのうちのいくつかは構造体です。このようなもの

root
 |-- foo: struct (nullable = true)
 |    |-- bar: string (nullable = true)
 |    |-- baz: string (nullable = true)
 |-- abc: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- def: struct (nullable = true)
 |    |    |    |-- a: string (nullable = true)
 |    |    |    |-- b: integer (nullable = true)
 |    |    |    |-- c: string (nullable = true)

UserDefinedFunctionbazを適用してbazbazの関数に置き換えたいのですが、その方法がわかりません。これは望ましい出力の例です(bazintになっていることに注意してください)

root
 |-- foo: struct (nullable = true)
 |    |-- bar: string (nullable = true)
 |    |-- baz: int (nullable = true)
 |-- abc: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- def: struct (nullable = true)
 |    |    |    |-- a: string (nullable = true)
 |    |    |    |-- b: integer (nullable = true)
 |    |    |    |-- c: string (nullable = true)

DataFrame.withColumnはトップレベルの列でのみ機能し、ネストされた列では機能しないようです。この問題にはScalaを使用しています。

誰かがこれを手伝ってくれる?

ありがとう

16
Jon

これは簡単です。ドットを使用してネストされた構造を選択するだけです。 $"foo.baz"

case class Foo(bar:String,baz:String)
case class Record(foo:Foo)

val df = Seq(
   Record(Foo("Hi","There"))
).toDF()


df.printSchema

root
 |-- foo: struct (nullable = true)
 |    |-- bar: string (nullable = true)
 |    |-- baz: string (nullable = true)


val myUDF = udf((s:String) => {
 // do something with s 
  s.toUpperCase
})


df
.withColumn("udfResult",myUDF($"foo.baz"))
.show

+----------+---------+
|       foo|udfResult|
+----------+---------+
|[Hi,There]|    THERE|
+----------+---------+

UDFの結果を既存のstruct fooに追加する場合、つまり次のようにします。

root
 |-- foo: struct (nullable = false)
 |    |-- bar: string (nullable = true)
 |    |-- baz: string (nullable = true)
 |    |-- udfResult: string (nullable = true)

2つのオプションがあります。

withColumn

df
.withColumn("udfResult",myUDF($"foo.baz"))
.withColumn("foo",struct($"foo.*",$"udfResult"))
.drop($"udfResult")

select

df
.select(struct($"foo.*",myUDF($"foo.baz").as("udfResult")).as("foo"))

編集:構造体の既存の属性をUDFからの結果で置き換える:残念ながら、これはnotで機能します。

df
.withColumn("foo.baz",myUDF($"foo.baz")) 

しかし、このように行うことができます:

// get all columns except foo.baz
val structCols = df.select($"foo.*")
    .columns
    .filter(_!="baz")
    .map(name => col("foo."+name))

df.withColumn(
    "foo",
    struct((structCols:+myUDF($"foo.baz").as("baz")):_*)
)
19
Raphael Roth

Raphael Rothはすでに上記の回答で示されているため、struct関数を使用してこれを行うことができます。 Make Structs Easy *ライブラリを使用すると、これを行う簡単な方法があります。ライブラリは、ColumnクラスにwithFieldメソッドを追加します。これにより、DataFrameクラスのwithColumnメソッドが追加/ DataFrame内の列を置き換えます。特定のユースケースでは、次のようなことができます。

import org.Apache.spark.sql.functions._
import com.github.fqaiser94.mse.methods._

// generate some fake data
case class Foo(bar: String, baz: String)
case class Record(foo: Foo, arrayOfFoo: Seq[Foo])

val df = Seq(
   Record(Foo("Hello", "World"), Seq(Foo("Blue", "Red"), Foo("Green", "Yellow")))
).toDF

df.printSchema

// root
//  |-- foo: struct (nullable = true)
//  |    |-- bar: string (nullable = true)
//  |    |-- baz: string (nullable = true)
//  |-- arrayOfFoo: array (nullable = true)
//  |    |-- element: struct (containsNull = true)
//  |    |    |-- bar: string (nullable = true)
//  |    |    |-- baz: string (nullable = true)

df.show(false)

// +--------------+------------------------------+
// |foo           |arrayOfFoo                    |
// +--------------+------------------------------+
// |[Hello, World]|[[Blue, Red], [Green, Yellow]]|
// +--------------+------------------------------+

// example user defined function that capitalizes a given string
val myUdf = udf((s: String) => s.toUpperCase)

// capitalize value of foo.baz
df.withColumn("foo", $"foo".withField("baz", myUdf($"foo.baz"))).show(false)

// +--------------+------------------------------+
// |foo           |arrayOfFoo                    |
// +--------------+------------------------------+
// |[Hello, WORLD]|[[Blue, Red], [Green, Yellow]]|
// +--------------+------------------------------+

配列内にネストされた構造体内にネストされた列を置き換えることについて、フォローアップの質問があることに気づきました。 Make Structs Easy ライブラリによって提供される関数と spark-hofs ライブラリによって提供される関数を次のように組み合わせて、これを行うこともできます。

import za.co.absa.spark.hofs._

// capitalize the value of foo.baz in each element of arrayOfFoo
df.withColumn("arrayOfFoo", transform($"arrayOfFoo", foo => foo.withField("baz", myUdf(foo.getField("baz"))))).show(false)

// +--------------+------------------------------+
// |foo           |arrayOfFoo                    |
// +--------------+------------------------------+
// |[Hello, World]|[[Blue, RED], [Green, YELLOW]]|
// +--------------+------------------------------+

*完全な開示:この回答で参照されている Make Structs Easy ライブラリの作成者です。

1
fqaiser94