DE Spark DataFrames

The DataFrame API is the primary way data engineers interact with Apache Spark. It provides a structured, tabular view of distributed data — similar to a SQL table or a pandas DataFrame — but backed by Spark's distributed processing engine. Engineers write familiar operations like filter, group, join, and sort, and Spark executes them in parallel across a cluster.

What Is a Spark DataFrame

A Spark DataFrame is a distributed collection of rows organized into named, typed columns. It represents a large dataset split across many machines in the cluster. From the engineer's perspective, it looks like a single table. Under the hood, Spark manages the partitioning, parallel execution, and fault tolerance automatically.

The Spreadsheet Across Many Computers Analogy

Imagine a spreadsheet with one billion rows. No laptop can open it — it is too large for RAM. Now imagine splitting that spreadsheet into 1,000 equal chunks and loading each chunk onto a different computer. All 1,000 computers can apply a filter simultaneously. When done, the results combine. A Spark DataFrame is that distributed spreadsheet — you interact with it as if it were one object, but it lives across many machines at once.

Creating a DataFrame

from pyspark.sql import SparkSession

# Start a Spark session (the entry point to Spark)
spark = SparkSession.builder \
    .appName("SalesAnalysis") \
    .getOrCreate()

# Read a CSV file from cloud storage into a DataFrame
df = spark.read \
    .option("header", "true") \
    .option("inferSchema", "true") \
    .csv("s3://my-bucket/sales/orders.csv")

# See the schema (column names and data types)
df.printSchema()

# Preview first 5 rows
df.show(5)

Common DataFrame Operations

select(): Choose Columns

# Select specific columns
df.select("order_id", "customer_id", "amount").show()

filter() / where(): Filter Rows

# Keep only orders above 1000
filtered = df.filter(df.amount > 1000)

# Multiple conditions
filtered = df.filter((df.amount > 1000) & (df.status == "completed"))

groupBy() and agg(): Aggregate

from pyspark.sql.functions import sum, count, avg

# Total revenue and order count per city
city_summary = df.groupBy("city") \
    .agg(
        sum("amount").alias("total_revenue"),
        count("order_id").alias("order_count"),
        avg("amount").alias("avg_order_value")
    )

city_summary.orderBy("total_revenue", ascending=False).show()

withColumn(): Add or Transform Columns

from pyspark.sql.functions import col, when, upper

# Add a new column based on logic
df = df.withColumn(
    "order_tier",
    when(col("amount") >= 5000, "High")
    .when(col("amount") >= 1000, "Medium")
    .otherwise("Low")
)

# Transform an existing column
df = df.withColumn("city_upper", upper(col("city")))

join(): Combine DataFrames

# Read a customers reference table
customers_df = spark.read.parquet("s3://my-bucket/customers/")

# Join orders with customers on customer_id
enriched = df.join(customers_df, on="customer_id", how="inner")

# Left join example
enriched = df.join(customers_df, on="customer_id", how="left")

dropDuplicates(): Remove Duplicates

# Remove exact duplicate rows
df_deduped = df.dropDuplicates()

# Remove duplicates based on specific columns (keep first occurrence)
df_deduped = df.dropDuplicates(["order_id"])

Using SQL with DataFrames

Data engineers who prefer SQL can register a DataFrame as a temporary view and query it with standard SQL syntax. Spark compiles the SQL into the same distributed execution plan as the DataFrame API.

# Register the DataFrame as a SQL view
df.createOrReplaceTempView("orders")

# Run SQL against the view
result = spark.sql("""
    SELECT
        city,
        SUM(amount)   AS total_revenue,
        COUNT(*)      AS order_count
    FROM orders
    WHERE status = 'completed'
    GROUP BY city
    ORDER BY total_revenue DESC
""")

result.show()

Writing DataFrames to Storage

After processing, DataFrames write to cloud storage or a database. Choosing the right format and partition strategy significantly impacts how fast downstream queries run.

# Write as Parquet (columnar, compressed — recommended for analytics)
df.write \
    .mode("overwrite") \
    .partitionBy("sale_year", "sale_month") \
    .parquet("s3://my-bucket/processed/orders/")

# Write as Delta Lake table (adds ACID transactions)
df.write \
    .format("delta") \
    .mode("append") \
    .save("s3://my-bucket/delta/orders/")

# Write to a database table
df.write \
    .format("jdbc") \
    .option("url", "jdbc:postgresql://host/db") \
    .option("dbtable", "processed_orders") \
    .mode("append") \
    .save()

Partitions and Parallelism

Spark divides a DataFrame into partitions. Each partition is a chunk of rows that one executor processes independently. More partitions mean more parallelism — up to the number of available CPU cores in the cluster. Too few partitions underutilize the cluster. Too many partitions create overhead from task scheduling. The recommended target is 128MB to 256MB of data per partition.

# Check current partition count
print(df.rdd.getNumPartitions())   # e.g., returns 200

# Repartition to a specific number
df = df.repartition(50)

# Coalesce (reduce partitions without full shuffle — faster)
df = df.coalesce(10)

Built-in Functions

Spark provides hundreds of built-in functions covering date arithmetic, string manipulation, mathematical operations, array handling, and type casting. These functions run natively in Spark's distributed execution engine — far faster than equivalent Python logic applied row by row.

from pyspark.sql.functions import (
    to_date, date_trunc, datediff,
    trim, lower, regexp_replace,
    round, abs, log,
    year, month, dayofweek
)

df = df.withColumn("order_date", to_date(col("order_date_str"), "yyyy-MM-dd"))
df = df.withColumn("order_month", date_trunc("month", col("order_date")))
df = df.withColumn("clean_name", trim(lower(col("customer_name"))))
df = df.withColumn("revenue_rounded", round(col("revenue"), 2))

Summary

Spark DataFrames provide a structured, distributed table abstraction that data engineers use to process massive datasets. Operations like select, filter, groupBy, join, and withColumn execute in parallel across the cluster. Engineers can use either the Python DataFrame API or SQL syntax interchangeably. Writing results in columnar formats like Parquet with smart partitioning ensures fast downstream access. The DataFrame API is the most practical entry point to building production Spark pipelines.

Leave a Comment

Your email address will not be published. Required fields are marked *