Pyspark数据框-中位数,无任何Numpy或其他库

时间:2018-07-27 03:26:28

标签: python dataframe pyspark median

我已经在pyspark上进行了一段时间的研究,但遇到了麻烦。我正在尝试获取其相应窗口的列数字的中位数。我需要在不使用其他库(例如numpy等)的情况下执行此操作。

到目前为止(如下图所示),我已经按照列 id 将数据集分组为窗口。 行号列对此进行了描述,该列向您显示每个窗口的外观。此数据框示例中有三个窗口。

这就是我想要的:

我希望每一行还包含列 id 的窗口的中间值,而不考虑其自己的行。我需要的中位数的位置在下面的函数中,称为 median_loc

示例:对于 row_number = 5,我需要找到其上方第1至4行的中位数(即不包括 row_number 5)。因此,中位数(根据我的要求)是同一窗口中的列 id 的平均值,其中 row_number = 1和 row_number = 2,即< / p>

Date        id      numbers row_number  med_loc
2017-03-02  group 1   98        1       [1]
2017-04-01  group 1   50        2       [1]
2018-03-02  group 1   5         3       [1, 2]
2016-03-01  group 2   49        1       [1]
2016-12-22  group 2   81        2       [1]
2017-12-31  group 2   91        3       [1, 2]
2018-08-08  group 2   19        4       [2]
2018-09-25  group 2   52        5       [1, 2]
2017-01-01  group 3   75        1       [1]
2018-12-12  group 3   17        2       [1]

我用来获取med_loc的最后一列的代码如下

def median_loc(sz):
    if sz == 1 or sz == 0:
        kth = [1]
        return kth
    elif sz % 2 == 0 and sz > 1:
        szh = sz // 2
        kth = [szh - 1, szh] if szh != 1 else [1, 2]
        return kth
    elif sz % 2 != 0 and sz > 1:
        kth = [(sz + 1) // 2]
        return kth


sqlContext.udf.register("median_location", median_loc)

median_loc = F.udf(median_loc)

df = df.withColumn("med_loc", median_loc(df.row_number)-1)

注意:为了使理解更容易,我仅使它们看起来像一个列表。这只是为了显示中位数在各个窗口中的位置。只是为了使人们更容易理解,请阅读Stack Overflow

我想要的输出如下:

Date        id      numbers row_number  med_loc     median
2017-03-02  group 1   98        1       [1]           98
2017-04-01  group 1   50        2       [1]           98
2018-03-02  group 1   5         3       [1, 2]        74
2016-03-01  group 2   49        1       [1]           49
2016-12-22  group 2   81        2       [1]           49
2017-12-31  group 2   91        3       [1, 2]        65
2018-08-08  group 2   19        4       [2]           81
2018-09-25  group 2   52        5       [1, 2]        65
2017-01-01  group 3   75        1       [1]           75
2018-12-12  group 3   17        2       [1]           75

基本上,到目前为止,获得中位数的方法是这样的:

  1. 如果med_loc是一位数字(即列表中只有一位,例如[1]或[3]等),则中位数= df.numbers,其中 df.row_number = df.med_loc

  2. 如果med_loc是两位数字(即,如果列表包含两位数字,例如[1,2]或[2,3]等),则中位数=平均值(df.numbers),其中 df。 df.med_loc

  3. 中的row_number

我不能足够强调使用numpy等其他库获取输出对我来说有多重要。我查看了使用np.median的其他解决方案,它们可以工作,但是,这不是我目前的要求。

对于这种解释如此之复杂以及使我感到复杂的情况,我感到抱歉。我已经看了好几天了,似乎无法弄清楚。我还尝试使用percent_rank函数,但由于并非所有窗口都包含0.5%,所以我无法弄清楚。

任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:1)

假设您从以下数据框架df开始:

