Apache spark 从早期行累积数组(PySpark数据帧)
一个Python示例将使我的问题变得清晰。假设我有一个Spark数据框,其中有在特定日期观看特定电影的人,如下所示:Apache spark 从早期行累积数组(PySpark数据帧),apache-spark,dataframe,pyspark,apache-spark-sql,Apache Spark,Dataframe,Pyspark,Apache Spark Sql,一个Python示例将使我的问题变得清晰。假设我有一个Spark数据框,其中有在特定日期观看特定电影的人,如下所示: root |-- name: string (nullable = true) |-- unixdate: long (nullable = true) |-- movies: array (nullable = true) | |-- element: string (containsNull = true) +-----+--------+----------
root
|-- name: string (nullable = true)
|-- unixdate: long (nullable = true)
|-- movies: array (nullable = true)
| |-- element: string (containsNull = true)
+-----+--------+------------------+
|name |unixdate|movies |
+-----+--------+------------------+
|Alice|1 |[Avatar] |
|Bob |2 |[Fargo, Tron] |
|Alice|4 |[Babe] |
|Alice|6 |[Avatar, Airplane]|
|Alice|7 |[Pulp Fiction] |
|Bob |9 |[Star Wars] |
+-----+--------+------------------+
movierecord=spark.createDataFrame[Alice,1,[Avatar],Bob,2,[Fargo,Tron],Alice,4,[Babe],Alice,6,[Avatar,飞机],Alice,7,[低俗小说],Bob,9,[Star Wars]],[name,unixdate,movies]
上面定义的模式和数据帧如下所示:
root
|-- name: string (nullable = true)
|-- unixdate: long (nullable = true)
|-- movies: array (nullable = true)
| |-- element: string (containsNull = true)
+-----+--------+------------------+
|name |unixdate|movies |
+-----+--------+------------------+
|Alice|1 |[Avatar] |
|Bob |2 |[Fargo, Tron] |
|Alice|4 |[Babe] |
|Alice|6 |[Avatar, Airplane]|
|Alice|7 |[Pulp Fiction] |
|Bob |9 |[Star Wars] |
+-----+--------+------------------+
我想从上面开始生成一个新的dataframe列,该列保存每个用户看到的所有以前的电影,在unixdate字段中没有重复的previous。所以它应该是这样的:
+-----+--------+------------------+------------------------+
|name |unixdate|movies |previous_movies |
+-----+--------+------------------+------------------------+
|Alice|1 |[Avatar] |[] |
|Bob |2 |[Fargo, Tron] |[] |
|Alice|4 |[Babe] |[Avatar] |
|Alice|6 |[Avatar, Airplane]|[Avatar, Babe] |
|Alice|7 |[Pulp Fiction] |[Avatar, Babe, Airplane]|
|Bob |9 |[Star Wars] |[Fargo, Tron] |
+-----+--------+------------------+------------------------+
如何以一种非常高效的方式实现这一点?仅SQL而不保留对象的顺序:
所需进口:
导入pyspark.sql.f函数
从pyspark.sql.window导入窗口
窗口定义:
w=Window.partitionByname.orderByunixdate
完整解决方案:
电影录影带
压平电影
上一部电影,f
收集独一无二的
.使用列Previous_movies,f.collect_setprevious_movie.over
删除单个unixdate的重复项
.groupByname,unixdate
.aggf.maxf.struct
f、 前几部电影,
f、 colmovies.Alias电影,
f、 colprevious_movies.alias previous_movies
.tmp
按1移位并提取
选择
名称、unixdate、tmp.movies、,
f、 lagtmp.previous_电影,1.Over.Alias previous_电影
结果是:
+---+----+---------+------------+
|名称| unixdate |电影|之前的|电影|
+---+----+---------+------------+
|鲍勃| 2 |[法戈,特隆]|空|
|鲍勃| 9 |[星球大战][法戈,特隆]|
|爱丽丝| 1 |[阿凡达]|空|
|爱丽丝| 4 |[宝贝]|[阿凡达]|
|爱丽丝| 6 |[阿凡达,飞机]|[宝贝,阿凡达]|
|爱丽丝| 7 |[低俗小说]|[宝贝,飞机,阿凡达]|
+---+----+---------+------------+
SQL是保留顺序的Python UDF:
进口:
导入pyspark.sql.f函数
从pyspark.sql.window导入窗口
从pyspark.sql导入列
从pyspark.sql.types导入ArrayType、StringType
从输入导入列表中,输入Union
https://github.com/pytoolz/toolz
从toolz导入唯一、concat、compose
UDF:
def展平\u distinctcol:Union[列,str]->列:
def flant_distinctxss:Union[List[List[str]],None]>List[str]:
返回composelist、unique、concatxss或[]
返回f.udfflaten\u distinct\u,ArrayTypeStringTypecol
窗口定义与以前一样
完整解决方案:
电影录影带
收集清单
.在前几部电影的专栏中,f.收集前几部电影
展平并删除重复项
.使用前几部电影,将前几部电影的内容展平
一班
.带专栏的前几部电影,f.Lag前几部电影,1.Over
仅供演示
.orderByunixdate
结果是:
+---+----+---------+------------+
|名称| unixdate |电影|之前的|电影|
+---+----+---------+------------+
|爱丽丝| 1 |[阿凡达]|空|
|鲍勃| 2 |[法戈,特隆]|空|
|爱丽丝| 4 |[宝贝]|[阿凡达]|
|爱丽丝| 6 |[阿凡达,飞机]|[阿凡达,宝贝]|
|爱丽丝| 7 |[低俗小说]|[阿凡达,宝贝,飞机]|
|鲍勃| 9 |[星球大战][法戈,特隆]|
+---+----+---------+------------+
性能:
我认为,鉴于这些限制,没有有效的方法来解决这一问题。不仅请求的输出需要大量的数据复制数据是二进制编码的,以适应钨格式,因此您可以获得可能的压缩,但对象标识松散,而且还可以获得许多操作,这些操作在给定的Spark计算模型中非常昂贵,包括昂贵的分组和排序
若之前电影的预期大小有限且很小,但通常不可行,那个么这应该是好的
通过为用户保留单一的惰性历史记录,数据复制非常容易解决。这不是用SQL可以完成的事情,但是使用低级RDD操作非常容易
爆炸和收集模式是昂贵的。如果您的要求很严格,但希望提高性能,那么可以使用Scala UDF代替Python UDF