簡體   English   中英

比較兩個 Spark 數據幀

[英]Compare two Spark dataframes

Spark 數據幀 1 -:

+------+-------+---------+----+---+-------+
|city  |product|date     |sale|exp|wastage|
+------+-------+---------+----+---+-------+
|city 1|prod 1 |9/29/2017|358 |975|193    |
|city 1|prod 2 |8/25/2017|50  |687|201    |
|city 1|prod 3 |9/9/2017 |236 |431|169    |
|city 2|prod 1 |9/28/2017|358 |975|193    |
|city 2|prod 2 |8/24/2017|50  |687|201    |
|city 3|prod 3 |9/8/2017 |236 |431|169    |
+------+-------+---------+----+---+-------+

Spark 數據框 2 -:

+------+-------+---------+----+---+-------+
|city  |product|date     |sale|exp|wastage|
+------+-------+---------+----+---+-------+
|city 1|prod 1 |9/29/2017|358 |975|193    |
|city 1|prod 2 |8/25/2017|50  |687|201    |
|city 1|prod 3 |9/9/2017 |230 |430|160    |
|city 1|prod 4 |9/27/2017|350 |90 |190    |
|city 2|prod 2 |8/24/2017|50  |687|201    |
|city 3|prod 3 |9/8/2017 |236 |431|169    |
|city 3|prod 4 |9/18/2017|230 |431|169    |
+------+-------+---------+----+---+-------+

請找出適用於上述給定火花數據幀 1 和火花數據幀 2 的以下條件的火花數據幀,

  1. 刪除的記錄
  2. 新紀錄
  3. 沒有變化的記錄
  4. 有變化的記錄

    這里的組合鍵是“城市”、“產品”、“日期”。

我們需要不使用 Spark SQL 的解決方案。

我不確定是否找到已刪除和已修改的記錄,但您可以使用 except 函數來獲取差異

df2.except(df1)

這將返回已在 dataframe2 或更改記錄中添加或修改的行。 輸出:

+------+-------+---------+----+---+-------+
|  city|product|     date|sale|exp|wastage|
+------+-------+---------+----+---+-------+
|city 3| prod 4|9/18/2017| 230|431|    169|
|city 1| prod 4|9/27/2017| 350| 90|    190|
|city 1| prod 3|9/9/2017 | 230|430|    160|
+------+-------+---------+----+---+-------+

您還可以嘗試使用 join 和 filter 來獲取已更改和未更改的數據

df1.join(df2, Seq("city","product", "date"), "left").show(false)
df1.join(df2, Seq("city","product", "date"), "right").show(false)

希望這有幫助!

一種可擴展且簡單的方法是使用spark-extension DataFrame兩個DataFrame

import uk.co.gresearch.spark.diff._

df1.diff(df2, "city", "product", "date").show

+----+------+-------+----------+---------+----------+--------+---------+------------+-------------+
|diff|  city|product|      date|left_sale|right_sale|left_exp|right_exp|left_wastage|right_wastage|
+----+------+-------+----------+---------+----------+--------+---------+------------+-------------+
|   N|city 1|prod 2 |2017-08-25|       50|        50|     687|      687|         201|          201|
|   C|city 1|prod 3 |2017-09-09|      236|       230|     431|      430|         169|          160|
|   I|city 3|prod 4 |2017-09-18|     null|       230|    null|      431|        null|          169|
|   N|city 3|prod 3 |2017-09-08|      236|       236|     431|      431|         169|          169|
|   D|city 2|prod 1 |2017-09-28|      358|      null|     975|     null|         193|         null|
|   I|city 1|prod 4 |2017-09-27|     null|       350|    null|       90|        null|          190|
|   N|city 1|prod 1 |2017-09-29|      358|       358|     975|      975|         193|          193|
|   N|city 2|prod 2 |2017-08-24|       50|        50|     687|      687|         201|          201|
+----+------+-------+----------+---------+----------+--------+---------+------------+-------------+

它確定nserted,C上吊,d eleted和加利-changed行。

查看 MegaSparkDiff,它是 GitHub 上的一個開源項目,可幫助比較數據幀。該項目尚未在 maven 中心發布,但您可以查看比較 2 個數據幀的 SparkCompare scala 類

下面的代碼片段將為您提供 2 個數據幀,其中一個具有 inLeftButNotInRight 行,另一個具有 InRightButNotInLeft。

如果您在兩者之間進行 JOIN,那么您可以應用一些邏輯來識別丟失的主鍵(在可能的情況下),然后這些鍵將構成已刪除的記錄。

我們正在努力添加您在項目中尋找的用例。 https://github.com/FINRAOS/MegaSparkDiff

https://github.com/FINRAOS/MegaSparkDiff/blob/master/src/main/scala/org/finra/msd/sparkcompare/SparkCompare.scala

