last(ignorenulls)
Forward-fill missing values with last over a window, and the frame that makes it work.
On this page
Show code in
Every code block on the page follows this.
You will learn
- How to forward-fill missing values with last(ignorenulls)
- Which window frame makes it work
- How to backward-fill with first
- Where forward-filling is wrong
F.last("price", ignorenulls=True) over a window ordered by time, with a frame from the start up to the current row, returns the most recent non-null value: a forward fill. first(ignorenulls=True) over the rest of the partition back-fills.What it does
Sensors, prices and statuses often only record a value when it changes. To get a value for every row, carry the last known value forward until a new one appears.
Step by step
readings (sensor A)
| ts | temp |
|---|---|
| 1 | 21 |
| 2 | null |
| 3 | null |
| 4 | 23.5 |
| 5 | null |
forward-filled
| ts | temp | temp_filled |
|---|---|---|
| 1 | 21 | 21 |
| 2 | null | 21 |
| 3 | null | 21 |
| 4 | 23.5 | 23.5 |
| 5 | null | 23.5 |
- 1The window is partitioned by sensor and ordered by ts.
- 2The frame runs from the first row of the partition to the current row.
- 3
last(temp, ignorenulls=True)looks at that frame and returns the latest non-null temp. - 4Sensor B's first row has no earlier value, so it stays null.
Run the example
from pyspark.sql import functions as F from pyspark.sql.window import Window w = Window.partitionBy("sensor").orderBy("ts") frame = w.rowsBetween(Window.unboundedPreceding, Window.currentRow) result = readings.withColumn("temp_filled", F.last("temp", ignorenulls=True).over(frame))
SELECT sensor, ts, temp, LAST(temp, TRUE) OVER ( PARTITION BY sensor ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS temp_filled FROM readings
Switch to PySpark to edit and run this example in your browser.
Spark 3.2 and later also accept the standard form LAST(temp) IGNORE NULLS OVER (...).
Why the explicit frame
The default frame for an ordered window is a range up to the current row, so rows with the same ts are all in each other's frame. With duplicate timestamps, a row could pick up a value from a tied row that comes "after" it. An explicit rowsBetween(unboundedPreceding, currentRow) makes the behaviour exact, and is clearer to read.
Back-filling
To fill from the next known value instead, look forward: F.first("temp", ignorenulls=True) over rowsBetween(Window.currentRow, Window.unboundedFollowing). Combining both, forward fill then back fill, fills everything except partitions with no values at all.
When not to forward-fill
Forward-filling asserts that the value did not change. That is true for a status or a slowly changing price, and false for a sensor that went offline for a day. Consider capping how far a value can be carried, for example by also carrying the timestamp of the last reading with last(F.when(temp.isNotNull(), ts), True) and nulling fills older than a threshold.
Common mistakes
Forgetting ignorenulls
Relying on the default frame with duplicate timestamps
No partitionBy
Key takeaways
- last(col, ignorenulls=True) over a running frame forward-fills.
- Use an explicit rowsBetween(unboundedPreceding, currentRow) frame.
- first(ignorenulls=True) over the following rows back-fills.
- Only forward-fill values that really persist.
Check yourself
3 questions1. What does last("v") (without ignorenulls) return over a running frame when the current v is null?
Show the answer
null. Without ignorenulls, last returns the last row of the frame, which is the current row.
2. Which frame forward-fills?
Show the answer
rowsBetween(unboundedPreceding, currentRow). The frame must include everything up to the current row so the latest non-null value is in it.
3. How do you back-fill?
Show the answer
first(ignorenulls=True) over the current and following rows. first over the rows ahead finds the next known value.
Practice it
Interview problems that use last(ignorenulls): write the PySpark, run it, and get graded on hidden tests.
Go deeper
Primary sources: functions.last · Window functions (SQL)