web-dev-qa-db-de.com

So finden Sie Median und Quantile mit Spark

Wie kann ich den Median einer RDD von ganzen Zahlen mithilfe einer verteilten Methode, IPython und Spark ermitteln? Das RDD ist ungefähr 700.000 Elemente und daher zu groß, um den Median zu sammeln und zu finden.

Diese Frage ähnelt dieser Frage. Die Antwort auf die Frage ist jedoch die Verwendung von Scala, die ich nicht kenne.

Wie kann ich den exakten Median mit Apache Spark berechnen?

Mit dem Denken für die Scala-Antwort versuche ich, eine ähnliche Antwort in Python zu schreiben.

Ich weiß, dass ich zuerst die RDD sortieren möchte. Ich weiß nicht wie. Ich sehe die sortBy (Sortiert diese RDD nach den angegebenen keyfunc) und sortByKey (Sortiert diese RDD-Methode, wobei angenommen wird, dass sie aus (Schlüssel-, Wert-) Paaren besteht. Ich denke, dass beide den Schlüsselwert verwenden und mein RDD nur Integer-Elemente hat.

  1. Zuerst dachte ich daran myrdd.sortBy(lambda x: x) zu machen? 
  2. Als nächstes werde ich die Länge des Rdd finden (rdd.count()).
  3. Schließlich möchte ich das Element oder 2 Elemente in der Mitte des Rdd finden. Ich brauche auch bei dieser Methode Hilfe.

BEARBEITEN:

Ich hatte eine Idee. Vielleicht kann ich mein RDD indizieren und dann key = index und value = element. Und dann kann ich versuchen, nach Wert zu sortieren? Ich weiß nicht, ob dies möglich ist, da es nur eine sortByKey-Methode gibt.

42
pr338

Spark 2.0+:

Sie können die approxQuantile-Methode verwenden, die Greenwald-Khanna-Algorithmus implementiert:

Python:

df.approxQuantile("x", [0.5], 0.25)

Scala:

df.stat.approxQuantile("x", Array(0.5), 0.25)

wobei der letzte Parameter ein relativer Fehler ist. Je niedriger die Zahl, desto genauer die Ergebnisse und die Berechnungen.

Seit Spark 2.2 ( SPARK-14352 ) wird die Schätzung für mehrere Spalten unterstützt:

df.approxQuantile(["x", "y", "z"], [0.5], 0.25)

und 

df.approxQuantile(Array("x", "y", "z"), Array(0.5), 0.25)

Funken <2,0

Python

Wie ich bereits in den Kommentaren erwähnt habe, lohnt es sich wahrscheinlich nicht, den ganzen Lärm zu machen. Wenn die Daten wie in Ihrem Fall relativ klein sind, sammeln und berechnen Sie den Median lokal:

import numpy as np

np.random.seed(323)
rdd = sc.parallelize(np.random.randint(1000000, size=700000))

%time np.median(rdd.collect())
np.array(rdd.collect()).nbytes

Es dauert ungefähr 0,01 Sekunden auf meinem Computer, der einige Jahre alt ist, und ungefähr 5,5 MB Arbeitsspeicher.

Wenn die Daten viel größer sind, ist die Sortierung ein begrenzender Faktor. Statt einen genauen Wert zu erhalten, ist es wahrscheinlich besser, lokal zu sammeln, zu sammeln und zu berechnen. Aber wenn Sie wirklich wollen, dass Sie Spark verwenden, dann sollte etwas wie dieser den Trick tun (wenn ich nichts vermasselt habe):

from numpy import floor
import time

def quantile(rdd, p, sample=None, seed=None):
    """Compute a quantile of order p ∈ [0, 1]
    :rdd a numeric rdd
    :p quantile(between 0 and 1)
    :sample fraction of and rdd to use. If not provided we use a whole dataset
    :seed random number generator seed to be used with sample
    """
    assert 0 <= p <= 1
    assert sample is None or 0 < sample <= 1

    seed = seed if seed is not None else time.time()
    rdd = rdd if sample is None else rdd.sample(False, sample, seed)

    rddSortedWithIndex = (rdd.
        sortBy(lambda x: x).
        zipWithIndex().
        map(lambda (x, i): (i, x)).
        cache())

    n = rddSortedWithIndex.count()
    h = (n - 1) * p

    rddX, rddXPlusOne = (
        rddSortedWithIndex.lookup(x)[0]
        for x in int(floor(h)) + np.array([0L, 1L]))

    return rddX + (h - floor(h)) * (rddXPlusOne - rddX)

Und einige Tests:

np.median(rdd.collect()), quantile(rdd, 0.5)
## (500184.5, 500184.5)
np.percentile(rdd.collect(), 25), quantile(rdd, 0.25)
## (250506.75, 250506.75)
np.percentile(rdd.collect(), 75), quantile(rdd, 0.75)
(750069.25, 750069.25)

Schließlich definieren wir den Median:

from functools import partial
median = partial(quantile, p=0.5)

So weit so gut, aber es dauert 4.66 s im lokalen Modus ohne Netzwerkkommunikation. Es gibt wahrscheinlich eine Möglichkeit, dies zu verbessern, aber warum sollte man sich überhaupt die Mühe machen?

Sprachunabhängig (Hive UDAF): 

Wenn Sie HiveContext verwenden, können Sie auch Hive-UDAFs verwenden. Mit ganzzahligen Werten:

rdd.map(lambda x: (float(x), )).toDF(["x"]).registerTempTable("df")

sqlContext.sql("SELECT percentile_approx(x, 0.5) FROM df")

Bei kontinuierlichen Werten:

sqlContext.sql("SELECT percentile(x, 0.5) FROM df")

In percentile_approx können Sie ein zusätzliches Argument übergeben, das die Anzahl der zu verwendenden Datensätze bestimmt.

71
zero323

Hinzufügen einer Lösung, wenn Sie nur eine RDD-Methode wünschen und nicht zu DF ..__ wechseln möchten. Dieser Ausschnitt kann ein Perzentil für eine RDD von double erhalten.

Wenn Sie das Perzentil als 50 eingeben, sollten Sie den erforderlichen Medianwert angeben. Geben Sie mir Bescheid, wenn Eckfälle vorliegen, die nicht berücksichtigt wurden.

/**
  * Gets the nth percentile entry for an RDD of doubles
  *
  * @param inputScore : Input scores consisting of a RDD of doubles
  * @param percentile : The percentile cutoff required (between 0 to 100), e.g 90%ile of [1,4,5,9,19,23,44] = ~23.
  *                     It prefers the higher value when the desired quantile lies between two data points
  * @return : The number best representing the percentile in the Rdd of double
  */    
  def getRddPercentile(inputScore: RDD[Double], percentile: Double): Double = {
    val numEntries = inputScore.count().toDouble
    val retrievedEntry = (percentile * numEntries / 100.0 ).min(numEntries).max(0).toInt


    inputScore
      .sortBy { case (score) => score }
      .zipWithIndex()
      .filter { case (score, index) => index == retrievedEntry }
      .map { case (score, index) => score }
      .collect()(0)
  }
6
Vedant

Hier ist die Methode, die ich verwendet habe, um Fensterfunktionen zu verwenden (mit Pyspark 2.2.0).

from pyspark.sql import DataFrame

class median():
    """ Create median class with over method to pass partition """
    def __init__(self, df, col, name):
        assert col
        self.column=col
        self.df = df
        self.name = name

    def over(self, window):
        from pyspark.sql.functions import percent_rank, pow, first

        first_window = window.orderBy(self.column)                                  # first, order by column we want to compute the median for
        df = self.df.withColumn("percent_rank", percent_rank().over(first_window))  # add percent_rank column, percent_rank = 0.5 coressponds to median
        second_window = window.orderBy(pow(df.percent_rank-0.5, 2))                 # order by (percent_rank - 0.5)^2 ascending
        return df.withColumn(self.name, first(self.column).over(second_window))     # the first row of the window corresponds to median

def addMedian(self, col, median_name):
    """ Method to be added to spark native DataFrame class """
    return median(self, col, median_name)

# Add method to DataFrame class
DataFrame.addMedian = addMedian

Rufen Sie dann die addMedian-Methode auf, um den Median von col2 zu berechnen:

from pyspark.sql import Window

median_window = Window.partitionBy("col1")
df = df.addMedian("col2", "median").over(median_window)

Schließlich können Sie nach Bedarf gruppieren.

df.groupby("col1", "median")
5
Benoît Carne

Ich habe die Funktion geschrieben, die den Datenrahmen als Eingabe nimmt und einen Datenrahmen zurückgibt, der den Median als Ausgabe über eine Partition hat. Order_col ist die Spalte, für die wir den Median berechnen möchten :

from pyspark.sql import Window
import pyspark.sql.functions as F

def calculate_median(dataframe, part_col, order_col):
    win = Window.partitionBy(*part_col).orderBy(order_col)
#     count_row = dataframe.groupby(*part_col).distinct().count()
    dataframe.persist()
    dataframe.count()
    temp = dataframe.withColumn("rank", F.row_number().over(win))
    temp = temp.withColumn(
        "count_row_part",
        F.count(order_col).over(Window.partitionBy(part_col))
    )
    temp = temp.withColumn(
        "even_flag",
        F.when(
            F.col("count_row_part") %2 == 0,
            F.lit(1)
        ).otherwise(
            F.lit(0)
        )
    ).withColumn(
        "mid_value",
        F.floor(F.col("count_row_part")/2)
    )

    temp = temp.withColumn(
        "avg_flag",
        F.when(
            (F.col("even_flag")==1) &
            (F.col("rank") == F.col("mid_value"))|
            ((F.col("rank")-1) == F.col("mid_value")),
            F.lit(1)
        ).otherwise(
        F.when(
            F.col("rank") == F.col("mid_value")+1,
            F.lit(1)
            )
        )
    )
    temp.show(10)
    return temp.filter(
        F.col("avg_flag") == 1
    ).groupby(
        part_col + ["avg_flag"]
    ).agg(
        F.avg(F.col(order_col)).alias("median")
    ).drop("avg_flag")
1