it-swarm.com.ru

Равенство DataFrame в Apache Spark

Предположим, что df1 и df2 - это две DataFrames в Apache Spark, вычисленные с использованием двух разных механизмов, например, Spark SQL и Scala/Java/Python API. 

Существует ли идиоматический способ определения того, являются ли два кадра данных эквивалентными (равными, изоморфными), где эквивалентность определяется тем, что данные (имена столбцов и значения столбцов для каждой строки) идентичны, за исключением порядка строк и столбцов?

Мотивация для этого вопроса заключается в том, что часто есть много способов вычислить результат больших данных, каждый из которых имеет свои собственные компромиссы. При изучении этих компромиссов важно поддерживать правильность и, следовательно, необходимость проверять эквивалентность/равенство в значимом наборе тестовых данных.

17
Sim

В наборах тестов Apache Spark есть несколько стандартных способов, однако большинство из них включают сбор данных локально, и если вы хотите провести тестирование на равенство на больших фреймах данных, то это, вероятно, не подходящее решение.

Сначала проверив схему, а затем вы можете сделать пересечение с df3 и убедиться, что все значения df1, df2 и df3 равны (однако это работает, только если нет повторяющихся строк, если есть разные повторяющиеся строки, этот метод все еще может верните истину).

Другим вариантом будет получение базовых RDD обоих DataFrames, сопоставление с (Row, 1), выполнение reduByKey для подсчета количества каждой строки, а затем объединение двух результирующих RDD с последующим регулярным агрегированием и возвращением false, если ни один из итераторов не равен.

9
Holden

Я не знаю насчет идиоматики, но я думаю, что вы можете получить надежный способ сравнения DataFrames, как вы описываете следующим образом. (Я использую 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

Этот подход корректно обрабатывает случаи, когда DataFrames могут иметь повторяющиеся строки, строки в разных порядках и/или столбцы в разных порядках.

Например:

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 (я создатель библиотеки):

Метод 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 сравнивает DataFrames, распределенные по нескольким машинам (код в основном скопирован из 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

Джава:

assert resultDs.union(answerDs).distinct().count() == resultDs.intersect(answerDs).count();
2
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()
0
Herman van Hovell