Scala 在Spark中创建给定架构的空数组列

Scala 在Spark中创建给定架构的空数组列,scala,apache-spark,Scala,Apache Spark,由于parquet不能使用空数组,所以在编写表之前,我将空数组替换为null。现在,当我阅读表格时,我想做相反的事情: 我有一个具有以下模式的数据帧: |-- id: long (nullable = false) |-- arr: array (nullable = true) | |-- element: struct (containsNull = true) | | |-- x: double (nullable = true) | | |-- y:

由于parquet不能使用空数组,所以在编写表之前,我将空数组替换为null。现在,当我阅读表格时,我想做相反的事情:

我有一个具有以下模式的数据帧:

|-- id: long (nullable = false)
 |-- arr: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- x: double (nullable = true)
 |    |    |-- y: double (nullable = true)
以及以下内容:

+---+-----------+
| id|        arr|
+---+-----------+
|  1|[[1.0,2.0]]|
|  2|       null|
+---+-----------+
我想用一个空数组替换空数组(id=2),即

+---+-----------+
| id|        arr|
+---+-----------+
|  1|[[1.0,2.0]]|
|  2|         []|
+---+-----------+
我试过:

val arrSchema = df.schema(1).dataType

df
.withColumn("arr",when($"arr".isNull,array().cast(arrSchema)).otherwise($"arr"))
.show()
其中:

java.lang.ClassCastException:org.apache.spark.sql.types.NullType$ 无法强制转换为org.apache.spark.sql.types.StructType

编辑:我不想“硬编码”我的数组列的任何模式(至少不是结构的模式),因为这可能因情况而异。我只能在运行时使用
df
中的模式信息

顺便说一下,我使用的是Spark 2.1,因此我不能使用
typedLit

  • 已知外部类型的火花2.2+

    通常,您可以使用
    typedLit
    提供空数组

    import org.apache.spark.sql.functions.typedLit
    
    typedLit(Seq.empty[(Double, Double)])
    
    要为嵌套对象使用特定名称,可以使用case类:

    case class Item(x: Double, y: Double)
    
    typedLit(Seq.empty[Item])
    
    或:

    其中,
    schema
    可以从现有的
    DataFrame
    中提取,并用附加的
    StructType
    进行包装:

    StructType(Seq(
      StructField("arr", df.schema("arr").dataType)
    ))
    

一种方法是使用自定义项:

val arrSchema = df.schema(1).dataType // ArrayType(StructType(StructField(x,DoubleType,true), StructField(y,DoubleType,true)),true)

val emptyArr = udf(() => Seq.empty[Any],arrSchema)

df
.withColumn("arr",when($"arr".isNull,emptyArr()).otherwise($"arr"))
.show()

+---+-----------+
| id|        arr|
+---+-----------+
|  1|[[1.0,2.0]]|
|  2|         []|
+---+-----------+

另一种方法是使用
合并

val df = Seq(
  (Some(1), Some(Array((1.0, 2.0)))),
  (Some(2), None)
).toDF("id", "arr")

df.withColumn("arr", coalesce($"arr", typedLit(Array.empty[(Double, Double)]))).
  show
// +---+-----------+
// | id|        arr|
// +---+-----------+
// |  1|[[1.0,2.0]]|
// |  2|         []|
// +---+-----------+

带有case类的UDF也很有趣:

case class Item(x: Double, y: Double)
val udf_emptyArr = udf(() => Seq[Item]())
df
.withColumn("arr",coalesce($"arr",udf_emptyArr()))
.show()

是否可能只使用df中的模式信息(这将使其更通用),实际上我发现使用UDF@RaphaelRoth整洁。这也仅适用于Spark 2.2+,并且需要硬编码类型信息
val df = Seq(
  (Some(1), Some(Array((1.0, 2.0)))),
  (Some(2), None)
).toDF("id", "arr")

df.withColumn("arr", coalesce($"arr", typedLit(Array.empty[(Double, Double)]))).
  show
// +---+-----------+
// | id|        arr|
// +---+-----------+
// |  1|[[1.0,2.0]]|
// |  2|         []|
// +---+-----------+
case class Item(x: Double, y: Double)
val udf_emptyArr = udf(() => Seq[Item]())
df
.withColumn("arr",coalesce($"arr",udf_emptyArr()))
.show()