检查Scala中两个Spark数据帧的相等性
我是Scala新手,在编写单元测试时遇到问题 我试图在Scala中为单元测试比较和检查两个Spark数据帧的相等性,并意识到检查两个Spark数据帧的相等性并不容易 <> C++等效代码是(假设数据框在C++中表示为双数组):检查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;行
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))
})