Spark Window Functions

A window function calculates a value for each row by looking at a group of related rows. A normal groupBy collapses each group into a single row. A window function keeps every row and adds the extra result beside it. Rankings, running totals, and comparisons with the previous row all depend on windows.

The Layman View

Picture a race with several age groups. A judge ranks runners inside each group and keeps the full results list. Each runner still appears on the sheet and gains a new column named rank. A window function creates that column.

Anatomy of a Window

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

w = Window.partitionBy("dept").orderBy(F.col("salary").desc())
  • partitionBy splits rows into independent groups.
  • orderBy sorts the rows inside each group.
  • rowsBetween limits how many neighbouring rows each calculation sees.

Sample Data

data = [("Asha","Sales",52000), ("Ben","Sales",48000), ("Cara","Sales",48000),
        ("Dev","IT",68000), ("Chitra","IT",75000), ("Elena","HR",45000)]
df = spark.createDataFrame(data, ["name","dept","salary"])

Ranking Functions

df.withColumn("row_num",   F.row_number().over(w)) \
  .withColumn("rank",      F.rank().over(w)) \
  .withColumn("dense_rank",F.dense_rank().over(w)).show()
dept   name   salary | row_number  rank  dense_rank
-----------------------------------------------------
Sales  Asha   52000  |     1        1       1
Sales  Ben    48000  |     2        2       2
Sales  Cara   48000  |     3        2       2     <-- tie
IT     Chitra 75000  |     1        1       1
IT     Dev    68000  |     2        2       2
HR     Elena  45000  |     1        1       1
  • row_number gives a unique number to every row.
  • rank gives tied rows the same number and skips the next numbers.
  • dense_rank gives tied rows the same number and skips nothing.

Top N Per Group

Combine row_number with a filter to keep the best rows of every group.

top1 = (df.withColumn("rn", F.row_number().over(w))
          .filter("rn = 1")
          .drop("rn"))
top1.show()

Looking at Neighbouring Rows

The lag function reads the previous row. The lead function reads the next row.

sales = spark.createDataFrame(
    [("Jan",100),("Feb",130),("Mar",120),("Apr",160)], ["month","amount"])

wm = Window.orderBy("month_no")
sales = sales.withColumn("month_no", F.expr("array_position(array('Jan','Feb','Mar','Apr'), month)"))

sales.withColumn("previous", F.lag("amount", 1).over(wm)) \
     .withColumn("change", F.col("amount") - F.col("previous")).show()
month  amount  previous  change
Jan     100     null      null
Feb     130     100        30
Mar     120     130       -10
Apr     160     120        40

Running Totals and Moving Averages

run = Window.orderBy("month_no").rowsBetween(Window.unboundedPreceding, Window.currentRow)
mov = Window.orderBy("month_no").rowsBetween(-1, 0)

sales.withColumn("running_total", F.sum("amount").over(run)) \
     .withColumn("moving_avg",    F.avg("amount").over(mov)).show()
Row Mar with frame rowsBetween(-1, 0):

Jan   100
Feb   130  <-- included
Mar   120  <-- current row, included
Apr   160

moving_avg = (130 + 120) / 2 = 125

Performance Notes

  • A window with partitionBy shuffles data so each group sits on one machine. Pick a partition column with many distinct values.
  • A window without partitionBy pulls all rows into one partition and slows large jobs.
  • Reuse one window definition for several columns.

Choosing the Right Function

GoalFunction
Number every row in a grouprow_number
Rank with gaps after tiesrank
Rank without gapsdense_rank
Compare with the previous rowlag
Compare with the next rowlead
Running totalsum with rowsBetween
Share of the group totalsum over a window without orderBy

Share of Total Example

A window with only partitionBy sums every row of the group. Divide each salary by that sum to find its share.

wd = Window.partitionBy("dept")

df.withColumn("dept_total", F.sum("salary").over(wd)) \
  .withColumn("share_pct", F.round(F.col("salary") / F.col("dept_total") * 100, 1)).show()

Asha earns 52000 inside a Sales total of 148000, which gives a share of 35.1 percent. Window functions deliver this result in a single pass and avoid a separate join.