检查Scala中两个Spark数据帧的相等性

检查Scala中两个Spark数据帧的相等性,scala,unit-testing,apache-spark,spark-dataframe,Scala,Unit Testing,Apache Spark,Spark Dataframe,我是Scala新手,在编写单元测试时遇到问题 我试图在Scala中为单元测试比较和检查两个Spark数据帧的相等性,并意识到检查两个Spark数据帧的相等性并不容易 C++等效代码是(假设数据框在C++中表示为双数组): int预期值[10][2]; int结果[10][2]; 对于(int行=0;行

我是Scala新手,在编写单元测试时遇到问题

我试图在Scala中为单元测试比较和检查两个Spark数据帧的相等性,并意识到检查两个Spark数据帧的相等性并不容易

<> C++等效代码是(假设数据框在C++中表示为双数组):

int预期值[10][2];
int结果[10][2];
对于(int行=0;行<10;行++){
for(int col=0;col<2;col++){
if(预期的[row][col]!=result[row][col])返回false;
}
}
实际测试将涉及基于数据帧列数据类型的相等性测试(使用浮点精度公差测试等)

使用Scala和其他用于检查两个数据帧相等性的解决方案(如
df1)迭代循环数据帧中的所有元素似乎不是一种简单的方法。除了(df2)
在我的情况下不起作用,因为我需要能够提供对测试相等性的支持,并允许浮动和双精度


当然,我可以尝试在之前对所有元素进行四舍五入,然后比较结果,但我想看看是否有其他解决方案允许我迭代数据帧以检查相等性。

如果出于测试目的,您想检查两个数据帧是否相等,您可以使用数据帧的
subtract()
方法(1.3及以上版本支持)

您可以检查两个数据帧的差异是空的还是0。
e、 g.
df1.subtract(df2).count()==0

假设您有固定的列和行,一个解决方案可以是按行索引连接两个Df(如果您没有记录的id),然后直接在最终Df中迭代[使用两个Df的所有列]。
import org.scalatest.{BeforeAndAfterAll, FeatureSpec, Matchers}

outDf.collect() should contain theSameElementsAs (dfComparable.collect())
# or ( obs order matters ! )

// outDf.except(dfComparable).toDF().count should be(0)
outDf.except(dfComparable).count should be(0)   
大概是这样的:

Schemas
DF1
root
 |-- col1: double (nullable = true)
 |-- col2: double (nullable = true)
 |-- col3: double (nullable = true)

DF2
root
 |-- col1: double (nullable = true)
 |-- col2: double (nullable = true)
 |-- col3: double (nullable = true)

df1
+----------+-----------+------+
|      col1|       col2|  col3|
+----------+-----------+------+
|1.20000001|       1.21|   1.2|
|    2.1111|        2.3|  22.2|
|       3.2|2.330000001| 2.333|
|    2.2444|      2.344|2.3331|
+----------+-----------+------+

df2
+------+-----+------+
|  col1| col2|  col3|
+------+-----+------+
|   1.2| 1.21|   1.2|
|2.1111|  2.3|  22.2|
|   3.2| 2.33| 2.333|
|2.2444|2.344|2.3331|
+------+-----+------+

Added row index
df1
+----------+-----------+------+---+
|      col1|       col2|  col3|row|
+----------+-----------+------+---+
|1.20000001|       1.21|   1.2|  0|
|    2.1111|        2.3|  22.2|  1|
|       3.2|2.330000001| 2.333|  2|
|    2.2444|      2.344|2.3331|  3|
+----------+-----------+------+---+

df2
+------+-----+------+---+
|  col1| col2|  col3|row|
+------+-----+------+---+
|   1.2| 1.21|   1.2|  0|
|2.1111|  2.3|  22.2|  1|
|   3.2| 2.33| 2.333|  2|
|2.2444|2.344|2.3331|  3|
+------+-----+------+---+

Combined DF
+---+----------+-----------+------+------+-----+------+
|row|      col1|       col2|  col3|  col1| col2|  col3|
+---+----------+-----------+------+------+-----+------+
|  0|1.20000001|       1.21|   1.2|   1.2| 1.21|   1.2|
|  1|    2.1111|        2.3|  22.2|2.1111|  2.3|  22.2|
|  2|       3.2|2.330000001| 2.333|   3.2| 2.33| 2.333|
|  3|    2.2444|      2.344|2.3331|2.2444|2.344|2.3331|
+---+----------+-----------+------+------+-----+------+
这就是你可以做到的:

println("Schemas")
    println("DF1")
    df1.printSchema()
    println("DF2")
    df2.printSchema()
    println("df1")
    df1.show
    println("df2")
    df2.show
    val finaldf1 = df1.withColumn("row", monotonically_increasing_id())
    val finaldf2 = df2.withColumn("row", monotonically_increasing_id())
    println("Added row index")
    println("df1")
    finaldf1.show()
    println("df2")
    finaldf2.show()

    val joinedDfs = finaldf1.join(finaldf2, "row")
    println("Combined DF")
    joinedDfs.show()

    val tolerance = 0.001
    def isInValidRange(a: Double, b: Double): Boolean ={
      Math.abs(a-b)<=tolerance
    }
    joinedDfs.take(10).foreach(row => {
      assert( isInValidRange(row.getDouble(1), row.getDouble(4)) , "Col1 validation. Row %s".format(row.getLong(0)+1))
      assert( isInValidRange(row.getDouble(2), row.getDouble(5)) , "Col2 validation. Row %s".format(row.getLong(0)+1))
      assert( isInValidRange(row.getDouble(3), row.getDouble(6)) , "Col3 validation. Row %s".format(row.getLong(0)+1))
    })
println(“模式”)
println(“DF1”)
df1.printSchema()
println(“DF2”)
df2.printSchema()
println(“df1”)
df1.show
println(“df2”)
df2.show
val finaldf1=df1.withColumn(“行”,单调递增的\u id())
val finaldf2=df2.withColumn(“行”,单调递增的id())
println(“添加的行索引”)
println(“df1”)
finaldf1.show()
println(“df2”)
finaldf2.show()
val joinedDfs=finaldf1.join(finaldf2,“行”)
println(“组合DF”)
joinedDfs.show()
val公差=0.001
定义isInValidRange(a:Double,b:Double):布尔值={
数学abs(a-b){
断言(isInValidRange(row.getDouble(1),row.getDouble(4)),“Col1验证.行%s.”格式(row.getLong(0)+1))
断言(isInValidRange(row.getDouble(2),row.getDouble(5)),“Col2验证.行%s.”格式(row.getLong(0)+1))
断言(isInValidRange(row.getDouble(3),row.getDouble(6)),“Col3验证.行%s.”格式(row.getLong(0)+1))
})
注意:断言没有序列化,解决方法是使用take()避免错误


你的数据帧有多大?如果它们不是那么大,你可以对它们进行排序/收集,然后很容易地进行比较。因为这些是单元测试数据帧,所以它们应该很小。只需将它们收集到一个列表中并进行比较。是的,我的测试目前将数据帧收集到一个列表中并进行比较,但我希望创建的工具也可以在更大的数据帧上进行测试。我猜没有简单的方法可以做到这一点?***3年前问,4个月前激活,5个月前查看了7k次---但仍然没有接受答案…感谢您的建议,但是
df1。除了(df2)
,我在问题中提到的与
df1.subtract(df2)具有相同的功能
在这种情况下不起作用,我希望将值与精度公差进行比较。except函数已返回数据帧,因此不需要
toDF
outDf。except(dfComparable)。计数应为(0)
不是一个好的选择,因为
。除了
返回一个表,其中包含从左到右的元素。如果左侧缺少某些元素,则测试不会失败。
assertSmallDataFrameEquality
是一个更好的选择,请参见spark testing base的
assertDataFrameEquals
println("Schemas")
    println("DF1")
    df1.printSchema()
    println("DF2")
    df2.printSchema()
    println("df1")
    df1.show
    println("df2")
    df2.show
    val finaldf1 = df1.withColumn("row", monotonically_increasing_id())
    val finaldf2 = df2.withColumn("row", monotonically_increasing_id())
    println("Added row index")
    println("df1")
    finaldf1.show()
    println("df2")
    finaldf2.show()

    val joinedDfs = finaldf1.join(finaldf2, "row")
    println("Combined DF")
    joinedDfs.show()

    val tolerance = 0.001
    def isInValidRange(a: Double, b: Double): Boolean ={
      Math.abs(a-b)<=tolerance
    }
    joinedDfs.take(10).foreach(row => {
      assert( isInValidRange(row.getDouble(1), row.getDouble(4)) , "Col1 validation. Row %s".format(row.getLong(0)+1))
      assert( isInValidRange(row.getDouble(2), row.getDouble(5)) , "Col2 validation. Row %s".format(row.getLong(0)+1))
      assert( isInValidRange(row.getDouble(3), row.getDouble(6)) , "Col3 validation. Row %s".format(row.getLong(0)+1))
    })