分散メソッド、IPython、およびSparkを使用して、整数のRDD
の中央値を見つけるにはどうすればよいですか? RDD
は約700,000要素であるため、収集して中央値を見つけるには大きすぎます。
この質問はこの質問に似ています。ただし、質問に対する答えはScalaを使用することですが、これはわかりません。
Apache Sparkで正確な中央値を計算するにはどうすればよいですか?
Scala回答の考え方を使用して、Pythonで同様の回答を作成しようとしています。
最初にRDD
をソートしたいと思います。私は方法がわからない。 sortBy
(指定されたkeyfunc
でこのRDDを並べ替えます)とsortByKey
((_key、value)ペアで構成されると想定されるRDD
を並べ替えます)が表示されます。メソッド。私は両方ともキー値を使用し、私のRDD
は整数要素のみを持っていると思います。
myrdd.sortBy(lambda x: x)
を行うことを考えていましたか?rdd.count()
)を見つけます。編集:
アイデアがありました。 RDD
にインデックスを付けてから、キー=インデックスと値=要素にインデックスを付けることができます。そして、私は値で並べ替えることができますか? sortByKey
メソッドしかないため、これが可能かどうかわかりません。
Greenwald-Khannaアルゴリズム を実装するapproxQuantile
メソッドを使用できます。
Python:
df.approxQuantile("x", [0.5], 0.25)
スカラ:
df.stat.approxQuantile("x", Array(0.5), 0.25)
ここで、最後のパラメーターは相対エラーです。数値が小さいほど、より正確な結果とより高価な計算が行われます。
Spark 2.2( SPARK-14352 )以降、複数の列の推定をサポートします。
df.approxQuantile(["x", "y", "z"], [0.5], 0.25)
そして
df.approxQuantile(Array("x", "y", "z"), Array(0.5), 0.25)
Python
私がコメントで言及したように、それはほとんどすべての大騒ぎの価値はありません。あなたの場合のようにデータが比較的小さい場合は、単に中央値を収集して計算します:
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
数年前のコンピューターでは約0.01秒、約5.5MBのメモリが必要です。
データがはるかに大きい場合、並べ替えが制限要因になるため、正確な値を取得するよりも、ローカルでサンプリング、収集、および計算することをお勧めします。しかし、本当にSparkを使用したい場合は、次のような方法でトリックを実行する必要があります(私が何も混乱しなかった場合)。
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)
そしていくつかのテスト:
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)
最後に、中央値を定義します。
from functools import partial
median = partial(quantile, p=0.5)
これまでのところは良いですが、ネットワーク通信なしのローカルモードでは4.66秒かかります。おそらくこれを改善する方法がありますが、なぜ気にするのでしょうか?
言語に依存しない(Hive UDAF):
HiveContext
を使用する場合は、Hive UDAFも使用できます。整数値の場合:
rdd.map(lambda x: (float(x), )).toDF(["x"]).registerTempTable("df")
sqlContext.sql("SELECT percentile_approx(x, 0.5) FROM df")
連続値の場合:
sqlContext.sql("SELECT percentile(x, 0.5) FROM df")
percentile_approx
では、使用するレコードの数を決定する追加の引数を渡すことができます。
RDDメソッドのみが必要で、DFに移行したくない場合は、ソリューションを追加します。このスニペットを使用すると、RDDが2倍になるパーセンタイルを取得できます。
50としてパーセンタイルを入力する場合、必要な中央値を取得する必要があります。説明されていないコーナーケースがあれば教えてください。
/**
* 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)
}
ウィンドウ関数を使用して使用した方法は次のとおりです(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
次に、addMedianメソッドを呼び出して、col2の中央値を計算します。
from pyspark.sql import Window
median_window = Window.partitionBy("col1")
df = df.addMedian("col2", "median").over(median_window)
最後に、必要に応じてグループ化できます。
df.groupby("col1", "median")
入力としてデータフレームを取り、パーティションの出力として中央値を持つデータフレームを返す関数を作成しました。order_colは、part_colの中央値を計算する列で、中央値を計算するレベルです:
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")