private def compareSchemaDataFrames(left: DataFrame , leftViewName: String
                              , right: DataFrame , rightViewName: String) :Pair[DataFrame, DataFrame] = {
    //make sure that column names match in both dataFrames
    if (!left.columns.sameElements(right.columns))
      {
        println("column names were different")
        throw new Exception("Column Names Did Not Match")
      }

    val leftCols = left.columns.mkString(",")
    val rightCols = right.columns.mkString(",")

    //group by all columns in both data frames
    val groupedLeft = left.sqlContext.sql("select " + leftCols + " , count(*) as recordRepeatCount from " +  leftViewName + " group by " + leftCols )
    val groupedRight = left.sqlContext.sql("select " + rightCols + " , count(*) as recordRepeatCount from " +  rightViewName + " group by " + rightCols )

    //do the except/subtract command
    val inLnotinR = groupedLeft.except(groupedRight).toDF()
    val inRnotinL = groupedRight.except(groupedLeft).toDF()

    return new ImmutablePair[DataFrame, DataFrame](inLnotinR, inRnotinL)
  }

請參閱下面我用來使用以下標准比較兩個數據幀的實用程序函數

  1. 柱長
  2. 記錄數
  3. 逐列比較所有記錄

任務三是通過使用記錄中所有列的串聯散列來完成的。

def verifyMatchAndSaveSignatureDifferences(oldDF: DataFrame, newDF: DataFrame, pkColumn: String) : Long = {
  assert(oldDF.columns.length == newDF.columns.length, s"column lengths don't match")
  assert(oldDF.count == newDF.count, s"record count don't match")

  def createHashColumn(df: DataFrame) : Column = {
     val colArr = df.columns
     md5(concat_ws("", (colArr.map(col(_))) : _*))
  }

  val newSigDF = newDF.select(col(pkColumn), createHashColumn(newDF).as("signature_new"))
  val oldSigDF = oldDF.select(col(pkColumn), createHashColumn(oldDF).as("signature"))

  val joinDF = newSigDF.join(oldSigDF, newSigDF("pkColumn") === oldSigDF("pkColumn")).where($"signature" !== $"signature_new").cache

  val diff = joinDF.count
  //write out any recorsd that don't match
  if (diff > 0)
     joinDF.write.saveAsTable("signature_table")

  joinDF.unpersist()

  diff
}

如果該方法返回 0,則兩個數據幀在其他所有方面都完全相同,hive 的默認架構中名為 signature_table 的表將包含兩者不同的所有記錄。

希望這會有所幫助。

使用 Spark 不同的連接類型似乎是計算行上的刪除、添加和更新的關鍵。

這個問題說明了不同類型的連接,具體取決於您要實現的目標: Spark 中有哪些不同的連接類型?

假設我們有兩個DataFrame ,z1 和 z1。 選項 1 適用於沒有重復的行。 您可以在spark-shell嘗試這些。

  • 選項1:直接做except

val inZ1NotInZ2 = z1.except(z2).toDF()
val inZ2NotInZ1 = z2.except(z1).toDF()

inZ1NotInZ2.show
inZ2NotInZ1.show
  • 選項 2:使用GroupBy (對於具有重復行的 DataFrame)
val z1Grouped = z1.groupBy(z1.columns.map(c => z1(c)).toSeq : _*).count().withColumnRenamed("count", "recordRepeatCount")
val z2Grouped = z2.groupBy(z2.columns.map(c => z2(c)).toSeq : _*).count().withColumnRenamed("count", "recordRepeatCount")

val inZ1NotInZ2 = z1Grouped.except(z2Grouped).toDF()
val inZ2NotInZ1 = z2Grouped.except(z1Grouped).toDF()

  • 選項 3,使用exceptAll ,它也適用於具有重復行的數據
// Source Code: https://github.com/apache/spark/blob/50538600ec/sql/core/src/main/scala/org/apache/spark/sql/Dataset.scala#L2029
val inZ1NotInZ2 = z1.exceptAll(z2).toDF()
val inZ2NotInZ1 = z2.exceptAll(z1).toDF()

星火版本:2.2.0

使用 except 和 left 反連接

df2.except(df1) 將類似於:

城市 產品 日期 銷售 經驗值 浪費
城市 3 產品 4 2017/9/18 230 431 169
城市 1 產品 4 2017/9/27 350 90 190
城市 1 產品 3 2017/9/9 230 430 160

正如koiralo所說,但刪除的項目'city 2 prod 1'丟失了,所以我們需要左反連接(或帶過濾器的左連接):

select * from df1 left anti join df2 on df1.city=df2.city and df1.product=df2.product

然后聯合 df2.except(df1) 的結果並離開 anti join

但是我沒有在大數據集上測試left anti join的性能

PS:如果你的spark版本超過2.4,使用spark-extension會更方便

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM