【问题标题】:Scala Spark handles Double.NaN differently in dataframe and datasetScala Spark 在数据帧和数据集中以不同方式处理 Double.NaN
【发布时间】:2021-01-01 23:12:09
【问题描述】:

在测试中,我正在尝试将数据帧/数据集转换为集合并进行比较。例如

actualResult.collect.toSet should be(expectedResult.collect.toSet)

我注意到一些关于 Double.NaN 价值的事实。

  1. 在 Scala 中,Double.NaN == Double.NaN 返回 false。
  2. 在火花中NaN == NaN 是真的。 (offical doc)

但我无法弄清楚为什么数据框和数据集的行为不同

import org.apache.spark.sql.SparkSession

object Main extends App {
  val spark = SparkSession.builder().appName("Example").master("local").getOrCreate()
  import spark.implicits._

  val dataSet = spark.createDataset(Seq(Book("book 1", Double.NaN)))

  // Compare Set(Book(book 1,NaN)) to itself
  println(dataSet.collect.toSet == dataSet.collect.toSet) //false, why?

  // Compare Set([book 1,NaN]) to itself
  println(dataSet.toDF().collect.toSet == dataSet.toDF().collect.toSet) //true, why?
}

case class Book (title: String, price: Double)

这是我的问题。欣赏任何见解。

  1. 它在代码中是如何发生的? (equals 在哪里被覆盖?等等)
  2. 这种设计背后有什么原因吗?是否有更好的范例来在测试中断言数据集/数据框?

【问题讨论】:

    标签: scala dataframe apache-spark dataset nan


    【解决方案1】:

    关于这个话题,我有几点想分享。

    1. 当您执行 dataSet.collect.toSet 时,您将其收集为 Set[Book],并且当您在两组图书对象之间进行比较时。

    单个(书)对象相等方法用于您在Book Case类中定义的比较。 这就是为什么 println(dataSet.collect.toSet == dataSet.collect.toSet) 返回 false 的原因,因为 Double.NaN == Double.NaN returns false

    1. 当您执行dataSet.toDF().collect.toSet 时,您将其收集为Set[Row]

    当您执行 toDF 时,spark 将转换**(即序列化 Book 然后反序列化为 javaType 字段 Row)** Book 类为 Row在此过程中,它还使用RowEncoders 对字段进行一些转换。

    使用 RowEncoder.scala 中的以下代码将所有 Primitive 字段转换为 java 类型

    def apply(schema: StructType): ExpressionEncoder[Row] = {
        val cls = classOf[Row]
        **val inputObject = BoundReference(0, ObjectType(cls), nullable = true)
        val serializer = serializerFor(AssertNotNull(inputObject, Seq("top level row object")), schema)
        val deserializer = deserializerFor(schema)**
        new ExpressionEncoder[Row](
          schema,
          flat = false,
          serializer.asInstanceOf[CreateNamedStruct].flatten,
          deserializer,
          ClassTag(cls))
      }
    

    如果你查看Double.java和Float.java相等方法的源代码。 NAN 的比较将返回 true。这就是 Row 对象比较将返回 true 的原因。 println(dataSet.toDF().collect.toSet == dataSet.toDF().collect.toSet) 是真的。

    <li>If {@code d1} and {@code d2} both represent
         *     {@code Double.NaN}, then the {@code equals} method
         *     returns {@code true}, even though
         *     {@code Double.NaN==Double.NaN} has the value
         *     {@code false}.
         * <li>If {@code d1} represents {@code +0.0} while
         *     {@code d2} represents {@code -0.0}, or vice versa,
         *     the {@code equal} test has the value {@code false},
         *     even though {@code +0.0==-0.0} has the value {@code true}.
         * </ul>
    

    **对不起,如果我语法错误。

    【讨论】:

      猜你喜欢
      • 2017-01-24
      • 2018-09-14
      • 2016-11-04
      • 2018-04-30
      • 2020-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多