28. Practical window patterns
Three patterns you'll use constantly — the reason window functions are worth mastering.
1. Deduplicate — keep one row per key
dropDuplicates keeps an arbitrary row per key. When you want a
specific one (the latest, the cheapest), use row_number and keep
rank 1:
# keep only the FIRST row for each order_id (kills the duplicate 1021)
w = Window.partitionBy("order_id").orderBy("order_date")
deduped = orders.withColumn("rn", F.row_number().over(w)).filter(F.col("rn") == 1).drop("rn")
print("41 ->", deduped.count())
Change the orderBy and you control which duplicate survives — order by
a timestamp desc() to keep the newest.
2. Top-N per group
row_number per partition, filter to <= N. The top 2 priciest products
in every category:
w = Window.partitionBy("category").orderBy(F.col("unit_price").desc())
orders.dropDuplicates().withColumn("rn", F.row_number().over(w)).filter(
F.col("rn") <= 2
).select("category", "product", "unit_price").show(12)
This is the answer to "top N per group" — a groupBy can't do it
(it collapses the rows you want to keep).
3. Sessionization / streaks
Flag where a new "session" starts (a gap bigger than some threshold), then cumulative-sum the flags into a session id. Here: a customer's orders more than 3 days apart start a new streak.
w = Window.partitionBy("customer_id").orderBy("order_date")
sessions = (
orders.dropDuplicates()
.withColumn("gap", F.datediff("order_date", F.lag("order_date").over(w)))
.withColumn("is_new", F.when((F.col("gap") > 3) | F.col("gap").isNull(), 1).otherwise(0))
.withColumn("session", F.sum("is_new").over(w))
)
sessions.select("customer_id", "order_date", "gap", "session").show(12)
The lag → flag → cumulative sum trio is the general sessionization
recipe — it works for clickstreams, logins, any event stream.
Your turn
Get the single most expensive product per category: rank by
unit_price descending within category with row_number, keep rank 1,
and return category, product, unit_price, from deduplicated
orders. Assign to result.
w = Window.partitionBy("category").orderBy(F.col("unit_price").desc())
result = orders # <- dropDuplicates, row_number().over(w) AS rn, keep rn == 1, select 3 cols
result.show()
w = Window.partitionBy("category").orderBy(F.col("unit_price").desc())
result = (
orders.dropDuplicates()
.withColumn("rn", F.row_number().over(w))
.filter(F.col("rn") == 1)
.select("category", "product", "unit_price")
)