3. Transformations, actions, and lazy evaluation

📖 Reading · 5 min
💡 Every code box below is live — edit it and hit Run.

This is the one mental model that makes PySpark click. Every DataFrame method is one of two kinds:

  • Transformation — describes a change and returns a new DataFrame. select, filter, withColumn, groupBy, joins. These do not run anything. They just build up a recipe.
  • Action — asks for a result, which forces the recipe to actually execute. show, count, collect, write.

Spark is lazy: it stacks up your transformations without touching data, then — the moment you call an action — plans the whole chain and runs it in one go. This is what lets it optimize across your entire pipeline instead of one step at a time.

See it happen

Building the transformation prints nothing — no data moves. Only the action at the end does work:

films = spark.createDataFrame(
    [(1, "Chinatown", 1974, 8.1), (2, "Alien", 1979, 8.5), (3, "Coco", 2017, 8.4)],
    ["id", "title", "year", "rating"],
)

hits = films.filter(F.col("rating") >= 8.4)   # transformation — nothing runs
print("built the recipe, no data touched yet")

hits.show()                                    # action — NOW it runs

The recipe is a query

Under the hood, your chain of transformations compiles to a query. You can see it — every DataFrame can show you the query it built with .sql():

films = spark.createDataFrame(
    [(1, "Chinatown", 1974, 8.1), (2, "Alien", 1979, 8.5), (3, "Coco", 2017, 8.4)],
    ["id", "title", "year", "rating"],
)

recipe = films.filter(F.col("rating") >= 8.4).select("title")
print(recipe.sql())   # the SQL your DataFrame chain becomes

That's the deep truth this whole course leans on: a DataFrame pipeline is a query. If you know SQL, you already know what PySpark is doing; if you don't, you'll pick up the shape of it here. (Module 7 makes the connection explicit — you'll even run SQL directly.)

Why laziness matters in practice

Because nothing runs until an action, a bug in a transformation often doesn't blow up where you wrote it — it surfaces later, at the show() or count(). When an error points at an action, the real mistake is usually somewhere up the chain that built the DataFrame.

Your turn

The setup gives you films. Build a DataFrame (assign to result) of the title and year of every film from the year 2000 or later.

films = spark.createDataFrame(
    [(1, "Chinatown", 1974, 8.1), (2, "Alien", 1979, 8.5), (3, "Coco", 2017, 8.4)],
    ["id", "title", "year", "rating"],
)
result = films  # <- keep year >= 2000, select title & year
result.show()
result = films.filter(F.col("year") >= 2000).select("title", "year")