为什么Spark SQL为Float / Double类型将String“null”转换为Object null?

时间:2017-05-12 05:54:13

标签: scala apache-spark apache-spark-sql spark-dataframe

我的数据框包含floatdouble值。

scala> val df = List((Float.NaN, Double.NaN), (1f, 0d)).toDF("x", "y")
df: org.apache.spark.sql.DataFrame = [x: float, y: double]

scala> df.show
+---+---+
|  x|  y|
+---+---+
|NaN|NaN|
|1.0|0.0|
+---+---+

scala> df.printSchema
root
 |-- x: float (nullable = false)
 |-- y: double (nullable = false)

当我用NaN值替换null值时,我在null操作中将fill作为字符串给了地图。

scala> val map = df.columns.map((_, "null")).toMap
map: scala.collection.immutable.Map[String,String] = Map(x -> null, y -> null)

scala> df.na.fill(map).printSchema
root
 |-- x: float (nullable = true)
 |-- y: double (nullable = true)


scala> df.na.fill(map).show
+----+----+
|   x|   y|
+----+----+
|null|null|
| 1.0| 0.0|
+----+----+

我得到了正确的价值。但是我无法理解Spark SQL如何/为什么将null作为字符串转换为null对象?

3 个答案:

答案 0 :(得分:3)

如果您查看fill中的Dataset函数,它会检查数据类型并尝试转换为其列模式的数据类型。如果它可以被转换,那么它将转换,否则返回null。

它不会转换为" null"对象null但如果转换时发生异常则返回null。

val map = df.columns.map((_, "WHATEVER")).toMap

给出

and val map = df.columns.map((_, "9999.99")).toMap

给出9999.99

如果要更新具有相同数据类型的NAN,可以按预期获得结果。

希望这有助于您理解!

答案 1 :(得分:1)

不是" null"作为String转换为null对象。您可以尝试使用任何String的转换并仍然获得null(除了可以直接转换为double / float的字符串,见下文)。例如,使用

val map = df.columns.map((_, "abc")).toMap

会给出相同的结果。我的猜测是,由于列的类型为floatdoubleNaN值转换为字符串,因此会null。使用数字可以按预期工作,例如

val map = df.columns.map((_, 1)).toMap

由于某些字符串可以直接转换为doublefloat,因此在这种情况下也可以使用这些字符串。

val map = df.columns.map((_, "1")).toMap

答案 2 :(得分:1)

我已查看了源代码,在fill中,您的字符串被转换为double / float:

private def fillCol[T](col: StructField, replacement: T): Column = {
    col.dataType match {
      case DoubleType | FloatType =>
        coalesce(nanvl(df.col("`" + col.name + "`"), lit(null)),
          lit(replacement).cast(col.dataType)).as(col.name)
      case _ =>
        coalesce(df.col("`" + col.name + "`"), lit(replacement).cast(col.dataType)).as(col.name)
    }
  }

此处的相关源代码(Floats代码类似):

Cast.scala(取自Spark 1.6.3):

  // DoubleConverter
  private[this] def castToDouble(from: DataType): Any => Any = from match {
    case StringType =>
      buildCast[UTF8String](_, s => try s.toString.toDouble catch {
        case _: NumberFormatException => null
      })
    case BooleanType =>
      buildCast[Boolean](_, b => if (b) 1d else 0d)
    case DateType =>
      buildCast[Int](_, d => null)
    case TimestampType =>
      buildCast[Long](_, t => timestampToDouble(t))
    case x: NumericType =>
      b => x.numeric.asInstanceOf[Numeric[Any]].toDouble(b)
  }

所以Spark尝试将String转换为Doubles.toString.toDouble),如果那是不可能的(即你获得NumberFormatException),你得到{{1 }}。因此,您可以使用null而不是"null"而不是"foo"。但是,如果您在地图中使用"1.0",则NaNsnulls将替换为1.0,因为String "1.0"确实可解析为Double