web-dev-qa-db-ja.com

Spark sql null値を失うことなく爆発する方法

フラット化しようとしているデータフレームがあります。プロセスの一部として、爆発させたいので、配列の列がある場合は、配列の各値を使用して個別の行を作成します。例えば、

id | name | likes
_______________________________
1  | Luke | [baseball, soccer]

になるはずです

id | name | likes
_______________________________
1  | Luke | baseball
1  | Luke | soccer

これは私のコードです

private DataFrame explodeDataFrame(DataFrame df) {
    DataFrame resultDf = df;
    for (StructField field : df.schema().fields()) {
        if (field.dataType() instanceof ArrayType) {
            resultDf = resultDf.withColumn(field.name(), org.Apache.spark.sql.functions.explode(resultDf.col(field.name())));
            resultDf.show();
        }
    }
    return resultDf;
}

問題は、私のデータでは、配列列の一部にヌルが含まれていることです。その場合、行全体が削除されます。したがって、このデータフレーム:

id | name | likes
_______________________________
1  | Luke | [baseball, soccer]
2  | Lucy | null

になる

id | name | likes
_______________________________
1  | Luke | baseball
1  | Luke | soccer

の代わりに

id | name | likes
_______________________________
1  | Luke | baseball
1  | Luke | soccer
2  | Lucy | null

Null行が失われないように配列を分解するにはどうすればよいですか?

私はSpark 1.5.2およびJava 8

27
alexgbelov

Spark 2.2 +

_explode_outer_関数を使用できます:

_import org.Apache.spark.sql.functions.explode_outer

df.withColumn("likes", explode_outer($"likes")).show

// +---+----+--------+
// | id|name|   likes|
// +---+----+--------+
// |  1|Luke|baseball|
// |  1|Luke|  soccer|
// |  2|Lucy|    null|
// +---+----+--------+
_

スパーク<= 2.1

In Scala but Java同等のものはほぼ同一である必要があります(個々の関数をインポートするには_import static_を使用します)。

_import org.Apache.spark.sql.functions.{array, col, explode, lit, when}

val df = Seq(
  (1, "Luke", Some(Array("baseball", "soccer"))),
  (2, "Lucy", None)
).toDF("id", "name", "likes")

df.withColumn("likes", explode(
  when(col("likes").isNotNull, col("likes"))
    // If null explode an array<string> with a single null
    .otherwise(array(lit(null).cast("string")))))
_

ここでの考え方は、基本的にNULLを目的のタイプのarray(NULL)に置き換えることです。複合型(別名structs)の場合、完全なスキーマを提供する必要があります。

_val dfStruct = Seq((1L, Some(Array((1, "a")))), (2L, None)).toDF("x", "y")

val st =  StructType(Seq(
  StructField("_1", IntegerType, false), StructField("_2", StringType, true)
))

dfStruct.withColumn("y", explode(
  when(col("y").isNotNull, col("y"))
    .otherwise(array(lit(null).cast(st)))))
_

または

_dfStruct.withColumn("y", explode(
  when(col("y").isNotNull, col("y"))
    .otherwise(array(lit(null).cast("struct<_1:int,_2:string>")))))
_

配列ColumncontainsNullfalseに設定して作成されている場合、これを最初に変更する必要があります(Spark 2.1)でテスト済み):

_df.withColumn("array_column", $"array_column".cast(ArrayType(SomeType, true)))
_
52
zero323

受け入れられた答えをフォローアップすると、配列要素が複雑な型である場合、手で定義するのが難しい場合があります(たとえば、大きな構造体で)。

自動的に行うために、次のヘルパーメソッドを作成しました。

  def explodeOuter(df: Dataset[Row], columnsToExplode: List[String]) = {
      val arrayFields = df.schema.fields
          .map(field => field.name -> field.dataType)
          .collect { case (name: String, type: ArrayType) => (name, type.asInstanceOf[ArrayType])}
          .toMap

      columnsToExplode.foldLeft(df) { (dataFrame, arrayCol) =>
      dataFrame.withColumn(arrayCol, explode(when(size(col(arrayCol)) =!= 0, col(arrayCol))
        .otherwise(array(lit(null).cast(arrayFields(arrayCol).elementType)))))    
 }
1
nsanglar

explode_outer()関数を使用できます。

1
TopGuys