Working with the data — best practices¶
Using this notebook in Databricks
Download the notebook using the button above, then import it into your Databricks workspace:
- In the Databricks UI, go to Workspace → Shared
- Click the ⋮ menu and choose Import
- Select File and upload the downloaded
.pyfile - The notebook will appear in the Shared space and be accessible to everyone in your workspace
Adding it to Shared means all workspace members can access and run it. If you want a personal copy to edit, clone it into your own folder first (File → Clone).
The data mart contains tables that can have over 100 million rows. This page covers how to work with that efficiently and avoid common pitfalls.
Key principle: filter early, collect late.
Let Spark and SQL do the heavy lifting on the full dataset. Only pull data into pandas (or CSV) once you have filtered it down to what you actually need.
1. Understand the size of your data first¶
Before doing anything, check how large the table is. This avoids accidentally trying to load 100M rows into memory.
-- Row count (fast — doesn't scan the data)
SELECT COUNT(*) AS total_rows FROM <mart_catalog_name>.default.diagnosis;
-- Size and column info
DESCRIBE DETAIL <mart_catalog_name>.default.diagnosis;
2. Filter in SQL or Spark, not in pandas¶
SQL and Spark process data in parallel across the cluster. Pandas runs on a single node and requires the entire dataset to fit in memory. For large tables, always filter before collecting.
Do this:
# Good — filter 100M rows down to what you need using Spark
df = (
spark.table("<mart_catalog_name>.default.diagnosis")
.filter("diag_beg_effective_dt_tm >= 2020")
.filter("encntr_type_class_cd = 391")
.select("salted_warehouse_identifier", "diag_beg_effective_dt_tm", "encounter_type_class", "icd10")
)
# Only now convert to pandas — should be a manageable number of rows
print(f"Filtered rows: {df.count():,}")
pdf = df.toPandas()
Not this:
# Bad — pulls 100M rows into driver memory, then filters
# This will be slow, expensive, and may crash with an OutOfMemory error
# pdf = spark.table("<mart_catalog_name>.<schema_name>.<table_name>").toPandas()
# pdf = pdf[pdf['diagnosis_year'] >= 2020]
3. Use SQL for aggregation¶
If you need summary statistics or group-level counts, do the aggregation in SQL. The result will be small enough to work with anywhere.
-- Aggregate 100M rows down to a summary — fast and returns a small result
-- SELECT
-- diagnosis_year,
-- stage,
-- COUNT(*) AS patient_count,
-- AVG(age_at_diagnosis) AS avg_age
-- FROM <mart_catalog_name>.<schema_name>.<table_name>
-- GROUP BY diagnosis_year, stage
-- ORDER BY diagnosis_year, stage;
The result of that query might be a few hundred rows — safe to pull into pandas for plotting or further analysis.
# Aggregate in Spark, then convert — small result, no memory issues
# summary = spark.sql("""
# SELECT diagnosis_year, stage, COUNT(*) AS n
# FROM <mart_catalog_name>.<schema_name>.<table_name>
# GROUP BY diagnosis_year, stage
# """)
# pdf_summary = summary.toPandas()
4. Sample when exploring¶
If you want to get a feel for the data before committing to a full analysis, take a sample rather than loading the whole table.
# Random 1% sample of a large table
# df_sample = spark.table("<mart_catalog_name>.<schema_name>.<table_name>").sample(0.01)
# print(f"Sample size: {df_sample.count():,}")
# pdf_sample = df_sample.toPandas()
# Or just take the first N rows (faster, but not random)
# pdf_preview = spark.table("<mart_catalog_name>.<schema_name>.<table_name>").limit(10000).toPandas()
5. Save intermediate results¶
If you have spent time filtering and transforming a large table, save the result to your workspace catalog so you don't have to reprocess it every time you open the notebook.
# Filter once, save to workspace catalog
# cohort = (
# spark.table("<mart_catalog_name>.<schema_name>.<table_name>")
# .filter("diagnosis_year >= 2018")
# .filter("stage IS NOT NULL")
# )
#
# cohort.write.mode("overwrite").saveAsTable(
# "<workspace_catalog_name>.my_project.filtered_cohort"
# )
# Next time, read from your saved table — much faster
# cohort = spark.table("<workspace_catalog_name>.my_project.filtered_cohort")
6. When you need a CSV¶
Sometimes you need a CSV file — for a bash script, an external tool, or a specific workflow. That's fine, but follow these guidelines.
Files written to /tmp are temporary
Files written to /tmp are stored on the cluster's local ephemeral storage. They are deleted when your session ends or the cluster is terminated. If you need results to persist between sessions, save them as a table in your workspace catalog (see section 5) rather than as a CSV on /tmp.
Always filter first¶
Never export a full 100M-row table to CSV. Filter down to what you need, then export.
# Good — export a filtered subset
# filtered = (
# spark.table("<mart_catalog_name>.<schema_name>.<table_name>")
# .filter("diagnosis_year = 2023")
# .filter("region = 'Thames Valley'")
# .select("patient_id", "diagnosis_date", "stage")
# )
#
# print(f"Exporting {filtered.count():,} rows")
# filtered.toPandas().to_csv("/tmp/cohort_2023.csv", index=False)
Understand the costs¶
Exporting to CSV involves compute (Spark reads and serialises the data), memory (the full result must fit in the driver node), and transfer charges if the source storage is in a different region.
For very large exports that don't fit in driver memory, write to CSV using Spark directly — this avoids the pandas memory bottleneck:
# Large export — write CSV using Spark (no memory limit, but creates multiple files)
# (
# spark.table("<mart_catalog_name>.<schema_name>.<table_name>")
# .filter("diagnosis_year >= 2020")
# .write
# .mode("overwrite")
# .option("header", "true")
# .csv("/tmp/large_export")
# )
#
# # This creates a folder with multiple CSV files (one per partition)
# # To combine into a single file:
# # .coalesce(1).write.mode("overwrite").option("header", "true").csv("/tmp/single_export")
# # Note: coalesce(1) forces all data through one node — only use for moderate-sized results
7. Running existing scripts¶
If you have existing Python or bash scripts that worked with CSV files, you can still use them.
- Filter the data in SQL or Spark
- Export the filtered result to a temporary CSV
- Run your script against the CSV
# Steps 1 & 2: filter and export
# (
# spark.table("<mart_catalog_name>.<schema_name>.<table_name>")
# .filter("diagnosis_year >= 2020")
# .select("patient_id", "diagnosis_date", "stage", "treatment")
# .toPandas()
# .to_csv("/tmp/filtered_data.csv", index=False)
# )
# Step 3: run your bash script against the exported CSV
# bash /Workspace/Shared/scripts/my_analysis.sh /tmp/filtered_data.csv
For Python scripts, you can paste them into a cell and change the data loading line from pd.read_csv(...) to spark.table(...).toPandas(). The rest of the script stays the same.
8. Choosing the right approach¶
| What you need | Approach | Notes |
|---|---|---|
| Counts, averages, summaries | SQL aggregation | Fastest — result is tiny |
| Explore a large table | .sample(0.01) or .limit(10000) |
Get a feel for the data without loading it all |
| Filter and analyse a subset | Spark filter → .toPandas() |
Let Spark do the filtering |
| Reuse a filtered dataset | Save to workspace catalog | Avoids re-filtering every session |
| Run an existing pandas script | Spark filter → .toPandas() → your script |
Change one line (the data load) |
| Run an existing bash script | Spark filter → export CSV → %sh bash script.sh |
Filter first, export the subset |
| Plot or visualise | SQL/Spark aggregate → pandas → matplotlib | Aggregate the data before plotting |
| Full table export | Avoid if possible; use Spark .write.csv() if essential |
Never use .toPandas() on 100M+ rows |
Remember: the data mart is always there. You don't need to download everything upfront. Query what you need, when you need it.