pyspark:自动填充隐式缺失值

时间:2020-06-02 06:44:19

标签: pyspark

我有一个数据框

user day amount
a 2 10
a 1 14
a 4 5
b 1 4

您看到,day的最大值为4,最小值为1。我想在所有用户的所有缺失天中为0列填充amount,这样上面的数据帧就会变成

user day amount
    a 2 10
    a 1 14
    a 4 5
    a 3 0
    b 1 4
    b 2 0
    b 3 0
    b 4 0

我如何在PySpark中做到这一点?非常感谢。

2 个答案:

答案 0 :(得分:4)

这是一种方法。您可以先获取最小值和最大值,然后在user列上分组并进行数据透视,然后填写缺少的列并将所有null填充为0,然后将它们堆叠回去:

min_max = df.agg(F.min("day"),F.max("day")).collect()[0]
df1 = df.groupBy("user").pivot("day").agg(F.first("amount").alias("amount")).na.fill(0)

missing_cols = [F.lit(0).alias(str(i)) for i in range(min_max[0],min_max[1]+1) 
                                                if str(i) not in df1.columns ]
df1 = df1.select("*",*missing_cols)

#+----+---+---+---+---+
#|user|  1|  2|  4|  3|
#+----+---+---+---+---+
#|   b|  4|  0|  0|  0|
#|   a| 14| 10|  5|  0|
#+----+---+---+---+---+

#the next step is inspired from https://stackoverflow.com/a/37865645/9840637
arr = F.explode(F.array([F.struct(F.lit(c).alias("day"), F.col(c).alias("amount"))
                                           for c in df1.columns[1:]])).alias("kvs")
(df1.select(["user"] + [arr])
    .select(["user"]+ ["kvs.day", "kvs.amount"]).orderBy("user")).show()

+----+---+------+
|user|day|amount|
+----+---+------+
|   a|  1|    14|
|   a|  2|    10|
|   a|  4|     5|
|   a|  3|     0|
|   b|  1|     4|
|   b|  2|     0|
|   b|  4|     0|
|   b|  3|     0|
+----+---+------+

请注意,由于列日已被调整,因此dtype可能已更改,因此您可能必须cast将它们恢复为原始dtype

答案 1 :(得分:2)

执行此操作的另一种方法是使用 sequence array functions explode (spark2.4+)

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

w=Window().partitionBy(F.lit(0))

df.withColumn("boundaries", F.sequence(F.min("day").over(w),F.max("day").over(w),F.lit(1)))\
  .groupBy("user").agg(F.collect_list("day").alias('day'),F.collect_list("amount").alias('amount')\
   ,F.first("boundaries").alias("boundaries")).withColumn("boundaries", F.array_except("boundaries","day"))\
  .withColumn("day",F.flatten(F.array("day","boundaries"))).drop("boundaries")\
  .withColumn("zip", F.explode(F.arrays_zip("day","amount")))\
  .select("user","zip.day", F.when(F.col("zip.amount").isNull(),\
                                   F.lit(0)).otherwise(F.col("zip.amount")).alias("amount")).show()
#+----+---+------+
#|user|day|amount|
#+----+---+------+
#|   a|  2|    10|
#|   a|  1|    14|
#|   a|  4|     5|
#|   a|  3|     0|
#|   b|  1|     4|
#|   b|  2|     0|
#|   b|  3|     0|
#|   b|  4|     0|
#+----+---+------+