scala - Scala Spark 在数据帧和数据集中以不同方式处理 Double.NaN
问题描述
在测试中,我试图将数据帧/数据集转换为集合并进行比较。例如
actualResult.collect.toSet should be(expectedResult.collect.toSet)
我注意到一些关于Double.NaN
价值的事实。
- 在 Scala 中,
Double.NaN == Double.NaN
返回 false。 - 在火花
NaN == NaN
中是真的。(官方文档)
但我无法弄清楚为什么 dataframe 和 dataset 的行为不同。
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)
这是我的问题。欣赏任何见解。
- 它是如何在代码中发生的?(在哪里
equals
被覆盖?等..) - 这种设计背后有什么原因吗?是否有更好的范例来在测试中断言数据集/数据框?
解决方案
关于这个话题,我有几点想分享。
- 当您
dataSet.collect.toSet
将其收集为Set[Book]并在两组书籍对象之间进行比较时。
单个(书)对象相等方法用于您在Book Case 类中定义的比较。
这就是为什么println(dataSet.collect.toSet == dataSet.collect.toSet)
返回 false 因为Double.NaN == Double.NaN returns false
.
- 当你这样做时,
dataSet.toDF().collect.toSet
你将它收集为Set [Row]
当您执行toDF时,火花将转换**(即序列化 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>
**对不起,如果我在语法上是错误的。
推荐阅读
- sql - 从登录时间和注销时间为不同行的表中计算持续时间
- google-apps-script - 在基于应用程序脚本的邮件合并中附加多个文件(动态和静态)
- c++ - 将原始指针与其拥有的 shared_ptr 一起缓存以获得更好的访问性能是一个好主意吗?
- flutter - 在 null 上调用了 getter 'documents'
- r - Rmarkdown 无法识别 kable_styling 命令中的内联
- python - HERE 请求如何为 params 中的键分配多个值
- node.js - 有没有办法忽略 Node.JS tls 中的自定义 CA 公用名?
- r - R - 有效地将列表列表的所有元素保存为 data.frame
- ruby-on-rails - 如何让活动作业永远重试所有作业?
- laravel - Laravel 建模系统