SparkSession and Spark DataFrames

First lesson of the Distributed Processing with Spark course, free to read: about 20 minutes, 3 exercises and 3 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Explain the role of the driver, the executors, and the cluster manager
  • Create a SparkSession and read a file with an explicit schema
  • Select, filter, and enrich a DataFrame with the functions of pyspark.sql.functions

Driver, executors, and clusterSparkSessionSpark DataFrameSchema and file readingColumn functions

When pandas is no longer enough

pandas loads all the data into the memory of a single machine. Beyond a few tens of gigabytes, or when a job must read thousands of files from a data lake, you need to distribute the work. Apache Spark is the most widely used distributed computing engine for this: it splits data into partitions and processes them in parallel across several machines, in memory as much as possible. You use it from Python with PySpark. This course relies on the DataFrame API of Spark 3.5 and 4.x.

Spark is not always the right answer: for a few million rows, pandas or DuckDB on one machine is simpler and often faster. Spark becomes relevant when volume or parallelism demands it.

The architecture in three roles

  • The driver runs your script, builds the execution plan, and distributes the work.
  • The executors are processes launched on the cluster’s machines; each one processes tasks, one task corresponding to one data partition.
  • The cluster manager (Kubernetes, YARN, Spark standalone mode, or a managed service) allocates resources.

Locally, everything runs in a single process (master("local[*]") uses all cores): convenient for learning, with exactly the same API.

Creating a session and reading data

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

spark = (SparkSession.builder
         .appName("ventes")
         .master("local[*]")          # on a cluster, this setting is provided at launch
         .getOrCreate())

schema = "id INT, jour DATE, region STRING, produit STRING, montant DOUBLE"

ventes = (spark.read
          .option("header", True)
          .option("mode", "DROPMALFORMED")   # ignore unreadable rows
          .schema(schema)
          .csv("s3a://lake/brut/ventes/"))   # a whole folder: all its files

ventes.printSchema()
ventes.show(5)

The explicit schema avoids two problems of inferSchema=True: an extra pass over the data to guess the types, and wrongly guessed types (a postal code read as an integer loses its leading zero). The read mode decides what happens to malformed rows: PERMISSIVE (the default) keeps them, setting to null whatever cannot be read; DROPMALFORMED discards them; FAILFAST stops the read at the first error.

For data that is already prepared, prefer Parquet: a columnar, compressed format that carries its own schema (spark.read.parquet(...)).

Transforming a DataFrame

The API resembles pandas, but with a declarative, SQL-inspired style:

resultat = (ventes
    .filter(F.col("montant") > 0)
    .withColumn("ttc", F.round(F.col("montant") * 1.2, 2))
    .withColumn("gamme", F.when(F.col("montant") >= 200, "haut").otherwise("standard"))
    .select("jour", "region", "produit", "ttc", "gamme"))

Three rules:

  1. A DataFrame is immutable: withColumn does not modify ventes, it returns a new DataFrame. So you must reassign or chain.
  2. Column expressions (F.col, F.when, F.to_date, F.upper…) run inside Spark’s engine, optimized and distributed. A Python function applied row by row (UDF) is much slower: reserve it for cases with no native equivalent.
  3. Nothing has been computed yet at this point: Spark waits for an action (show, count, write…), as we will see in the next lesson.

Bringing data back to the driver

show(n) displays a few rows; count() counts; collect() brings all the rows back into the driver’s memory. On a real volume, collect() crashes the driver: prefer limit(n), show(), or write the result (write.parquet). To pass an already aggregated, small result to pandas, toPandas() is fine.

Key takeaways

  • Driver = conductor, executors = workers, one task per partition.
  • Explicit schema and a chosen read mode; Parquet for prepared data.
  • Immutable DataFrame, F functions rather than row-by-row Python, and never collect() on a large volume.

Exercises

Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.

  1. CodeDifficulty 1Python in the browser2 hints

    Pipeline in pure Python

    Reproduce in pure Python this Spark pipeline applied to a list of dictionaries:

    (df.filter(F.col("montant") > 0)
       .withColumn("ttc", F.round(F.col("montant") * 1.2, 2))
       .withColumn("gamme", F.when(F.col("montant") >= 200, "haut").otherwise("standard"))
       .select("region", "ttc", "gamme"))

    Write pipeline(lignes) that returns a new list of dictionaries with only the keys region, ttc, gamme, without modifying lignes.

    def pipeline(lignes):
        ...
  2. CodeDifficulty 2Python in the browser2 hints

    CSV read modes

    Simulate reading a CSV without a header, using a schema. schema is a list of (nom, type) pairs where type is int, float, or str. Write lire(lignes, schema, mode="PERMISSIVE"):

    • each line is split on , and each value converted with its type;
    • a line is malformed if its number of fields differs from the schema or if a conversion fails;
    • PERMISSIVE: the line is kept, unreadable or missing values are None (extra fields are ignored);
    • DROPMALFORMED: the line is discarded;
    • FAILFAST: raise ValueError.

    Return a list of dictionaries.

    def lire(lignes, schema, mode="PERMISSIVE"):
        ...
  3. Multiple choiceDifficulty 11 hint

    Why an explicit schema?

    What is the main benefit of .schema(...) compared with inferSchema=True on a large volume?

    • It compresses the data
    • It avoids an extra read of the data and guarantees the expected types
    • It allows reading Excel files
    • It enables caching

End-of-lesson quiz

  1. Who processes the data partitions in a Spark cluster?

    • The driver
    • The executors
    • The cluster manager
  2. What does df.withColumn("x", F.lit(1)) do to df?

    • It adds the column to df
    • It returns a new DataFrame; df is unchanged
    • It raises an error
  3. Why avoid collect() on a DataFrame of 500 million rows?

    • Because collect is deprecated
    • Because all the rows are brought back into the driver's memory
    • Because it deletes the data

The rest of the course

  1. Transformations, Actions, and Lazy Evaluation20 min
  2. Partitions, Shuffle, and Joins20 min
  3. Spark SQL and Window Functions20 min
  4. Optimizing a Spark Job20 min

Distributed Processing with Spark · Advanced · 1 h 40 min