40. Schema on read — infer vs. declare
When you read text (CSV/JSON), Spark has to decide each column's type. There are two ways, and the choice matters more than it looks.
Inference
Letting the reader guess is convenient. But inference has costs: it scans the data first (slow on big files), and a column that's numeric most of the time but text in one stray row gets guessed as a string — silently changing every downstream calculation.
orders.dropDuplicates().write.mode("overwrite").option("header", True).csv("s_in")
inferred = spark.read.option("header", True).csv("s_in")
inferred.printSchema()
Declaring types yourself
The production habit is to not guess — state the types you expect. The
portable way that runs everywhere is read-then-cast: read the raw
columns, then cast each to its intended type. Now the types are a
contract, not a guess:
raw = spark.read.option("header", True).csv("s_in")
typed = (
raw
.withColumn("quantity", F.col("quantity").cast("int"))
.withColumn("unit_price", F.col("unit_price").cast("double"))
.withColumn("order_id", F.col("order_id").cast("long"))
)
typed.printSchema()
Spark's one-liner:
spark.read.schema(...). On a real cluster you hand the reader aStructTypeup front —spark.read.schema(my_schema).csv(path)— and it skips inference entirely, applying your types as it reads. The in-browser engine here doesn't expose.schema()on the reader, so we cast after reading (same result). The notebook below runs the.schema()form on real Spark.
# colab: 05-data-sources-io/00-file-formats-and-options
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, DoubleType
ORDERS_SCHEMA = StructType([
StructField("order_id", IntegerType()),
StructField("customer_id", StringType()),
StructField("order_date", StringType()),
StructField("product", StringType()),
StructField("category", StringType()),
StructField("quantity", IntegerType()),
StructField("unit_price", DoubleType()),
StructField("country", StringType()),
])
# No inference pass at all: Spark applies these types as it reads.
orders = spark.read.schema(ORDERS_SCHEMA).csv(f"{DATA_DIR}/orders.csv", header=True)
orders.printSchema()
And this is why Parquet is nice
All of this — inference cost, wrong guesses, casting — is a text-format problem. Parquet stores the schema in the file, so there's nothing to infer or declare. Read messy text once, cast carefully, write Parquet, and every read after that is typed and fast.
Your turn
Read the CSV back and pin the types: cast quantity to int and
unit_price to double, then return order_id, quantity,
unit_price. Assign to result.
orders.dropDuplicates().write.mode("overwrite").option("header", True).csv("sor")
raw = spark.read.option("header", True).csv("sor")
result = raw # <- cast quantity->int, unit_price->double; select order_id, quantity, unit_price
result.show()
raw = spark.read.option("header", True).csv("sor")
result = (
raw
.withColumn("quantity", F.col("quantity").cast("int"))
.withColumn("unit_price", F.col("unit_price").cast("double"))
.select("order_id", "quantity", "unit_price")
)