在pyspark中,根据变量字段进行分组,并添加一个计数器以获取特定值(变量更改时重置)

时间:2019-07-10 14:53:16

标签: python-3.x pyspark apache-spark-sql user-defined-functions

从熊猫数据框创建一个火花数据框

import pandas as pd
df = pd.DataFrame({"b": ['A','A','A','A','B', 'B','B','C','C','D','D', 'D','D','D','D','D','D','D','D','D'],"Sno": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],"a": [3,-4,2, -1, -3, -1,-7,-6, 1, 1, -1, 1,4,5,-3,2,3,4, -1, -2]})

df2=spark.createDataFrame(df) 

接下来,我在字段'b'上使用窗口分区

from pyspark.sql import window
win_spec = (window.Window.partitionBy(['b']).orderBy("Sno").rowsBetween(window.Window.unboundedPreceding, 0))

根据值添加一个正值,负值字段,并创建一个lambda函数

df2 = df2.withColumn("pos_neg",col("a") <0)
pos_neg_func =udf(lambda x: ((x) & (x != x.shift())).cumsum())

尝试创建新列(该计数器是负值的计数器,但在变量'b'中。因此,当'b'中的字段更改时,计数器重新启动。此外,如果存在连续的-ve值,则应将它们视为a单值,计数器变化1

df3 = (df2.select('pos_neg',pos_neg_func('pos_neg').alias('val')))

我希望输出为

      b  Sno  a    val  val_2
0   A    1  3  False      0
1   A    2 -4   True      1
2   A    3  2  False      1
3   A    4 -1   True      2
4   B    5 -3   True      1
5   B    6 -1   True      1
6   B    7 -7   True      1
7   C    8 -6   True      1
8   C    9  1  False      1
9   D   10  1  False      0
10  D   11 -1   True      1
11  D   12  1  False      1
12  D   13  4  False      1
13  D   14  5  False      1
14  D   15 -3   True      2
15  D   16  2  False      2
16  D   17  3  False      2
17  D   18  4  False      2
18  D   19 -1   True      3
19  D   20 -2   True      3

在python中,一个简单的函数如下所示:

df['val'] = df.groupby('b')['pos_neg'].transform(lambda x: ((x) & (x != x.shift())).cumsum())

josh-friedlander在上面的代码中提供了支持

1 个答案:

答案 0 :(得分:1)

Pyspark没有移位功能,但是您可以使用lag窗口功能,该功能为您提供当前行之前的行。如果val列的值为pos_neg并且前一个True的值为{,则第一个窗口(称为w)将pos_neg列的值设置为1。 {1}},否则为0。 在第二个窗口(称为w2)中,我们计算出累加和以获得所需的

False

输出:

import pandas as pd
import pyspark.sql.functions as F
from pyspark.sql import Window

df = pd.DataFrame({"b": ['A','A','A','A','B', 'B','B','C','C','D','D', 'D','D','D','D','D','D','D','D','D'],"Sno": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],"a": [3,-4,2, -1, -3, -1,-7,-6, 1, 1, -1, 1,4,5,-3,2,3,4, -1, -2]})

df2=spark.createDataFrame(df) 

w = Window.partitionBy('b').orderBy('Sno')
w2 = Window.partitionBy('b').rowsBetween(Window.unboundedPreceding, 0).orderBy('Sno')

df2 = df2.withColumn("pos_neg",col("a") <0)

df2 = df2.withColumn('val', F.when((df2.pos_neg == True) & (F.lag('pos_neg', default=False).over(w) == False), 1).otherwise(0))
df2 = df2.withColumn('val',  F.sum('val').over(w2))

df2.show()

您可能想知道为什么必须有一个列来允许我们对数据集进行排序。让我尝试用一​​个例子来解释一下。熊猫读取了以下数据,并为其分配了索引(左列)。您想计算+---+---+---+-------+---+ |Sno| a| b|pos_neg|val| +---+---+---+-------+---+ | 5| -3| B| true| 1| | 6| -1| B| true| 1| | 7| -7| B| true| 1| | 10| 1| D| false| 0| | 11| -1| D| true| 1| | 12| 1| D| false| 1| | 13| 4| D| false| 1| | 14| 5| D| false| 1| | 15| -3| D| true| 2| | 16| 2| D| false| 2| | 17| 3| D| false| 2| | 18| 4| D| false| 2| | 19| -1| D| true| 3| | 20| -2| D| true| 3| | 8| -6| C| true| 1| | 9| 1| C| false| 1| | 1| 3| A| false| 0| | 2| -4| A| true| 1| | 3| 2| A| false| 1| | 4| -1| A| true| 2| +---+---+---+-------+---+ True的出现,而不想计算连续pos_neg的消耗。该逻辑导致进入True列,如下所示:

val2

...但是取决于它从熊猫(行顺序)获得的索引。当您更改行的顺序(以及相应的熊猫索引)时,将逻辑应用于同一行时,您会得到不同的结果,只是因为顺序不同:

    b  Sno  a   pos_neg  val_2
0   A    1  3  False      0
1   A    2 -4   True      1
2   A    3  2  False      1
3   A    4 -1   True      2
4   A    5 -5   True      2

您看到行的顺序很重要。您可能现在想知道pyspark为什么不像pandas那样创建索引。这是因为spark将您的数据保留在群集中分布的几个分区中,并且甚至取决于您的数据源,甚至能够读取分布式数据。因此,在读取数据期间无法添加索引。您可以在使用monotonically_increasing_id函数读取数据后添加一个,但是由于读取过程,数据与数据源的顺序可能已经不同。

您的 b Sno a pos_neg val_2 0 A 1 3 False 0 1 A 3 2 False 0 2 A 2 -4 True 1 3 A 4 -1 True 1 4 A 5 -5 True 1 列避免了此问题,并保证对于相同的数据(确定性),您将始终获得相同的结果。