【问题标题】:Pyspark: how to duplicate a row n time in dataframe?Pyspark:如何在数据框中复制一行 n 次?
【发布时间】:2018-11-10 11:50:07
【问题描述】:

我有一个这样的数据框,如果列 n 大于一,我想复制行 n 次:

A   B   n  
1   2   1  
2   9   1  
3   8   2    
4   1   1    
5   3   3 

然后像这样变换:

A   B   n  
1   2   1  
2   9   1  
3   8   2
3   8   2       
4   1   1    
5   3   3 
5   3   3 
5   3   3 

我想我应该使用explode,但我不明白它是如何工作的......
谢谢

【问题讨论】:

  • @Learningstatsbyexample : 它在 python 中

标签: python pyspark bigdata


【解决方案1】:

explode 函数为给定数组或映射中的每个元素返回一个新行。

利用此功能的一种方法是使用udf 为每一行创建大小为n 的列表。然后分解生成的数组。

from pyspark.sql.functions import udf, explode
from pyspark.sql.types import ArrayType, IntegerType
    
df = spark.createDataFrame([(1,2,1), (2,9,1), (3,8,2), (4,1,1), (5,3,3)] ,["A", "B", "n"]) 

+---+---+---+
|  A|  B|  n|
+---+---+---+
|  1|  2|  1|
|  2|  9|  1|
|  3|  8|  2|
|  4|  1|  1|
|  5|  3|  3|
+---+---+---+

# use udf function to transform the n value to n times
n_to_array = udf(lambda n : [n] * n, ArrayType(IntegerType()))
df2 = df.withColumn('n', n_to_array(df.n))

+---+---+---------+
|  A|  B|        n|
+---+---+---------+
|  1|  2|      [1]|
|  2|  9|      [1]|
|  3|  8|   [2, 2]|
|  4|  1|      [1]|
|  5|  3|[3, 3, 3]|
+---+---+---------+ 

# now use explode  
df2.withColumn('n', explode(df2.n)).show()

+---+---+---+ 
| A | B | n | 
+---+---+---+ 
|  1|  2|  1| 
|  2|  9|  1| 
|  3|  8|  2| 
|  3|  8|  2| 
|  4|  1|  1| 
|  5|  3|  3| 
|  5|  3|  3| 
|  5|  3|  3| 
+---+---+---+ 

【讨论】:

  • 我更喜欢 @jxc 对 spark 2.4+ 的回答,因为它使用所有内置插件而不是 udf。
  • @DVL 不幸的是,我们中的一些人仍然坚持使用以前的 spark 版本......
【解决方案2】:

我认为@Ahmed 的udf 回答是最好的方法,但这里有另一种方法,对于小型n 可能同样好或更好:

首先,在整个DataFrame上收集n的最大值:

max_n = df.select(f.max('n').alias('max_n')).first()['max_n']
print(max_n)
#3

现在为长度为max_n 的每一行创建一个数组,其中包含range(max_n) 中的数字。此中间步骤的输出将产生一个 DataFrame,如下所示:

df.withColumn('n_array', f.array([f.lit(i) for i in range(max_n)])).show()
#+---+---+---+---------+
#|  A|  B|  n|  n_array|
#+---+---+---+---------+
#|  1|  2|  1|[0, 1, 2]|
#|  2|  9|  1|[0, 1, 2]|
#|  3|  8|  2|[0, 1, 2]|
#|  4|  1|  1|[0, 1, 2]|
#|  5|  3|  3|[0, 1, 2]|
#+---+---+---+---------+

现在我们分解n_array 列,并过滤以仅保留数组中小于n 的值。这将确保我们拥有每行的n 副本。最后我们删除分解后的列得到最终结果:

df.withColumn('n_array', f.array([f.lit(i) for i in range(max_n)]))\
    .select('A', 'B', 'n', f.explode('n_array').alias('col'))\
    .where(f.col('col') < f.col('n'))\
    .drop('col')\
    .show()
#+---+---+---+
#|  A|  B|  n|
#+---+---+---+
#|  1|  2|  1|
#|  2|  9|  1|
#|  3|  8|  2|
#|  3|  8|  2|
#|  4|  1|  1|
#|  5|  3|  3|
#|  5|  3|  3|
#|  5|  3|  3|
#+---+---+---+

但是,我们正在为每一行创建一个 max_n 长度数组,而不是 udf 解决方案中的一个 n 长度数组。我目前还不清楚这将如何扩展与 udf 对于大型 max_n,但我怀疑 udf 会胜出。

【讨论】:

    【解决方案3】:

    使用 Spark 2.4.0+,使用内置函数更容易:array_repeat + explode

    from pyspark.sql.functions import expr
    
    df = spark.createDataFrame([(1,2,1), (2,9,1), (3,8,2), (4,1,1), (5,3,3)], ["A", "B", "n"])
    
    new_df = df.withColumn('n', expr('explode(array_repeat(n,int(n)))'))
    
    >>> new_df.show()
    +---+---+---+
    |  A|  B|  n|
    +---+---+---+
    |  1|  2|  1|
    |  2|  9|  1|
    |  3|  8|  2|
    |  3|  8|  2|
    |  4|  1|  1|
    |  5|  3|  3|
    |  5|  3|  3|
    |  5|  3|  3|
    +---+---+---+
    

    【讨论】:

    • 在pyspark中使用array_repeat API函数时,第二个参数(count)怎么引用n呢?尝试使用F.col() 时,我得到Column is not iterable
    • @DavidFoster,这不能使用 pyspark API 函数来完成,请检查这个 Frequent question。使用 SQL 表达式,这不是问题,而且 IMO 代码通常更简洁且易于维护。
    • 感谢您确认它不受支持。当底层 SQL 很简单时,Python API 不支持这一点似乎很奇怪。这是一个简洁的观点,但是,在这种情况下,它看起来就像 Python 解决方案一样,我的 IDE 会将其视为字符串而不是函数/方法等。
    猜你喜欢
    • 2023-03-31
    • 2018-08-10
    • 1970-01-01
    • 2018-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-03
    • 2018-12-07
    相关资源
    最近更新 更多