ML Engineer MasterClass (October) | 4 seats left

Spark · Previous and next rows
AmazonAmazon Analytics
Amazon · Window Functions

Previous and next rows

Read neighboring rows and calculate differences with explicit boundary handling.

Step 1 of 6 · Learn

Look in either direction

lag reads an earlier row; lead reads a later row in the window order. Here order_id defines the sequence, not a date or elapsed time. An offset of 1 means one row away. At a boundary with no neighbor, Spark returns null. Partitioning would restart this sequence for each group.

Lesson reference: PySpark and Scala

Look in either direction

lag reads an earlier row; lead reads a later row in the window order. Here order_id defines the sequence, not a date or elapsed time. An offset of 1 means one row away. At a boundary with no neighbor, Spark returns null. Partitioning would restart this sequence for each group.

PySpark example

from pyspark.sql import Window
from pyspark.sql.functions import lag

result = orders.withColumn(
    "neighbor_total", lag("total", 1).over(Window.orderBy("order_id"))
).select("order_id", "total", "neighbor_total").orderBy("order_id")
result.show(truncate=False)

Scala example

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._

val result = orders.withColumn(
    "neighbor_total", lag("total", 1).over(Window.orderBy("order_id"))
).select("order_id", "total", "neighbor_total").orderBy("order_id")
result.show(false)

Change the offset

The offset is a row count within the ordered partition. lag(total, 1) reads one row back; increasing the offset looks farther back without changing the current row. Skipped order IDs do not create extra rows. The first rows return null when the requested neighbor does not exist.

PySpark example

from pyspark.sql import Window
from pyspark.sql.functions import lag

result = orders.withColumn(
    "neighbor_total", lag("total", 1).over(Window.orderBy("order_id"))
).select("order_id", "total", "neighbor_total").orderBy("order_id")
result.show(truncate=False)

Scala example

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._

val result = orders.withColumn(
    "neighbor_total", lag("total", 1).over(Window.orderBy("order_id"))
).select("order_id", "total", "neighbor_total").orderBy("order_id")
result.show(false)

Calculate a difference

Subtracting the previous total gives the change between adjacent rows. The first change is null because subtraction with null returns null. Treating a missing previous value as zero is a separate business choice: it makes the first change equal to its current total, not zero.

PySpark example

from pyspark.sql import Window
from pyspark.sql.functions import col, lag

result = orders.withColumn(
    "previous_total", lag("total", 1).over(Window.orderBy("order_id"))
).withColumn(
    "change", col("total") - col("previous_total")
).select("order_id", "total", "change").orderBy("order_id")
result.show(truncate=False)

Scala example

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._

val result = orders.withColumn(
    "previous_total", lag("total", 1).over(Window.orderBy("order_id"))
).withColumn(
    "change", col("total") - col("previous_total")
).select("order_id", "total", "change").orderBy("order_id")
result.show(false)
example.pyPySpark
Ordered rowsRead a neighboring valueKeep the current row
Window sequenceOrder IDs in window order1001 → 1002 → 1003 → 1004 → 1005 → 1006
Source orders6 rows
order_idcustomer_idstatustotalitem_count
1001101Delivered89.52
1002102Shipped1493
1003101Cancelled351
1004103Delivered219.994
1005104Delivered49.991
1006105Shipped1202
Look in either direction
Result6 rows
order_idtotalneighbor_total
100189.5NULL
100214989.5
100335149
1004219.9935
100549.99219.99
100612049.99
Order 1001 has no previous row. Order 1002 reads 89.50 from order 1001.

The example is loaded in the editor. Run it as written, then try a small change.

Runs on the Spark backend. First startup may take a moment.

Run your code to see the result.