web-dev-qa-db-ja.com

ApacheのDataFrameの平等Spark

df1およびdf2は、Apache Sparkの2つのDataFrameです。2つの異なるメカニズムを使用して計算されます。たとえば、Spark SQLとScala/Java/Python API。

行と列の順序を保存せずにデータ(列名と各行の列値)が同じであると同等である場合、2つのデータフレームが等しい(等しい、同形)かどうかを判定する慣用的な方法はありますか?

質問の動機は、多くの場合、ビッグデータの結果を計算する多くの方法があり、それぞれにトレードオフがあることです。これらのトレードオフを検討する際、正確性を維持することが重要であるため、意味のあるテストデータセットの等価性/等価性をチェックする必要があります。

22
Sim

Apache Sparkテストスイートにはいくつかの標準的な方法がありますが、これらのほとんどはローカルでデータを収集する必要があり、大規模なDataFrameで同等性テストを行いたい場合、適切なソリューションではない可能性があります。

最初にスキーマをチェックしてからdf3との交差を行い、df1、df2およびdf3のカウントがすべて等しいことを確認できます(ただし、これは重複行がない場合にのみ機能しますが、異なる重複行がある場合はこの方法でも可能です) trueを返します。

別のオプションは、両方のDataFramesの基礎となるRDDを取得し、(Row、1)にマッピングし、reduceByKeyを実行して各行の数をカウントし、結果の2つのRDDをグループ化し、通常の集約を行い、次の場合にfalseを返しますいずれのイテレーターも等しくありません。

11
Holden

イディオムについては知りませんが、次のように説明するように、DataFrameを比較する堅牢な方法を得ることができると思います。 (説明のためにPySparkを使用していますが、このアプローチは複数の言語に適用されます。)

_a = spark.range(5)
b = spark.range(5)

a_prime = a.groupBy(sorted(a.columns)).count()
b_prime = b.groupBy(sorted(b.columns)).count()

assert a_prime.subtract(b_prime).count() == b_prime.subtract(a_prime).count() == 0
_

このアプローチは、データフレームに重複行、異なる順序の行、および/または異なる順序の列がある場合を正しく処理します。

例えば:

_a = spark.createDataFrame([('nick', 30), ('bob', 40)], ['name', 'age'])
b = spark.createDataFrame([(40, 'bob'), (30, 'nick')], ['age', 'name'])
c = spark.createDataFrame([('nick', 30), ('bob', 40), ('nick', 30)], ['name', 'age'])

a_prime = a.groupBy(sorted(a.columns)).count()
b_prime = b.groupBy(sorted(b.columns)).count()
c_prime = c.groupBy(sorted(c.columns)).count()

assert a_prime.subtract(b_prime).count() == b_prime.subtract(a_prime).count() == 0
assert a_prime.subtract(c_prime).count() != 0
_

このアプローチは非常に高価ですが、完全な差分を実行する必要があるため、ほとんどの費用は避けられません。また、ローカルで何かを収集する必要がないので、これはうまくスケールするはずです。比較で重複行を考慮すべき制約を緩和する場合は、groupBy()を削除してsubtract()を実行するだけで、おそらく速度が大幅に向上します。

8
Nick Chammas

spark-fast-tests ライブラリには、DataFrameの比較を行うための2つのメソッドがあります(私はライブラリの作成者です)。

assertSmallDataFrameEqualityメソッドは、ドライバーノードでDataFramesを収集し、比較を行います

def assertSmallDataFrameEquality(actualDF: DataFrame, expectedDF: DataFrame): Unit = {
  if (!actualDF.schema.equals(expectedDF.schema)) {
    throw new DataFrameSchemaMismatch(schemaMismatchMessage(actualDF, expectedDF))
  }
  if (!actualDF.collect().sameElements(expectedDF.collect())) {
    throw new DataFrameContentMismatch(contentMismatchMessage(actualDF, expectedDF))
  }
}

assertLargeDataFrameEqualityメソッドは、複数のマシンに分散したDataFrameを比較します(コードは基本的に spark-testing-base からコピーされます)

def assertLargeDataFrameEquality(actualDF: DataFrame, expectedDF: DataFrame): Unit = {
  if (!actualDF.schema.equals(expectedDF.schema)) {
    throw new DataFrameSchemaMismatch(schemaMismatchMessage(actualDF, expectedDF))
  }
  try {
    actualDF.rdd.cache
    expectedDF.rdd.cache

    val actualCount = actualDF.rdd.count
    val expectedCount = expectedDF.rdd.count
    if (actualCount != expectedCount) {
      throw new DataFrameContentMismatch(countMismatchMessage(actualCount, expectedCount))
    }

    val expectedIndexValue = zipWithIndex(actualDF.rdd)
    val resultIndexValue = zipWithIndex(expectedDF.rdd)

    val unequalRDD = expectedIndexValue
      .join(resultIndexValue)
      .filter {
        case (idx, (r1, r2)) =>
          !(r1.equals(r2) || RowComparer.areRowsEqual(r1, r2, 0.0))
      }

    val maxUnequalRowsToShow = 10
    assertEmpty(unequalRDD.take(maxUnequalRowsToShow))

  } finally {
    actualDF.rdd.unpersist()
    expectedDF.rdd.unpersist()
  }
}

assertSmallDataFrameEqualityは小規模なDataFrame比較で高速であり、テストスイートで十分であることがわかりました。

4
Powers

Java:

assert resultDs.union(answerDs).distinct().count() == resultDs.intersect(answerDs).count();
4
user1442346

これを行うには、完全な外部結合と組み合わせて少しの重複排除を使用します。このアプローチの利点は、ドライバーに結果を収集する必要がなく、複数のジョブの実行を回避できることです。

import org.Apache.spark.sql._
import org.Apache.spark.sql.functions._

// Generate some random data.
def random(n: Int, s: Long) = {
  spark.range(n).select(
    (Rand(s) * 10000).cast("int").as("a"),
    (Rand(s + 5) * 1000).cast("int").as("b"))
}
val df1 = random(10000000, 34)
val df2 = random(10000000, 17)

// Move all the keys into a struct (to make handling nulls easy), deduplicate the given dataset
// and count the rows per key.
def dedup(df: Dataset[Row]): Dataset[Row] = {
  df.select(struct(df.columns.map(col): _*).as("key"))
    .groupBy($"key")
    .agg(count(lit(1)).as("row_count"))
}

// Deduplicate the inputs and join them using a full outer join. The result can contain
// the following things:
// 1. Both keys are not null (and thus equal), and the row counts are the same. The dataset
//    is the same for the given key.
// 2. Both keys are not null (and thus equal), and the row counts are not the same. The dataset
//    contains the same keys.
// 3. Only the right key is not null.
// 4. Only the left key is not null.
val joined = dedup(df1).as("l").join(dedup(df2).as("r"), $"l.key" === $"r.key", "full")

// Summarize the differences.
val summary = joined.select(
  count(when($"l.key".isNotNull && $"r.key".isNotNull && $"r.row_count" === $"l.row_count", 1)).as("left_right_same_rc"),
  count(when($"l.key".isNotNull && $"r.key".isNotNull && $"r.row_count" =!= $"l.row_count", 1)).as("left_right_different_rc"),
  count(when($"l.key".isNotNull && $"r.key".isNull, 1)).as("left_only"),
  count(when($"l.key".isNull && $"r.key".isNotNull, 1)).as("right_only"))
summary.show()
1

次のことを試してください。

df1.except(df2).isEmpty
0
Asher A