Calculate running total in Pyspark dataframes and break the loop when a condition occurs

Viewed 171

I have a spark dataframe, where I need to calculate a running total based on the current and previous row sum of amount valued based on the col_x. when ever there is occurance of negative amount in col_y, I should break the running total of previous records, and start doing the running total from current row.

Sample dataset:

enter image description here

The expected output should be like:

enter image description here

How to acheive this with dataframes using pyspark?

2 Answers

I am hoping in real scenario you will be having a timestamp column to do ordering of the data, I am ordering the data using line number with zipindex for the explanation basis here.

from pyspark.sql.window import Window
import pyspark.sql.functions as f
from pyspark.sql.functions import *
from pyspark.sql.types import *
data = [
           ("ID1", -17.9),
           ("ID1", 21.9),
           ("ID1", 236.9),
           ("ID1", 4.99),
           ("ID1", 610.2),
           ("ID1", -35.8),
           ("ID1",21.9),
           ("ID1",17.9)
            ]
    
schema = StructType([
  StructField('Col_x', StringType(),True), \
  StructField('Col_y', FloatType(),True)
])
df = spark.createDataFrame(data=data, schema=schema)
df_1 = df.rdd.map(lambda r: r).zipWithIndex().toDF(['value', 'index'])
df_1.createOrReplaceTempView("valuewithorder")
w = Window.partitionBy('Col_x').orderBy('index')
w1 = Window.partitionBy('Col_x','group').orderBy('index')
df_final=spark.sql("select value.Col_x,round(value.Col_y,1) as Col_y, index from valuewithorder")
"""Group The data into different groups based on the negative value existance"""
df_final = df_final.withColumn("valueChange",(f.col('Col_y')<0).cast("int")) \
        .fillna(0,subset=["valueChange"])\
        .withColumn("indicator",(~((f.col("valueChange") == 0))).cast("int"))\
        .withColumn("group",f.sum(f.col("indicator")).over(w.rangeBetween(Window.unboundedPreceding, 0)))

"""Cumlative sum with idfferent parititon of group and col_x"""
df_cum_sum = df_final.withColumn("Col_z", sum('Col_y').over(w1))
df_cum_sum.createOrReplaceTempView("FinalCumSum")
df_cum_sum = spark.sql("select Col_x , Col_y ,round(Col_z,1) as Col_z from FinalCumSum")
df_cum_sum.show()

Results of intermedite data set and results

>>> df_cum_sum.show()
+-----+-----+-----+
|Col_x|Col_y|Col_z|
+-----+-----+-----+
|  ID1|-17.9|-17.9|
|  ID1| 21.9|  4.0|
|  ID1|236.9|240.9|
|  ID1|  5.0|245.9|
|  ID1|610.2|856.1|
|  ID1|-35.8|-35.8|
|  ID1| 21.9|-13.9|
|  ID1| 17.9|  4.0|
+-----+-----+-----+

>>> df_final.show()
+-----+-----+-----+-----------+---------+-----+
|Col_x|Col_y|index|valueChange|indicator|group|
+-----+-----+-----+-----------+---------+-----+
|  ID1|-17.9|    0|          1|        1|    1|
|  ID1| 21.9|    1|          0|        0|    1|
|  ID1|236.9|    2|          0|        0|    1|
|  ID1|  5.0|    3|          0|        0|    1|
|  ID1|610.2|    4|          0|        0|    1|
|  ID1|-35.8|    5|          1|        1|    2|
|  ID1| 21.9|    6|          0|        0|    2|
|  ID1| 17.9|    7|          0|        0|    2|
+-----+-----+-----+-----------+---------+-----+

Another way

Create Index

df = df.rdd.map(lambda r: r).zipWithIndex().toDF(['value', 'index'])

Regenerate Columns

df = df.select('index', 'value.*')#.show()

Create groups bounded by negative values

w=Window.partitionBy().orderBy('index').rowsBetween(-sys.maxsize,0)
df=df.withColumn('cat', f.min('Col_y').over(w))

Cumsum within groups

    y=Window.partitionBy('cat').orderBy(f.asc('index')).rowsBetween(Window.unboundedPreceding,0)
df.withColumn('cumsum', f.round(f.sum('Col_y').over(y),2)).sort('index').drop('cat','index').show()

Outcome

+-----+-------------------+------+
|Col_x|              Col_y|cumsum|
+-----+-------------------+------+
|  ID1|-17.899999618530273| -17.9|
|  ID1| 21.899999618530273|   4.0|
|  ID1| 236.89999389648438| 240.9|
|  ID1|  4.989999771118164|245.89|
|  ID1|  610.2000122070312|856.09|
|  ID1| -35.79999923706055| -35.8|
|  ID1| 21.899999618530273| -13.9|
|  ID1| 17.899999618530273|   4.0|
+-----+-------------------+------+
Related