+----------+-------+-------+
|      Date|     id|numbers|
+----------+-------+-------+
|2017-03-02|group 1|     98|
|2017-04-01|group 1|     50|
|2018-03-02|group 1|      5|
|2016-03-01|group 2|     49|
|2016-12-22|group 2|     81|
|2017-12-31|group 2|     91|
|2018-08-08|group 2|     19|
|2018-09-25|group 2|     52|
|2017-01-01|group 3|     75|
|2018-12-12|group 3|     17|
+----------+-------+-------+

订购DataFrame

首先像在示例中一样添加row_number,然后将输出分配给新的DataFrame df2

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

df2 = df.select(
    "*", f.row_number().over(Window.partitionBy("id").orderBy("Date")).alias("row_number")
)
df2.show()
#+----------+-------+-------+----------+
#|      Date|     id|numbers|row_number|
#+----------+-------+-------+----------+
#|2017-03-02|group 1|     98|         1|
#|2017-04-01|group 1|     50|         2|
#|2018-03-02|group 1|      5|         3|
#|2016-03-01|group 2|     49|         1|
#|2016-12-22|group 2|     81|         2|
#|2017-12-31|group 2|     91|         3|
#|2018-08-08|group 2|     19|         4|
#|2018-09-25|group 2|     52|         5|
#|2017-01-01|group 3|     75|         1|
#|2018-12-12|group 3|     17|         2|
#+----------+-------+-------+----------+

收集中位数的值

现在,您可以在df2列上将id与其自身连接起来,条件是左侧的row number1或大于右侧的{{ 1}}。然后,将左边的DataFrame的row_number分组,并将右边的DataFrame的("id", "Date", "row_number")收集到一个列表中。

对于numbers等于1的情况,我们只想保留此收集列表的第一个元素。否则保留所有数字,但对它们进行排序,因为我们需要对它们进行排序以计算中位数。

将此中间数据​​框称为row_number

df3

请注意,df3 = df2.alias("l").join(df2.alias("r"), on="id", how="left")\ .where("l.row_number = 1 OR (r.row_number < l.row_number)")\ .groupBy("l.id", "l.Date", "l.row_number")\ .agg(f.collect_list("r.numbers").alias("numbers"))\ .select( "id", "Date", "row_number", f.when( f.col("row_number") == 1, f.array([f.col("numbers").getItem(0)]) ).otherwise(f.sort_array("numbers")).alias("numbers") ) df3.show() #+-------+----------+----------+----------------+ #| id| Date|row_number| numbers| #+-------+----------+----------+----------------+ #|group 1|2017-03-02| 1| [98]| #|group 1|2017-04-01| 2| [98]| #|group 1|2018-03-02| 3| [50, 98]| #|group 2|2016-03-01| 1| [49]| #|group 2|2016-12-22| 2| [49]| #|group 2|2017-12-31| 3| [49, 81]| #|group 2|2018-08-08| 4| [49, 81, 91]| #|group 2|2018-09-25| 5|[19, 49, 81, 91]| #|group 3|2017-01-01| 1| [75]| #|group 3|2018-12-12| 2| [75]| #+-------+----------+----------+----------------+ 的{​​{1}}列中列出了我们想要为其找到中位数的适当值。

计算中位数

由于您的Spark版本大于2.1,因此可以使用numbers从该值列表中计算中值。对于较低版本的spark,您需要使用df3

首先在pyspark.sql.functions.posexplode()中创建2个帮助者列:

  • udf:一个布尔值,用于指示df3数组是否具有偶数个元素
  • isEven:数组中间的索引,它是长度/ 2的底数。

创建这些列之后,使用numbers展开数组,这将返回两个新列:middleposexplode()。然后,我们将结果DataFrame过滤掉,只保留计算中位数所需的位置。

保持位置的逻辑如下:

  • 如果poscol,我们只会保持中间位置
  • 如果isEvenFalse,我们将保持中间位置和中间位置-1。

最后将isEvenTrue分组,然后对剩余的id进行平均。

Date