Back to Data Analysis
Data Analysis · Cheat Sheet
Knowing Your Data
Tip: Use your browser's print function (Ctrl+P / Cmd+P) to save as PDF for offline study.
Foundations Cheatsheet
The first-15-minutes inspection ritual
Run this checklist on every new dataset, before any metric. Predict each answer before you look — a check against an expectation can fail; a check without one is just scrolling.
- Row count —
COUNT(*)/len(df). Does it match what you were told? - Column types —
df.dtypes. Flag anyobject-typed column named like a number or date. - Head AND tail — first and last 10 rows. Hunt export footers, trailing nulls, summary rows.
- Key uniqueness / grain —
COUNT(DISTINCT key)vsCOUNT(*). Mismatch = duplicates or wrong mental model. Resolve before proceeding. - Date min/max — every date column. Full expected coverage? Max date recent (pipeline not stale)?
- Categorical distinct values —
value_counts()on every low-cardinality column. Hunt case variants (active/Active), blanks, typos. - Numeric distributions — min / p25 / median / p75 / max (p25 and p75 are percentiles: the values 25% and 75% of rows fall at or below). Hunt negatives, impossible zeros, 100x unit errors.
- Missingness profile —
df.isna().mean(). Per column: how much, which story, concentrated in a segment? - Sentinel hunt — −999, −1, 1970-01-01, 1900-01-01, empty string, 'N/A', impossible zeros.
Grain
- Grain = "one row means one what?" — the first question of every analysis
- Verify empirically:
SELECT COUNT(*), COUNT(DISTINCT key) FROM t;— equal means the key defines the grain - Composite grains are common:
(user_id, date)unique when neither column is alone - Columns copied down from a coarser grain (e.g.,
order_totalon line-item rows) sum incorrectly at the finer grain - Long format: one row per entity-category; wide format: one row per entity, category per column — different grains, same information
The row-count habit
- Log the row count after every load, filter, and join; explain every change
- Every metric is a claim about rows kept and dropped — narrate what happened to your rows
Dtype traps
| Symptom | Cause |
|---|---|
'9' > '10' is true | Numbers stored as text compare alphabetically |
| ZIP 02138 (a US postal code) becomes 2138 | Identifier loaded as number; identifiers should be text |
03/04/2025 ambiguous | String dates; parse explicitly, verify min/max |
| Sum raises TypeError (or concatenates) | Currency symbols / separators made the column text |
NULL semantics
- NULL is absence, not a value:
NULL = NULLis unknown; useIS NULL - Negative filters (
<>meaning "not equal to",NOT IN) drop NULL rows from both sides — segments stop summing to totals NOT IN (subquery)returns zero rows if the subquery contains any NULL — useNOT EXISTS- Aggregates skip NULLs:
AVGhas a smaller denominator thanCOUNT(*)— report n (the count of non-null values) with every mean - Empty string
''is NOT NULL — it's an invisible category
The three missingness stories
| Story | Example | Handling |
|---|---|---|
| Never collected | Field added to form in March | Analyze subset; disclose the bias |
| Lost | ETL dropped a batch | Data incident: fix upstream or exclude visibly |
| Not applicable | cancellation_date for active customers | Never impute or drop — the NULL is the signal |
- Missing ≠ zero: fill with 0 only when absent and zero mean the same thing for the question
- Report denominators: "avg 8.0 (n = 7 of 10)", never bare "avg 8.0"
Aligned to the pandas documentation and the practical data-analysis canon.
Make this cheat sheet yours
Personalize this sheet — focus it however you study, or build one from the exact questions you keep getting wrong.
Sign up free to create a personalized cheat sheet.