LEARN · MODERN DATA ENGINEERING
Large analytical datasets often start life as CSV files. They are easy to create, easy to open, and supported almost everywhere.
The problem appears when the data grows.
A CSV file stores information row by row:
Alice,29,London,Engineer Bob,41,Berlin,Designer Carol,35,Paris,Manager
If a query only needs the age column, a CSV reader still has to:
-
Read each row.
-
Parse separators and text values.
-
Decode columns that will never be used.
-
Discard unnecessary data.
For small files, this does not matter. For gigabytes or terabytes of analytical data, the wasted work becomes expensive.
Columnar storage changes the layout completely. Instead of storing complete records together, it stores values from the same column together:
name: Alice, Bob, Carol age: 29, 41, 35 city: London, Berlin, Paris job: Engineer, Designer, Manager
This simple design choice powers much of modern analytics. Apache Parquet and Apache Arrow are two technologies built around this idea, but they solve different problems.
In this guide, you will learn:
-
How Parquet stores analytical data efficiently.
-
Why Arrow improves in-memory processing.
-
How column pruning and predicate pushdown accelerate queries.
-
How compression works better with column-oriented data.
-
How to benchmark CSV versus Parquet yourself.
-
Why some workloads can become dramatically faster.
Parquet and Arrow solve different problems
Although Parquet and Arrow are often mentioned together, they operate at different layers.
Apache Parquet: storage optimized for analytics
Parquet is a columnar file format designed for storing data.
Its strengths include:
-
Column-oriented layout.
-
Efficient compression.
-
Embedded schemas.
-
Support for analytical query engines.
-
Cross-language compatibility.
Think of Parquet as an optimized alternative to CSV for datasets that will be queried repeatedly.
Apache Arrow: memory optimized for data exchange
Arrow is an in-memory columnar format.
It focuses on moving and processing data efficiently between systems such as:
-
Python.
-
Rust.
-
Java.
-
C++.
-
Data processing libraries.
-
Query engines.
Arrow avoids many expensive conversions by representing data in contiguous memory buffers rather than collections of individual objects.
A useful mental model:
-
Parquet = optimized for storing data on disk.
-
Arrow = optimized for representing data in memory.
Arrow is a common interchange format in modern data systems, but it is not a requirement for every execution engine. Some engines use Arrow internally, while others use their own columnar memory formats or only convert data into Arrow structures at specific boundaries.
Why columnar storage is faster
Imagine a user table with 150 columns.
The query is:
SELECT age FROM users WHERE age > 30;
A traditional row-oriented reader may need to process every field in every record.
A Parquet reader can often read only the columns needed for the query.
This technique is called column pruning.
The benefits are significant:
-
Less disk I/O.
-
Less CPU spent parsing data.
-
Lower memory usage.
-
Faster query execution.
For wide analytical datasets, avoiding unnecessary columns is often the biggest performance improvement.
Row groups: the building blocks behind fast scans
Parquet files are not simply one giant block of columns. They are organized into structures called row groups.
A simplified layout looks like this:
Parquet file Row group 1 age column country column salary column Row group 2 age column country column salary column Row group 3 age column country column salary column
Each row group can contain metadata describing its contents.
For example:
age column statistics Row group 1: minimum = 18 maximum = 25 Row group 2: minimum = 40 maximum = 65
If a query asks:
SELECT * FROM users WHERE age > 50;
The engine can skip row group 1 because it knows that group cannot contain matching values.
This optimization is called predicate pushdown.
Instead of:
-
Read everything.
-
Filter later.
The engine can do:
-
Check metadata.
-
Skip irrelevant data.
-
Read only useful sections.
Why Parquet compresses so well
Compression works best when similar values are stored together.
Rows mix unrelated information:
Alice,29,London Bob,41,Berlin Carol,35,Paris
Columns group similar information:
29 41 35
or:
London London London London
Parquet can apply specialized encodings before compression, including:
-
Dictionary encoding for repeated values.
-
Run-length encoding for repeated sequences.
-
Bit packing for compact numeric storage.
-
Delta encoding for values that change predictably.
After encoding, Parquet commonly uses compression codecs such as:
-
Zstandard.
-
Snappy.
-
Gzip.
-
Brotli.
The combination of column layout and encoding is why analytical datasets often become much smaller than their CSV equivalents.
Arrow keeps analytical data CPU-friendly
Many analytical operations are faster when values are stored in contiguous memory buffers.
Traditional Python objects can carry significant overhead because each value may be a separate object managed by the runtime.
Arrow arrays instead store data in structures designed for efficient processing.
Advantages include:
-
Better CPU cache usage.
-
Efficient vectorized operations.
-
Lower memory overhead.
-
Faster interchange between compatible tools.
When Arrow is preferable to a DataFrame workflow depends on the task. For interactive analysis, Pandas is often convenient. For large-scale pipelines, streaming, cross-language processing, or moving data between engines, Arrow can be a better fit.
A runnable CSV vs. Parquet benchmark
The following benchmark creates a dataset, writes both formats, compares file sizes, and measures scan performance.
Install current packages:
pip install -U "pandas>=2.2" "pyarrow>=21.0.0" "numpy>=2.0"
Create the dataset:
import numpy as np import pandas as pd rows = 2_000_000 rng = np.random.default_rng(42) df = pd.DataFrame( { "id": np.arange(rows), "country": rng.choice( ["US", "UK", "DE", "FR", "JP", "CA"], rows, ), "age": rng.integers(18, 80, rows), "salary": rng.normal(70000, 15000, rows).round(2), "score": rng.random(rows), } ) df.to_csv("data.csv", index=False) df.to_parquet( "data.parquet", compression="zstd", )
Compare storage size:
from pathlib import Path csv_size = Path("data.csv").stat().st_size / 1024 / 1024 parquet_size = Path("data.parquet").stat().st_size / 1024 / 1024 print(f"CSV: {csv_size:.1f} MB") print(f"Parquet: {parquet_size:.1f} MB") print(f"Compression ratio: {csv_size / parquet_size:.2f}x")
A typical result might show several times less storage for Parquet. Your result will depend on:
-
Data distribution.
-
Compression settings.
-
Column types.
-
Dataset size.
Now compare reading the files:
import time import pandas as pd start = time.perf_counter() csv_df = pd.read_csv("data.csv") csv_df["age"].mean() csv_time = time.perf_counter() - start start = time.perf_counter() parquet_df = pd.read_parquet("data.parquet") parquet_df["age"].mean() parquet_time = time.perf_counter() - start print(f"CSV scan: {csv_time:.3f}s") print(f"Parquet scan: {parquet_time:.3f}s")
Finally, test column pruning:
import time import pandas as pd import pyarrow.parquet as pq start = time.perf_counter() ages = pd.read_csv( "data.csv", usecols=["age"], ) ages["age"].mean() csv_column_time = time.perf_counter() - start start = time.perf_counter() table = pq.read_table( "data.parquet", columns=["age"], ) table.column("age").to_numpy().mean() parquet_column_time = time.perf_counter() - start print(f"CSV single column: {csv_column_time:.3f}s") print(f"Parquet single column: {parquet_column_time:.3f}s")
When can Parquet become 100× faster?
The phrase “100× faster” can happen, but it is not a universal Parquet benchmark result.
Large speedups usually happen when several optimizations combine:
-
Only a small number of columns are needed.
-
Predicate pushdown removes most row groups.
-
Compression reduces storage reads.
-
The engine executes operations in parallel.
-
Vectorized processing avoids row-by-row work.
Consider a dataset with:
-
150 columns.
-
Billions of rows.
-
A query needing two columns.
-
A filter that eliminates most data.
The engine may read a tiny fraction of the original dataset.
That can create order-of-magnitude improvements.
However, scanning every column of every row is a different workload. Parquet still helps through compression and efficient storage, but the speed difference will usually be smaller.
Cherry on the cake: a taxi dataset that shrank dramatically
A famous real-world example comes from the public New York City Taxi and Limousine Commission trip records.
The raw taxi trip data is published as CSV files. Data engineers frequently convert these files into Parquet for analytics workflows.
One commonly reproduced conversion of a monthly yellow taxi dataset reduced storage from roughly 3 GB of CSV data to around 500 MB of Parquet data using columnar encoding and compression.
The surprising part is that the compression codec is only part of the story. The column layout itself creates the conditions for better compression:
-
Passenger counts repeat.
-
Payment types repeat.
-
Location identifiers repeat.
-
Numeric columns often have predictable patterns.
Parquet does not just compress the file better; it organizes the information so compression algorithms have much more useful patterns to work with.
When should you use Parquet?
Parquet is a strong choice for:
-
Data lakes.
-
Business intelligence systems.
-
Machine learning datasets.
-
ETL pipelines.
-
Historical analytics.
-
Batch processing.
CSV remains useful when:
-
Humans need to inspect files manually.
-
A simple interchange format is required.
-
Maximum compatibility matters more than performance.
For modern ColumnarStorage workflows, Parquet is often the default format because it balances speed, size, and interoperability.
Best practices
A few habits provide most of the benefits:
-
Store analytical datasets as Parquet instead of repeatedly processing CSV.
-
Read only the columns required by your query.
-
Use appropriate compression, such as Zstandard, when supported by your tools.
-
Keep schemas consistent across files.
-
Partition very large datasets carefully.
-
Avoid unnecessary conversions between formats.
-
Use Arrow-compatible tools when moving large amounts of data between systems.
Key takeaways
Remember these fundamentals:
-
CSV stores complete rows; Parquet stores columns.
-
Parquet optimizes storage; Arrow optimizes memory representation and data exchange.
-
Column pruning avoids unnecessary reads.
-
Predicate pushdown skips irrelevant data.
-
Column-oriented layouts improve compression.
-
The biggest speedups come from reducing the amount of data processed.
Try the benchmark on your own dataset, compare your CSV and Parquet results, and measure how much storage and query time you can save. Follow the next lessons in this course to keep building practical data engineering skills.