我已经在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
基本上,到目前为止,获得中位数的方法是这样的:
如果med_loc是一位数字(即列表中只有一位,例如[1]或[3]等),则中位数= df.numbers,其中 df.row_number = df.med_loc
如果med_loc是两位数字(即,如果列表包含两位数字,例如[1,2]或[2,3]等),则中位数=平均值(df.numbers),其中 df。 df.med_loc
我不能足够强调使用numpy等其他库获取输出对我来说有多重要。我查看了使用np.median的其他解决方案,它们可以工作,但是,这不是我目前的要求。
对于这种解释如此之复杂以及使我感到复杂的情况,我感到抱歉。我已经看了好几天了,似乎无法弄清楚。我还尝试使用percent_rank函数,但由于并非所有窗口都包含0.5%,所以我无法弄清楚。
任何帮助将不胜感激。
答案 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|
+----------+-------+-------+
首先像在示例中一样添加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 number
为1
或大于右侧的{{ 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
展开数组,这将返回两个新列:middle
和posexplode()
。然后,我们将结果DataFrame过滤掉,只保留计算中位数所需的位置。
保持位置的逻辑如下:
pos
是col
,我们只会保持中间位置isEven
是False
,我们将保持中间位置和中间位置-1。最后将isEven
和True
分组,然后对剩余的id
进行平均。
Date