- Learn
- Computer Science & IT
- Data Science & AI
- Data Analysis with Python
- Exploratory Data Analysis in Python
Exploratory Data Analysis in Python
IntermediateLesson 8 of 8 in Data Analysis with Python Course
What you will learn
- Explain what exploratory data analysis is for, turn a broad request into concrete questions, and set up a notebook that anyone can rerun from top to bottom.
- Frame the purpose and questions of an exploratory analysisDescribe what exploratory data analysis does, follow its workflow from questions to findings, and write specific questions that the data can answer.
- Organize a notebook that anyone can rerunStructure a Jupyter notebook so that it runs from top to bottom with the same results, with clear sections, relative paths, recorded versions, fixed random seeds and built-in checks.
- Frame the purpose and questions of an exploratory analysis
- Inspect the size, structure and variable types of a new dataset, measure missing and duplicate data, and check that values are valid and consistent before trusting any pattern.
- Inspect structure, size and variable typesUse pandas inspection methods to learn the shape, columns, types and keys of a dataset, classify each variable for analysis, and spot values stored with the wrong type.
- Measure missing values and duplicatesCount missing values and their share per column, find hidden missing codes, check whether missingness follows a pattern, and detect full-row and key duplicates.
- Check that values are valid and consistentApply range, category, cross-column, unit and time-coverage checks, and keep a record of every data-quality issue and decision.
- Inspect structure, size and variable types
- Summarize and plot single numeric and categorical variables, recognize distribution shapes, and find outliers and decide what to do with them.
- Summarize a numeric variable with center and spreadCompute and interpret measures of center, spread and position in pandas, and choose robust summaries when data are skewed or contain extreme values.
- Plot and describe distribution shapesDraw histograms, density curves, box plots and ECDFs with pandas and seaborn, choose bins and scales that show the real shape, and describe a distribution in words with numbers.
- Summarize categorical variablesBuild frequency tables and bar charts for categorical variables, keep ordinal categories in order, handle rare categories and missing labels, and state the denominator of every percentage.
- Find outliers and decide what to do with themFlag unusual values with the IQR rule, z-scores and robust scores, investigate their cause, and choose and document a justified way to treat them.
- Summarize a numeric variable with center and spread
- Show relationships between numeric variables with scatter plots, measure their strength with correlation coefficients, and interpret what an association can and cannot show.
- Show and describe relationships between two numeric variablesDraw scatter plots and scatter-plot matrices that stay readable with many points, add a third variable with color or panels, and describe direction, form, strength and unusual points.
- Measure association with correlation coefficientsCompute and interpret Pearson, Spearman and Kendall correlation in pandas, read a correlation matrix and heatmap, and recognize what a coefficient misses.
- Interpret relationships without overclaimingExplain why an association does not establish cause, recognize confounding, Simpson's paradox, time trends, restricted range, aggregation and chance findings, and phrase conclusions accordingly.
- Show and describe relationships between two numeric variables
- Compare distributions, shares and rates across groups and periods, with group sizes in view and the right denominators, and recognize when a difference rests on too little data.
- Compare a numeric variable across groupsSummarize a numeric variable by group with counts, centers and spreads, plot the group distributions side by side, and express differences in absolute and relative terms.
- Compare category shares and rates across segmentsBuild cross-tabulations with row or column percentages, compare rates with the right denominator, and distinguish percentage points from percent changes.
- Compare segments over timeAggregate data by period and segment, avoid traps such as partial and unequal periods, separate seasonal patterns from trends, and question gaps and sudden shifts.
- Compare a numeric variable across groups
- Turn exploration into clear, supported findings with honest limitations, and share the analysis as a clean notebook or report that others can rerun and check.
- Turn observations into clear, supported findingsWrite findings that answer the questions with evidence, size and scope, choose and label charts that make each finding visible, and state limitations and next steps.
- Share a reproducible analysisPrepare a clean, rerunnable notebook, compute every reported number in code, export it as a report, save figures and tables, record provenance and protect personal data.
- Turn observations into clear, supported findings
Exploratory Data Analysis in Python study guide
1Plan an exploratory analysis in a notebook
Clear questions make an exploratory analysis useful, and a reliable notebook makes its results checkable. Start by connecting the request to a decision, identifying the population and learning what one row means. Then organize the code, settings and checks so a fresh session can reproduce the work. These habits give later plots and comparisons a purpose and a dependable starting point.
1.1 Questions that give exploration a purpose
Exploratory data analysis, or EDA, uses summaries and plots to learn what a dataset contains. You look for quality problems, patterns and surprises, then turn them into better questions. John Tukey developed and promoted this approach, most notably in his 1977 book Exploratory Data Analysis.
Start with a decision
A request such as "How are our deliveries doing?" is too broad to guide the first calculation. Ask what decision the analysis will support, then name the variables and comparisons that matter. Useful questions include "What is the typical delivery time, and how much does it vary?", "What share of orders takes longer than 45 minutes?", "Do times differ by city or hour?" and "Has the monthly median changed since January?" Each points toward a summary, a plot or a comparison.
Before computing, establish where the data came from, how they were collected, their period and population, and each column's meaning and unit. A jump in completed fields may reflect a form change that made a field required. That context changes the explanation of the pattern.
Know what one row means
The unit of observation, also called granularity, is the thing represented by one row. It might be an order, an item within an order, a customer or a store on a particular day. A table with 12,000 order-line rows and 4,000 distinct order IDs averages 3 rows per order. Its row count describes items, so an order-level question needs an order-level count. Repeated order IDs are expected in that table; they would be a quality problem in a table promised to contain one row per order.
Work through a loop
A useful workflow is:
- State the questions and the decision they support.
- Load the data and confirm the meaning of a row.
- Profile structure, types and quality.
- Examine important variables individually.
- Explore relationships between variables.
- Compare segments and periods.
- Write findings, limitations and next questions.
These steps form a loop. A surprising relationship can send you back to the source to check units or missing values. New data can change the profile and reopen earlier questions. Starting with questions keeps the analysis focused; staying open to surprises keeps it exploratory. Plotting every possible pair without a purpose usually creates a long chart collection with little to explain.
Distinguish a lead from confirmation
Exploration generates hypotheses. Confirmatory analysis checks a hypothesis stated in advance, ideally using data that did not reveal the pattern. Looking through many charts makes chance patterns more likely, so a striking exploratory result needs follow-up before it supports a general claim. EDA helps prepare that work by showing usable variables, skewed distributions and promising relationships.
The data's coverage also sets the scope of a finding. Product reviews come from people who chose to write reviews. Their ratings describe those respondents; extending the result to all customers requires evidence about how respondents differ from everyone else.
Key Takeaways
- Begin with a decision and concrete questions about variables, populations and comparisons.
- Confirm what one row represents before interpreting counts or repeated keys.
- Use the workflow as a loop: surprises can expose quality issues or new questions.
- Exploration generates hypotheses; confirmation checks hypotheses stated in advance.
- The collection process and population limit how widely findings apply.
1.2 A notebook that runs from top to bottom
A Jupyter notebook combines code, outputs and Markdown explanations. Its kernel is the Python process that holds variables in memory. The kernel remembers the order in which cells ran, which can differ from the order visible on the page.
Remove hidden state
Suppose you run a cell that creates clean, delete that cell, and continue using clean below. The current session still works because the variable remains in memory. A fresh session fails because the notebook no longer contains its definition. Editing an early cell after running later calculations can also leave old outputs beside new code.
Execution counts such as [7] help reveal out-of-order runs. The practical test is stronger: in JupyterLab, choose Kernel → Restart Kernel and Run All Cells. Check that the whole notebook completes and its numbers agree with the outputs you intend to share.
Put dependencies in a clear order
Start with a title, purpose and questions. Follow with imports and a settings cell, then sections for loading, quality checks, distributions, relationships, comparisons and findings. For example:
from pathlib import Path
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
DATA = Path("data/raw/orders.csv")
LATE_MINUTES = 45
SEED = 42
df = pd.read_csv(DATA)
A relative path works inside the shared project structure. A path tied to one person's desktop breaks when the project moves. State the working directory used to run the notebook so the relative path resolves correctly. Preserve raw files and save processed data separately. Make changes in code: a manual spreadsheet edit is invisible to the next rerun.
Record the environment and random choices
Save package versions with an environment file, such as the output of pip freeze > requirements.txt, and print important versions in the notebook with print(pd.__version__). Behavior changes across releases: pandas 3 gives text a dedicated str dtype, and a DataFrame containing text needs an explicit numeric selection for correlation.
A sampled plot should show the same observations on each rerun. Use df.sample(n=1000, random_state=42) when at least 1,000 rows are available, or create a NumPy generator with rng = np.random.default_rng(42). Record the seed with the other settings. A seed controls random choices; reproducing the complete output also requires the same inputs and software.
Make assumptions executable
Turn important expectations into checks:
assert df["order_id"].is_unique
assert df["minutes"].ge(0).all()
The first requires one unique order ID per row. The second requires every duration to be nonnegative and present for ordinary floating-point data. If missing times are allowed, define a separate missingness check and apply the range rule only to recorded times. A failed assertion stops the run where the input first violates the expected contract.
Put repeated procedures into functions so one correction updates every use. Keep code and notebooks under version control. Notebook outputs are stored inside their JSON, which can make changes hard to compare; clearing outputs before a commit or pairing the notebook with a text script through Jupytext can help.
Key Takeaways
- The kernel stores variables from execution history, including cells no longer visible.
- Restart the kernel and run every cell to expose hidden dependencies and stale outputs.
- Keep imports and settings together, use relative paths and preserve raw inputs.
- Record package versions and fix the seed of each random step.
- Use assertions to check the assumptions that a new input file must meet.
- Make every data change in code so another run can repeat it.
2Profile a dataset and assess its quality
A dataset can look tidy while hiding repeated records, missing codes or incompatible units. Build a profile of its structure and meanings, measure gaps and duplicates, and apply rules that reflect how the data were collected. The goal is to understand which observations support each result and to leave a visible record of the decisions that change the analyzed data.
2.1 The first inspection of a dataset
A first inspection establishes the table's size, what its columns represent and whether their stored types suit those meanings. This profile helps you choose sensible summaries before trusting the output of a convenient command.
Inspect structure from several angles
Use df.shape for the row and column counts, df.head() and df.tail() for the ends of the file, and df.sample(5, random_state=0) for a reproducible sample when at least five rows exist. The first rows alone may all belong to the earliest date or the same store. Run df.info() to inspect column names, non-null counts, dtypes and memory use. Use df.nunique() to count distinct recorded values in each column.
Separate meaning from storage
| Analysis type | Example | Useful treatment |
|---|---|---|
| Continuous numeric | Distance in km | Center, spread and distribution |
| Discrete numeric | Items per order | Counts and integer-centered bins |
| Nominal category | City | Frequencies and shares |
| Ordinal category | Low, medium, high | Frequencies in a meaningful order |
| Date and time | Order timestamp | Coverage and period comparisons |
| Identifier | Customer ID | Uniqueness and links between records |
| Free text | A written comment | Inspect text and its quality |
An integer dtype does not make every column a quantity. Region codes and customer IDs have numeric storage but no useful arithmetic mean. A rating from 1 to 5 has a small set of ordered responses and often benefits from a categorical display.
pandas commonly uses int64, float64, bool, datetime64, category and, from pandas 3, str. Earlier pandas versions often used object for text. Ordinary integer data may become float64 when missing values occur because NaN is a floating-point marker; nullable Int64 supports integers with missing values.
Check conversions and keys
A numeric-looking string can hide separators, units or missing codes. For a Series containing "1,234", "950", "n/a" and "1,010":
parsed = pd.to_numeric(
s.str.replace(",", ""), errors="coerce"
)
The values become 1234.0, 950.0, NaN and 1010.0. Compare missing counts before and after conversion, and inspect newly missing entries. Coercion also turns an unhandled value such as "12 kg" into NaN, so successful execution is not enough to show that the conversion preserved the data.
Read codes with meaningful leading zeros as text, for example pd.read_csv(path, dtype={"postal_code": "str"}). Recovering a lost zero later requires knowledge that the integer representation discarded.
Identify the expected row key. df["order_id"].is_unique checks a single-column key; df.duplicated(subset=["store_id", "date"]).sum() checks repeated store-date combinations. Relate key counts back to the row meaning. A single-valued column cannot explain differences within this dataset; thousands of supposed city categories may signal inconsistent labels.
Complete a data dictionary as you inspect: record each column's meaning, analysis type, unit, allowed values and known issues. Those entries supply the rules for the next quality checks.
Key Takeaways
- Inspect size, sample rows, non-null counts, dtypes and distinct-value counts.
- Choose analysis methods from what a variable means, including order and units.
- Check newly missing entries after any conversion that coerces parsing failures.
- Test the expected key against the table’s unit of observation.
2.2 Missing values and repeated records
Missing values change which observations contribute to a result. Duplicate records can give some observations extra weight. Measure both before choosing a treatment, and keep the decision visible in the analysis.
Measure missingness and its pattern
pandas uses NaN, NaT for missing datetimes and <NA> for nullable types. Count and rank missingness with:
missing_count = df.isna().sum()
missing_share = df.isna().mean().sort_values(ascending=False)
rows_with_gaps = df.isna().any(axis=1).sum()
A Boolean mean gives a share because True counts as 1 and False as 0. With 96 missing ages among 1,200 rows, the missing share is 8%. Rows with any gap can be much more numerous than missing entries in one column; this is the row loss that a blanket df.dropna() would cause.
Some gaps hide behind ordinary values. Default CSV parsing recognizes many empty and text missing markers, but codes such as -999, "unknown" and "?" may survive. Inspect frequencies and extremes, check the data dictionary, and declare additional markers with na_values. Zero needs context: it can mean no returns, or an undocumented placeholder.
Check whether gaps concentrate in a segment:
age_gaps = df["customer_age"].isna()
age_gaps.groupby(df["city"]).mean()
If one city has 40% missing ages and the others about 2%, complete-row analysis will underrepresent that city. The terms MCAR, MAR and MNAR describe different mechanisms: missingness unrelated to observed or missing values; missingness depending only on observed variables; and missingness depending on the missing value itself. High-income respondents skipping an income question illustrate MNAR. EDA can detect links with recorded variables; deciding the full mechanism needs more evidence about collection.
Name the denominator
For 6 card payments, 2 cash payments and 2 gaps, value_counts(normalize=True) reports 75% card and 25% cash among the 8 recorded payments. Adding dropna=False reports 60% card, 20% cash and 20% missing among all 10 rows. Neither table answers the other's question. Label the population behind each share.
Inspect full-row and key duplicates
df.duplicated().sum() counts copies after the first occurrence of each full row. A key-based check can find conflicting records that are not identical:
repeated = df["order_id"].duplicated(keep=False)
df.loc[repeated].sort_values("order_id")
Consider IDs 101, 102, 102, 103, 104, 104 with amounts 20, 35, 35, 12, 50, 55. There is 1 full-row duplicate, 2 repeated key occurrences after the first, and 4 rows involved in repeated keys. The two 104 records disagree about the amount, so deleting a copy without investigation could discard the correct value.
Record the count, likely cause, treatment and affected rows for each issue. Dropping all gaps can remove a biased subset; filling them with the mean hides their pattern and reduces apparent spread. Choose a treatment for the question and collection process, then keep the original missingness measurable.
Key Takeaways
- Count gaps per column and rows with any gap before choosing a treatment.
- Check sentinel codes and compare missingness across meaningful segments.
- Missingness mechanisms describe why gaps occur; observed patterns alone cannot identify the full mechanism.
- Default frequency proportions exclude missing values, so state the denominator.
- Full-row duplicates and conflicting repeated keys require different investigations.
- Log treatments because dropping or filling values changes the analyzed data.
2.3 Rules that reveal invalid or inconsistent data
A value is valid when it meets its column's allowed range or set. Records are consistent when their values agree across columns, rows and sources. Build checks from the data dictionary and knowledge of the process that created the data.
Check ranges without mixing in gaps
Start with minima and maxima, then test explicit limits. To count recorded ages outside 0–120:
invalid_age = df["age"].notna() & ~df["age"].between(0, 120)
invalid_age.sum()
between returns False for missing values. Negating it alone would count missing ages as invalid ages; the presence check separates the issues. Other rules might require nonnegative quantities, percentages no greater than 100, or extraction dates no earlier than the events they contain. The permitted range comes from the column's meaning.
Reconcile labels and columns
"Lyon", "lyon" and " Lyon" are three strings, even when they name the same city. Counts of 410, 22 and 5 become 437 for "lyon" after a justified trim and case conversion:
city_clean = df["city"].str.strip().str.lower()
Compare category counts before and after. Spelling variants and synonyms need an explicit mapping; changing case alone cannot merge "UK" with "United Kingdom". Retain the rule used so a rerun makes the same decision.
Cross-column checks look for contradictions. df.query("delivered_at < ordered_at") finds deliveries timestamped before their orders. Other checks ask whether item amounts add to the total, age agrees with birth date, or a canceled order has an unexpected delivery time. These rules require shared units, time conventions and definitions across columns.
Look for scale and coverage changes
Mixed kilograms and grams can create clusters roughly 1,000 times apart. A share recorded partly on a 0–1 scale and partly on a 0–100 scale also creates a misleading distribution. Check the source before converting: two clusters can also represent two genuine populations. A plot suggests where to investigate; the source establishes which units apply.
Inspect the earliest and latest dates and counts by month. A missing month may be an extraction gap, while a partial last month naturally contains fewer records. An abrupt increase can reflect a new recording system. Record the coverage you actually have before reporting a time pattern.
Keep a quality log
Use a small table with the issue, check, number of affected rows, evidence and action. For example, a negative duration can be flagged by a nonnegative-time rule, investigated against timestamps and corrected only when the source establishes its true value. Important expectations can become assertions that stop a future run.
A 120-minute delivery is unusual but possible. A −5-minute duration is impossible under the same duration definition. The first needs an outlier investigation; the second needs an error investigation.
Key Takeaways
- Use domain rules to check valid ranges, category sets and cross-column agreement.
- Count missing values separately from recorded values outside an allowed range.
- Check label variants, mixed units and time coverage before interpreting patterns.
- Keep a quality log and turn important input expectations into assertions.
- Investigate unusual values; distinguish a possible extreme from an impossible value.
3Describe the distribution of each variable
A single average can hide a long tail, two distinct groups or a recording limit. Describe each important variable with summaries and plots that fit its meaning. Numeric variables need center, spread, shape and unusual-value checks; categorical variables need clear labels, order and denominators. Outlier rules help locate records worth investigating, while context determines their treatment.
3.1 Center, spread and the effect of extreme values
A numeric distribution describes which values occur and how often. Its center gives a typical location, its spread shows variation, and its shape reveals what those summaries leave out. Use a plot alongside the numbers.
Choose summaries that fit the distribution
The mean is the sum divided by the count. The median is the middle sorted value, or the mean of the two middle values for an even count. The range is maximum minus minimum. The interquartile range, or IQR, is Q3 minus Q1: the distance between the 75th and 25th percentiles. Standard deviation measures spread around the mean in the variable's own units.
pandas s.std() uses the sample calculation with ddof=1, dividing the squared-deviation total by one less than the count. NumPy np.std(a) defaults to ddof=0, dividing by the count. This matters especially with small samples, so use matching settings when comparing results.
Read the summary before interpreting it
s = df["minutes"]
s.describe(percentiles=[0.1, 0.5, 0.9])
The result includes the non-missing count, mean, standard deviation, minimum, requested percentiles and maximum. The 50% entry is the median. On a DataFrame, describe() covers numeric and datetime columns by default; datetime summaries include their mean and percentiles. describe(include="all") also summarizes other columns. Text and categorical columns receive count, unique, top and freq, where top is a most frequent value and freq is its count. Exclude numeric identifiers from arithmetic summaries.
Work through a long right tail
Consider delivery times of 18, 22, 25, 25, 27, 29, 31, 34, 38, 41 and 95 minutes. Their sum is 385 and their count is 11, so the mean is 35.0 minutes. The sixth sorted value is 29, giving the median. pandas gives Q1 = 25, Q3 = 36, IQR = 11 and a sample standard deviation of about 21.02 minutes.
Without the 95-minute delivery, the mean is 29.0, the median 28 and the standard deviation about 7.15. One value moves the mean by 6 minutes and roughly triples the standard deviation; the median moves by 1 minute. This is why the median and IQR are useful resistant summaries for data with extreme values. A roughly symmetric distribution without extremes is often well described by its mean and standard deviation.
Describe the tail and useful positions
Right skew has a long high-value tail; left skew has a long low-value tail. The mean usually lies toward the long tail relative to the median, but check the shape because this rule of thumb has exceptions. s.skew() is positive for a right tail and negative for a left tail; it is about 2.71 for these delivery times.
s.quantile(0.9) gives 41 minutes here, a useful description of the upper part of the distribution. One of the 11 deliveries exceeds 45 minutes, about 9.1%. Compute threshold shares over recorded values when gaps exist. Small-sample quartiles can differ between programs: pandas uses linear interpolation by default. Record the method when exact quartiles matter.
Key Takeaways
- Use center, spread and a plot to describe a numeric distribution.
- The median and IQR resist extremes; the mean and standard deviation respond strongly to them.
- Check non-missing counts and matching standard-deviation settings.
- Percentiles and threshold shares can answer practical questions directly.
- Skew direction describes the long tail, not the side holding most observations.
3.2 Plots that show the shape of a distribution
A distribution plot reveals tails, peaks, gaps and limits that a center alone hides. Each plot emphasizes different features, so choose it for the question and check how display choices affect the picture.
Use meaningful histogram bins
A histogram divides a numeric scale into intervals and counts the observations in each. sns.histplot(data=df, x="minutes", binwidth=5) uses five-minute bins. seaborn chooses automatic bins by default; pandas and Matplotlib histograms default to 10. Try several reasonable bin settings. A very broad bin can merge two peaks, while very narrow bins can turn sampling noise into apparent structure. For integer counts, discrete=True centers a bar on each integer.
Describe the shape as symmetric, right- or left-skewed, uniform, or having one, two or several peaks. Look for gaps, caps and floors. Spikes at round values such as 30 and 60 minutes can reflect heaping from rounded self-reports. Two peaks near 25 and 50 minutes might reflect bike and car deliveries. Adding hue="vehicle" checks whether those groups explain the split.
Compare the main plot choices
| Plot | What it helps you see | What to check |
|---|---|---|
| Histogram | Counts within intervals, peaks and gaps | Bin width and alignment |
| KDE | A smooth estimate of distribution shape | Bandwidth and impossible tails |
| Box plot | Median, middle half and flagged extremes | Hidden peaks and sample size |
| ECDF | The share at or below each value | Axis units and the population |
A kernel density estimate, or KDE, smooths observations into a curve. sns.kdeplot(data=df, x="minutes") can be helpful, but bandwidth changes its shape. Smoothing can conceal caps or extend below zero even when durations are nonnegative. Read that extension as a feature of the estimate, not as negative observed durations.
A box plot draws the median inside a box from Q1 to Q3. Default whiskers reach the most extreme observed values still within 1.5 IQR of the box; points farther away appear separately. A whisker is therefore not necessarily the minimum or maximum, and its end is an observed value rather than the calculated fence.
Read cumulative shares and multiplicative scales
sns.ecdfplot(data=df, x="minutes")
An ECDF shows the share less than or equal to each value. Read horizontally from a share such as 0.9 to locate a percentile. It avoids the bin choice of a histogram and is useful for comparing how often values stay below a threshold.
A log scale helps with positive data spanning large ratios. On a base-10 scale, 10, 100 and 1,000 are equally spaced because each step multiplies by 10. Ordinary real logarithms require positive values; np.log1p(x) computes the natural logarithm of one plus the value and handles zero in nonnegative data.
When comparing differently sized groups, normalize within each group using stat="percent", common_norm=False, or use panels with shared axes. Finish with a numerical description, such as "Delivery times are right-skewed, with a median of 29 minutes and half of the orders between 25 and 36 minutes; one order took 95 minutes."
Key Takeaways
- Try sensible bin settings before deciding that a peak or gap is real.
- Check whether mixtures of groups explain multiple peaks.
- KDE smoothness can hide features or extend beyond possible observations.
- A box plot summarizes position and spread but hides modality and sample size.
- An ECDF gives cumulative shares without bins.
- Log scales show ratios; separately normalized plots help compare group shapes.
3.3 Category counts, order and denominators
Categorical summaries count observations in named groups. Their meaning depends on the labels, their order and the population used for each percentage. A numeric category code still needs a category summary.
Build a frequency table
s.value_counts() lists categories from most to least frequent. Use normalize=True for proportions and dropna=False to include a missing-value row. For text, describe() reports the number recorded, the number of distinct values, a most frequent value and its count.
In 200 survey responses, 30 ratings are "low", 50 are "medium" and 120 are "high". Dividing each count by 200 gives 15%, 25% and 60%. If those are the 200 people who answered a question in a larger survey, the percentages describe respondents to that question. Missing responses change the denominator when they are included.
Preserve a meaningful order
Nominal categories such as cities have no inherent rank; sorting by frequency can make comparisons easy. Ordinal categories have a natural order that should stay visible:
order = ["low", "medium", "high"]
counts = s.value_counts().reindex(order, fill_value=0)
An ordered categorical records the order for sorting, comparisons and plots. Its value_counts() still sorts by frequency by default; use sort=False or explicitly reindex the counts. A chart ordered "high", "medium", "low" because of frequency can make the scale harder to recognize.
Keep rare labels and gaps distinct
When many categories make a plot unreadable, retain the most common labels and collect the rest as "Other":
top = s.value_counts().nlargest(5).index
grouped = s.where(s.isna() | s.isin(top), "Other")
The presence of s.isna() keeps gaps missing. Without it, missing entries would also become "Other", mixing nonresponses with real rare answers. Report how many labels and observations were combined. Check that a rare category important to the question has not disappeared into the combined group.
Horizontal bar charts suit long labels. For example, sns.countplot(data=df, y="city", order=df["city"].value_counts().index) sorts city counts. Start the count axis at zero because bar length encodes the number of observations. A pie chart becomes difficult to compare when it contains many slices.
Turn numeric ranges into labeled categories
bands = pd.cut(
df["minutes"], bins=[0, 30, 45, np.inf],
labels=["up to 30", "over 30 to 45", "over 45"],
include_lowest=True
)
The default right-closed intervals put 30 in the first band and 45 in the second. include_lowest=True also includes 0 in the first band. Values outside these ranges become missing in the binned result, so validate negative durations before interpreting its missing count. Record the boundaries because a label such as "30–45" alone leaves the treatment of 30 unclear.
For nominal codes, map 1 and 2 to their region labels before displaying them. Averaging region codes would describe their arbitrary numbering, not the distribution of regions.
Key Takeaways
- Frequency tables report category counts or proportions; label the denominator.
- Keep ordinal values in their natural order, even when frequencies differ.
- Keep missing responses separate when combining rare labels as Other.
- Start bar-chart count axes at zero and make long labels readable.
- Give numeric bands explicit boundaries and check their endpoints.
3.4 Outlier flags and treatment decisions
An outlier lies far from the bulk of the data under a stated rule and context. It may be a recording error, a genuine rare event or a member of a separate group. A flag starts an investigation.
Calculate IQR fences
The IQR rule flags values below or above these limits:
For delivery times 18, 22, 25, 25, 27, 29, 31, 34, 38, 41 and 95, Q1 is 25 and Q3 is 36. The IQR is 11, so the lower fence is 8.5 and the upper fence is 52.5. The 95-minute order is flagged.
q1, q3 = s.quantile([0.25, 0.75])
iqr = q3 - q1
flag = (s < q1 - 1.5 * iqr) | (s > q3 + 1.5 * iqr)
These are the default fences underlying box-plot outlier points. A value exactly on a fence is inside it. In a long right tail the rule can flag many genuine high values.
Understand masking and robust scores
A z-score measures distance from the mean in standard-deviation units:
A common flag is an absolute z-score greater than 3. Here the mean is 35 and the sample standard deviation is about 21.02, so 95 gives a z-score of about 2.85. The extreme value has increased the very center and spread used to judge it. This effect, called masking, can let an unusual value escape the rule. The rule also fits roughly symmetric distributions better than skewed ones.
The median absolute deviation, or MAD, is the median of the absolute distances from the median. It uses resistant quantities. A modified z-score is:
Here the median is 29 and MAD is 5. For 95 minutes, the modified score is about 8.90, exceeding the common absolute threshold of 3.5. If MAD is zero, this division is undefined; inspect the repeated values and use another justified check instead of reporting an infinite score as a measured result.
Investigate before changing data
Domain rules come first. A negative elapsed duration is an error under this definition regardless of its statistical score. For a possible extreme, inspect the whole row, its timestamps, distance, group and source. A storm or a long route may explain a slow delivery.
| Evidence | Possible action |
|---|---|
| The source establishes the correct value | Correct it in code and log the change |
| A record is a proven error with no recoverable value | Remove or mark it missing, recording the effect |
| The value is genuine | Keep it; use resistant summaries or a useful scale |
| The conclusion depends heavily on the value | Compare results with and without it |
Comparing results with and without the observation is a sensitivity check. Capping extremes at a selected percentile, called winsorizing, is another deliberate transformation; report the cap and that values changed. Also inspect unusual combinations: two individually plausible measurements can form a rare point in a scatter plot.
Key Takeaways
- IQR fences flag values outside Q1 − 1.5 IQR and Q3 + 1.5 IQR.
- Extreme values can inflate the mean and standard deviation and mask themselves in z-scores.
- Modified z-scores use the median and MAD; a zero MAD needs another approach.
- Domain rules distinguish impossible values from possible extremes.
- Investigate the full record and document every treatment.
- Use sensitivity checks to show when extremes change a conclusion.
- Look for unusual combinations as well as single-variable extremes.
4Explore relationships between variables
Two variables may move together in a straight line, along a curve or only because distinct groups were combined. Use scatter plots to see the form, correlation coefficients to summarize specific aspects of association, and subgroup checks to investigate its source. Interpretation needs the population, usable sample size and collection process as well as the apparent strength of the pattern.
4.1 Scatter plots that reveal the relationship
A scatter plot places one observation at each pair of numeric values. Put a possible explanatory variable on the horizontal axis and the response on the vertical axis. The plot helps you see the form of a relationship before compressing it into a coefficient.
Describe four features
Use direction, form, strength and unusual points. Positive direction means the response tends to rise as the horizontal variable rises; negative means it tends to fall. Form might be straight, curved or split into clusters. Strength describes how closely points follow that form, which can be strong even when the form is curved.
For temperatures of 8, 12, 15, 18, 21, 24, 27 and 30 °C, daily bike rentals are 310, 420, 515, 600, 690, 760, 800 and 780. The relationship is strong and positive overall. It is close to linear up to about 27 °C, then levels off and falls slightly. Describing it only as "positive" misses the change at the warm end.
sns.scatterplot(data=df, x="temp_c", y="rentals")
Make dense plots readable
With many rows, points cover one another: overplotting hides where observations concentrate. Smaller markers and alpha=0.2 reveal some density through transparency. A hexbin plot aggregates observations into hexagons:
df.plot.hexbin(x="distance_km", y="minutes", gridsize=30)
A two-dimensional sns.histplot provides a similar view with rectangular bins. A random sample can reduce clutter; fix its seed so reruns show the same subset. When an axis takes only a few values, jitter spreads stacked points for visibility. The jitter is display movement, not extra measured precision.
Add groups and check a fitted line
Use hue="season" for color or sns.relplot(..., col="season") for separate panels. A pooled cloud may contain distinct seasonal clusters. Marker size can encode another quantity when its meaning stays clear.
sns.regplot(data=df, x="temp_c", y="rentals") adds a least-squares straight line and a shaded uncertainty band. A line helps the eye but can miss a bend. lowess=True draws a smooth curve when the statsmodels dependency is installed. Inspect the observations before choosing either display.
For a small selection of variables, a pair plot draws their pairwise scatter plots and individual distributions. For example, use bill length in mm, flipper length in mm, body mass in g and species as a color group. About three to six numeric variables stay readable; adding many more creates a wall of small panels.
Investigate crowding and shared time trends
Log axes can spread positive values spanning large ratios, such as population and total energy use. Keep the scale visible in the labels. When one extreme point sets a huge axis range, inspect it and add a clearly labeled view of the bulk so the remaining pattern is visible.
Two variables can also trace a pattern because both grow over time. Color points by date or plot each series against time. That check can reveal whether the apparent relationship is mostly a shared trend.
Key Takeaways
- Describe direction, form, strength and unusual observations.
- Use transparency, smaller points, binning or seeded samples to reveal density.
- Use color or panels to investigate groups inside a pooled pattern.
- Inspect curvature before relying on a straight fitted line.
- Check scales, extreme points and time trends that can dominate the display.
4.2 What a correlation coefficient measures
A correlation coefficient summarizes an aspect of association. Choose the coefficient for the relationship you want to describe, and read it beside a plot and the number of usable observations.
Pearson measures a straight-line relationship
Pearson's coefficient is the centered cross-product total divided by the product of the centered lengths:
Its range is −1 to 1. The sign gives direction and the magnitude gives linear strength. Perfect positive or negative values mean all usable points lie on a straight line, with both variables having variation. The coefficient has no unit, is unchanged by swapping axes, and is unchanged by a positive conversion between measurement units.
A coefficient near zero means little linear association. For inputs −3, −2, −1, 0, 1, 2, 3 and outputs equal to their squares, Pearson correlation is exactly zero despite a perfect U-shaped relationship. Correlation also differs from slope: a shallow line can have a high correlation.
Rank methods describe ordering
Spearman correlation computes Pearson correlation on ranks and describes monotonic association: values tend to move consistently upward or downward, even along a curve. Kendall's tau compares concordant observation pairs, ordered the same way on both variables, with discordant pairs, ordered oppositely. Rank methods are less sensitive to extreme magnitudes than Pearson.
For the temperature–rental example, Pearson is 0.974, Spearman 0.976 and Kendall 0.929. The scatter plot still reveals the leveling above about 27 °C. A high coefficient can coexist with important curvature.
Compute a matrix carefully
cols = ["temp_c", "rentals", "distance_km"]
corr = df[cols].corr(method="spearman")
sns.heatmap(corr, annot=True, fmt=".2f",
vmin=-1, vmax=1, center=0, cmap="vlag")
For a mixed DataFrame, df.corr(numeric_only=True) selects numeric columns; also exclude identifiers and category codes that lack a meaningful numeric relationship. Missing values are excluded pair by pair, so matrix cells may describe different subsets. Check each pair's usable count. The matrix is symmetric. Self-correlation is 1 where defined; a constant column has undefined correlation and a missing row, column and diagonal entry.
Check sensitivity to the data geometry
For inputs 1 through 10 and outputs 3, 1, 4, 2, 5, 3, 4, 2, 5, 3, Pearson is about 0.307. Adding the single point (30, 30) raises it to 0.938; Spearman becomes about 0.462. One point can dominate Pearson while the bulk retains a weak pattern.
Anscombe's quartet makes the same lesson visible: four datasets share a mean input of 9, mean output of 7.50, input variance of 11, correlation near 0.816 and fitted line , yet their plots show a cloud, a curve and different influential points. Report the value and count with any verbal strength label; fields have different conventions for labels such as "strong".
Key Takeaways
- Pearson describes linear association, with no unit and values between −1 and 1.
- Zero Pearson correlation can hide a strong curved relationship.
- Spearman and Kendall describe ordering and reduce sensitivity to extreme magnitudes.
- Pairwise missing-value removal can give matrix cells different samples.
- A constant variable has undefined correlation.
- Plots and sensitivity checks reveal features that identical coefficients can hide.
4.3 Associations, causes and misleading comparisons
An association describes how variables occur together in the observed data. Its cause needs a separate explanation. Possible sources include a causal effect, reverse causation, a third variable, selection and chance.
Ask what could explain the link
A confounder influences both variables and creates or distorts their association. Hot weather increases ice-cream sales and swimming accidents. Reducing ice-cream sales would leave the weather-related risk in place. A mediator is different: it lies on the causal path between the variables.
Reverse causation reverses the proposed direction. Customers who visit help pages more often may have more failed orders because failures send them to those pages. The observed link gives no basis for expecting fewer failures after hiding help pages.
Separate subgroup patterns from the pooled result
Simpson's paradox occurs when the pooled comparison reverses the direction seen in each subgroup. The kidney-stone treatment data from a 1986 study illustrate the arithmetic:
| Group | Treatment A | Treatment B |
|---|---|---|
| Small stones | 81/87 = 93.1% | 234/270 = 86.7% |
| Large stones | 192/263 = 73.0% | 55/80 = 68.8% |
| Combined | 273/350 = 78.0% | 289/350 = 82.6% |
A has the higher success share in both stone-size groups. B has the higher pooled share because A was used mostly in the harder large-stone cases. Pooled rates weight subgroup rates by their counts. Inspect composition and compare meaningful subgroups before interpreting a combined result.
Check time, selection and aggregation
Two unrelated series can both rise over the years and appear highly correlated. Plot them over time and inspect period-to-period changes or within-period relationships. The level relationship may mostly reflect their common time trend.
A selected range also changes what is visible. Among students selected for high entrance scores, the score range is narrow, and the score–grade correlation can be much weaker than among all applicants. Describe the selected population; its coefficient answers a different question from the coefficient for all applicants.
A correlation between city averages belongs to cities. Applying it to individual residents is the ecological fallacy. Aggregation can strengthen, weaken or reverse an association because the within-group pattern and between-group composition need not agree.
Treat a wide search as a source of hypotheses
With 20 numeric variables there are 190 distinct pairs. Searching those pairs and many segments gives chance patterns many opportunities to appear. Report how widely you explored, then identify a surprising pattern as a hypothesis for new data or a planned confirmatory analysis.
Use wording tied to the observations: "Orders placed in rain were associated with longer delivery times during this period." That statement records a relationship. A claim that rain caused the difference would also need evidence ruling out competing explanations, such as a different mix of routes or times.
Key Takeaways
- Consider reverse causation, confounding, selection and chance when explaining an association.
- Confounders influence both variables; mediators lie on a causal path.
- Pooled comparisons can reverse subgroup comparisons when group composition differs.
- Time trends, selected ranges and aggregation change the association being measured.
- Group-level relationships need group-level interpretations.
- Use exploratory patterns to form hypotheses and report the extent of the search.
5Compare segments of the data
A combined summary can conceal differences between cities, channels or periods. Compare group distributions, category shares and event rates with their denominators and sample sizes visible. Then check whether group composition, missing outcomes or unequal time coverage explains the apparent gap. A useful comparison matches the measure to the question and keeps the basis of each number clear.
5.1 Compare centers, spreads and tails across groups
A segment is a subset defined by a category, numeric band or period. A useful comparison shows how the whole distribution differs, with the amount of evidence behind each summary visible.
Put group sizes beside summaries
by_city = df.groupby("city")["minutes"].agg(
["count", "mean", "median", "std"]
)
The count here is the number of recorded durations. If some durations are missing, also report the number of orders and the missing share so the table's basis is clear. Named aggregations can give columns useful labels, such as orders=("order_id", "count") and median_min=("minutes", "median").
Consider these delivery times:
| City | Times in minutes | Mean | Median | Sample SD |
|---|---|---|---|---|
| Lyon | 24, 30, 27, 33, 29, 31 | 29.0 | 29.5 | 3.2 |
| Porto | 22, 25, 21, 40, 24 | 26.4 | 24.0 | 7.8 |
| Tartu | 19, 55 | 37.0 | 37.0 | 25.5 |
Tartu has the highest average but only two observations. One long delivery dominates its result. Small groups often appear at both ends of a ranking because their summaries swing more with individual observations. Set a minimum count before ranking and keep small groups visible as incomplete evidence.
Choose a display for the distribution
A box plot on a shared numeric axis compares medians, middle halves and flagged extremes. A violin plot adds estimated shape. With small groups, strip or swarm plots show each observation and make the sample size easier to see.
For histograms, use panels with the same axes or normalized overlays such as stat="density", common_norm=False. Count overlays mainly reveal which group contains more records. A density overlay lets each group's shape be compared on the same basis.
Two groups with the same mean may have very different tails. A city with mostly quick deliveries and a few very long ones can share a mean with a city of consistently moderate times. Compare spread, percentiles and a relevant threshold share alongside the center.
Name the baseline of a difference
The Tartu–Lyon mean difference in this sample is 8 minutes. Relative to Lyon, it is about 27.6%:
The baseline is Lyon's 29 minutes. Reversing the baseline changes the relative percentage, even though the absolute gap remains 8 minutes. Report the limited group counts with this descriptive calculation; the arithmetic alone cannot establish a stable city difference.
Check composition within groups
df.groupby(["city", "vehicle"])["minutes"].median().unstack()
This city-by-vehicle table can reveal whether a city difference comes from the mix of vehicles. A pivot table with the same grouping and median gives the same comparison. Inspect counts for these smaller cells too: splitting a group can make the evidence thinner even while clarifying the pattern.
Key Takeaways
- Report group counts with center and spread, including the basis of each count.
- Use shared scales and within-group normalization to compare distributions.
- Inspect tails and threshold shares as well as centers.
- Name the baseline when reporting a relative difference.
- Check subgroup composition and small-cell counts before ranking groups.
5.2 Shares, rates and the right denominator
Counts answer how many events occurred. Rates answer how often events occurred relative to their opportunities. When groups differ in size, the choice of denominator can change the apparent comparison.
Read a cross-tabulation in the intended direction
A cross-tabulation counts combinations of two categories:
pd.crosstab(df["region"], df["channel"], margins=True)
North has 30 app orders and 20 web orders; South has 60 app orders and 90 web orders. South has more app orders, but its larger order total changes the share comparison.
| Question | Denominator | Result |
|---|---|---|
| Within North, what share uses the app? | All 50 North orders | 60% |
| Within South, what share uses the app? | All 150 South orders | 40% |
| Of all app orders, what share comes from North? | All 90 app orders | 33.3% |
normalize="index" divides by row totals; normalize="columns" divides by column totals. normalize="all" uses the grand total. Read the question aloud before choosing the normalization. Row-normalized rows each sum to 1, while column-normalized columns do so.
Preserve unknown outcomes
A Boolean mean gives the share of True values. For a late-delivery flag, missing times need special treatment:
df["late"] = df["minutes"].astype("Float64") > 45
rates = df.groupby("city")["late"].mean()
known = df.groupby("city")["late"].count()
Nullable Float64 preserves missing times as missing flags. The grouped mean then estimates the late share among recorded times. Ordinary floating-point NaN > 45 is False, which would count unknown times as on time and lower the rate. State the known-outcome denominator and report missingness beside the rate.
Distinguish two kinds of percentage change
A late rate rising from 4% to 5% rises by 1 percentage point. The relative increase uses the original rate as baseline:
Write "rose by 1 percentage point, a 25% relative increase." A bare "rose by 1%" leaves the meaning unclear. For a rate based on a small cell, show its count too: 1 late order in 2 produces 50%, but a single additional on-time order changes it to one third.
Compare outcome shares with group size
If North supplies 25% of all orders but 40% of complaints, it is overrepresented among complaints. That compares its complaint share with its order share. It is a different question from the complaint rate within North, which requires North's complaint count divided by North's orders.
Use grouped bars, 100% stacked bars or a labeled heatmap to show proportions. A row-normalized region-by-channel table plotted as stacked bars compares channel mix within regions. Put the denominator in the title or axis label so readers can distinguish that mix from the regional composition of each channel.
Key Takeaways
- Row shares, column shares and grand-total shares answer different questions.
- Compare event rates using the opportunities in the same segment.
- Keep unknown outcomes missing when creating Boolean rate indicators.
- Percentage-point gaps subtract rates; relative changes divide by the baseline rate.
- Show counts beside rates, especially for small cells.
- Compare a group’s outcome share with its base share to assess overrepresentation.
5.3 Time comparisons with comparable periods
Period totals depend on both activity and the time covered. Before describing a rise or fall, check period boundaries, missing dates and the normal seasonal pattern. Compare segments on a measure that fits their size.
Aggregate by calendar period
With parsed order timestamps, monthly counts can be built as follows:
monthly = (
df.set_index("order_date")
.resample("MS")["order_id"].count()
)
"MS" labels each calendar month by its first day. Use "ME" for month-end labels in current pandas. For a monthly city table, combine pd.Grouper(key="order_date", freq="MS") with the city grouping and use .size().unstack(). The method counts rows, so first establish that a row is an order.
Check exposure before comparing totals
Suppose there are 100 orders per day from August 1 through October 12. Monthly totals are 3,100 in August, 3,000 in September and 1,200 in the recorded part of October. The October total looks much lower, yet every observed day has the same activity.
Divide each total by its observed days: 31, 30 and 12. All three daily averages are 100. Compare such rates or omit incomplete periods. The denominator must represent actual coverage: an unrecorded day cannot safely be treated as a day with zero activity.
Even complete months have different lengths. They can also contain different numbers of busy weekdays. If Saturday activity is high, compare matching weekdays or rates that account for the weekday mix before interpreting a monthly difference.
Separate a cycle from a lasting change
Seasonality is a recurring pattern, such as a weekly or yearly cycle. daily.rolling(7).mean() smooths a seven-day window in a regularly spaced daily series. It makes a weekly cycle less prominent while retaining slower changes. Compare December with the previous December when the question is about year-over-year change in a seasonal activity.
A few unusually high days can look like a trend in a short window. Show enough history to understand the normal range of movement, and retain the unsmoothed values when they help explain an important event.
Check gaps and compare segment paths
A resampled count of zero can mean no recorded events or a collection outage. A sudden level change can reflect a new app version or a changed definition. Confirm coverage and collection history with the data source before treating those points as changes in behavior.
Plot each segment as a line on shared axes, with units and a consistent color per group. Large groups may dominate count plots. Shares, rates or an index relative to each group's starting value can make smaller groups' changes visible; label which measure is shown.
A cohort groups customers by when they started. Following March starters and April starters by time since their first order can separate changes within customers' histories from changes in the overall customer mix.
Key Takeaways
- Check complete coverage and use current calendar-period aliases.
- Compare totals only with their period length and weekday mix in view.
- Use observed exposure for rates and investigate gaps before treating them as zeros.
- Compare seasonal periods and view enough history to interpret movement.
- Use segment rates or indexed paths when group sizes differ.
- Cohorts separate time since starting from changes in the overall customer mix.
6Report exploratory findings reproducibly
Exploration becomes useful to other people when its findings answer clear questions and its evidence can be checked. Select the charts and numbers that explain the results, distinguish observations from proposed causes and actions, and state the limits that affect interpretation. Prepare a clean notebook, current exports and enough provenance for an authorized reader to repeat the work.
6.1 Findings that answer the question
A useful finding connects a question to evidence. It gives the size of a pattern, the population and period it describes, and the limitations that affect its interpretation. Build the report around questions instead of the order in which charts were created.
Give a finding an evidence base
For example: "From January to September 2026, the median delivery time was 29 minutes; 8% of the 48,200 orders took longer than 45 minutes. The late share was highest on Friday and Saturday evenings: 14%, compared with 6% on other evenings."
This statement names the period, center, threshold, denominator and comparison. Use enough precision for the decision. "29 minutes" is easier to use than "29.4545 minutes" when the extra digits add no practical information. For a skewed duration, a median and an upper percentile often explain the experience better than a mean alone.
Separate what happened from why and what to do
| Kind of statement | Example | Role |
|---|---|---|
| Observation | "Late deliveries were more common on rainy evenings." | Describes an observed association |
| Interpretation | "Rain slows couriers down." | Proposes a causal explanation |
| Recommendation | "Schedule extra couriers on rainy evenings." | Proposes an action based on that explanation |
A recommendation depends on assumptions about the cause and the action's effect. Keep those assumptions visible and name the follow-up evidence needed. An observation can be useful even while its explanation remains a hypothesis.
Make a chart explain one finding
Select the few charts that answer the questions and the surprises that matter. Keep other useful exploratory views in an appendix or notebook. Give a report chart a title that states its finding, such as "Late deliveries peak on Friday and Saturday evenings." Label axes with units, sort categories where appropriate and keep each segment's color consistent across charts. Include the data source and period.
Report contradictory evidence too. If a pooled chart suggests improvement but one large segment worsened, the segment result matters to the interpretation. Selecting only charts that support an expected story would hide information the reader needs.
Organize a compact report
A clear order is question and context; data source, period, row count and row meaning; quality issues and cleaning decisions; three to five key findings with evidence; limitations; next steps. Each finding should answer a question or explain an important surprise.
Limitations identify what the data cover, what was excluded and why, how much is missing, and the association-based nature of the patterns. For example, disclose 37 canceled orders removed from a completed-delivery comparison. Next steps can include checking a hypothesis on new data, collecting a missing variable, fixing a source-system issue or investigating a question for later modeling.
Key Takeaways
- Give each finding evidence, size, population and period.
- Separate observations, interpretations and recommendations.
- Select and label charts around findings, keeping contradictory evidence visible.
- Report useful precision, denominators, missingness and exclusions.
- End with concrete next questions or actions supported by the analysis.
6.2 Share results that others can rerun
A shared analysis needs both readable results and a route back to the code and inputs that produced them. Prepare a final notebook whose order, outputs and supporting files agree.
Clean and execute the final notebook
Remove dead and duplicate cells, keep dependencies in run order and add Markdown that explains decisions between code sections. Preserve the more experimental work separately when it is useful. Restart the kernel and run all cells before every export. Inspect both errors and changed outputs: a successful run can still reveal that an old written number is stale.
Generate reported numbers from code:
s = df["minutes"]
print(f"Median delivery time: {s.median():.0f} minutes")
A number typed into a paragraph can remain unchanged after the data refresh. Keep the displayed statement linked to the calculation so the next run updates it.
Export reports, figures and tables
An HTML export can be read in a browser without a Python environment:
jupyter nbconvert --to html --execute report.ipynb
The execute option runs the notebook before conversion. Check the resulting report as well as the notebook. Publishing tools such as Quarto offer another route to a readable report.
Save figures and tables from code so reruns refresh the files:
fig.savefig("figures/late_by_hour.png", dpi=200,
bbox_inches="tight")
summary.to_csv("output/city_summary.csv")
Create the destination folders as part of project setup. A screenshot preserves one moment's output but leaves no automatic link to the next calculation.
Include the provenance and run instructions
Record the data source, extraction date, input file or version, row count, filters, software versions and random seeds. These details explain which input and settings produced a finding. Collect changing settings near the top of the notebook so a monthly update has one clear entry point.
A project can separate raw and processed data, notebooks, figures and exported tables. Include a README that states the purpose, expected working directory and run steps, plus a package environment file. Keep notebook, instructions and environment together when sharing the rerunnable analysis. If inputs need controlled access, state how an authorized reader obtains the matching data version.
Check what the outputs reveal
Before sharing, inspect every output cell and export for personal information. Preview tables can expose names, email addresses and exact locations even when the final chart is aggregated. Remove, mask or aggregate identifying columns as appropriate, and follow the agreements and data-protection rules that apply to the dataset.
Key Takeaways
- Clean the final notebook and verify a fresh restart-and-run-all.
- Compute reported numbers in code and save figures and tables programmatically.
- Execute before exporting so the report contains current outputs.
- Include data provenance, settings, environment and clear run instructions.
- Inspect all shared outputs for personal information.
Exploratory Data Analysis in Python examples
1Turn a broad request into a rerunnable analysis
12 steps
- 1IntroductionGive the analysis a clear starting point
A team asks, "How reliable is our repair service?" You have a table of repair tasks and a notebook. Before making a chart, decide what the team needs to know and what each row can tell you. You will turn the request into concrete questions, identify the level of detail, and arrange the work so a fresh notebook session can repeat it.
- 2ExplanationAsk questions that suggest a comparison
Start with the decision: perhaps the team needs to identify where long waits occur. Useful questions are "What is the typical repair time?", "What share exceeds our service threshold?" and "How do times differ by device model?" Each names a quantity or comparison. Keep a place for new questions when a plot reveals an unexpected group or quality issue.
- 3ExampleEstablish what the rows represent
The file contains 900 task rows and 300 distinct job IDs. A job may involve several tasks. The ratio is 3 tasks per job on average, but individual jobs can have different task counts. Counting rows answers a task question. A question about the number of jobs needs distinct job IDs or a job-level table, with a justified rule for combining task information.
- 4ChallengeChoose the count that matches the question
The team wants the share of jobs with a delay. The table has multiple task rows per job. Would taking the mean of a task-level delay flag answer that question?
---reveal---
It gives the delayed-task share and weights multi-task jobs more heavily. First define when a job counts as delayed, combine task information at the job level, and divide delayed jobs by eligible jobs.
- 5ConnectionReturn to quality checks when a pattern surprises you
Exploration runs in a loop: questions, loading, profiling, distributions, relationships, comparisons and findings. Suppose one device model seems to take 1,000 times longer. Return to the column definitions and source units before explaining a performance gap. The surprise has generated a quality question, which can change the next analysis step.
- 6Key InsightSeparate discovery from a planned check
A pattern found after searching many charts is a lead for further work. Confirmatory analysis checks a hypothesis stated in advance, ideally on fresh observations. Also check who is represented: ratings from customers who volunteered feedback describe those respondents. The collection process determines what evidence is needed to extend the finding to everyone served.
- 7ExplanationSee what the kernel remembers
A notebook kernel retains variables between cell runs. If you create a filtered table, delete its defining cell and keep plotting it, the current session can still work. A new kernel has no way to recreate that table. Execution counts reveal run order, but the decisive check is a restart followed by running every cell from top to bottom.
- 8ExampleMake dependencies visible
Organize the notebook as purpose and questions, imports, settings, then analysis sections. Put changing paths, thresholds and seeds in the settings cell. Use a relative project path such as
Path("data/raw/repairs.csv"), and document the working directory. Keep raw files unchanged and express corrections in code so the next dataset follows the same procedure. - 9TipControl the parts that vary between runs
Record package versions and save an environment file. Fix the seed of each random step; for a table with at least 200 rows,
df.sample(n=200, random_state=17)repeats the sample with matching inputs and software. A fixed seed cannot restore an overwritten input file, so record the data version and extraction details as well. - 10ChallengeFind an invisible dependency
A summary cell runs successfully now. After restarting and running all cells, it fails because
job_totalsis undefined. What does this tell you, and what should you repair?---reveal---
The old kernel held a variable that the visible sequence does not create. Restore or move its defining code before the summary, then rerun the whole notebook. Saving the old output would preserve the result without repairing its dependency.
- 11ChallengeCheck a new file before answering the same question
Next month’s file arrives. Your question counts jobs, and the prepared job-level table is expected to have unique job IDs. How can you combine the row-meaning check with a dependable rerun?
---reveal---
Load the new version through the settings cell and assert that the prepared job key is unique. Restart and run all cells. A failed check exposes an input or preparation problem before repeated jobs distort the reported share.
- 12RecapCarry the question and the computation together
You now have a question tied to a decision, a row meaning that determines counts, and a notebook with visible dependencies. Use the same habits for the first profile: record the input version, inspect its structure and let checks stop the run when assumptions fail. When a new pattern appears, decide whether it needs another quality check, a new comparison or a follow-up hypothesis.
2Inspect a new file before trusting its patterns
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionBuild a profile with evidence behind it
- 2ExplanationInspect beyond the first few rows
- 3ExampleChoose analysis types from column meanings
- 4ChallengeCheck what a conversion lost
- 5ExplanationMeasure gaps before choosing a treatment
- 6ExampleSee how deletion changes the mix
- 7Key InsightKeep the missingness claim within the evidence
- 8ChallengeInterpret duplicate counts
- 9ExampleSeparate missing values from invalid recorded values
- 10ExplanationCheck agreement between fields
- 11ChallengeChoose a justified action
- 12ChallengeConnect the profile to the next comparison
- 13RecapKeep the profile alongside the findings
3Build a complete picture of each variable
17 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionLook beyond one average
- 2ExplanationCompare center and spread
- 3ExampleWork out the centers
- 4ExampleAdd a measure of the middle spread
- 5ChallengePredict the effect of an extreme
- 6ExplanationUse bins to reveal structure
- 7ExampleRead what a cumulative plot answers
- 8ChallengeInvestigate a suspicious second peak
- 9TipRead smooth curves and scales carefully
- 10ExampleSummarize categories with a clear basis
- 11ExplanationKeep Other distinct from missing
- 12ChallengeCheck an interval boundary
- 13ExampleCalculate outlier fences
- 14Key InsightSee how a value can mask itself
- 15ChallengeMake a treatment decision from evidence
- 16ChallengeCombine the picture into a finding
- 17RecapChoose a view for the question
4Read an association before explaining it
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionKeep the observations in view
- 2ExampleDescribe more than the direction
- 3TipReveal density and groups
- 4ChallengeChoose a plot adjustment with a purpose
- 5ExplanationIdentify Pearson’s target
- 6ExampleUse a curved example to test the meaning
- 7ExplanationUse ranks for consistent ordering
- 8ChallengeInvestigate a coefficient dominated by one point
- 9TipRead the sample behind a heatmap cell
- 10ExplanationList plausible directions and common causes
- 11ExampleCompare within difficulty groups
- 12ExampleSee the pooled reversal
- 13ChallengeChoose the level of interpretation
- 14ChallengeChoose follow-up evidence for a surprising result
- 15RecapReport the pattern and the remaining question
5Compare groups on the same basis
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionMake the comparison’s basis visible
- 2ExampleInclude enough information to judge a mean
- 3ExampleExpress a gap with its baseline
- 4ChallengeLook for a hidden distribution difference
- 5ExampleChoose the direction of a percentage
- 6TipKeep missing outcomes out of the False group
- 7ChallengeDistinguish the gap from relative change
- 8ChallengeTest whether counts answer the rate question
- 9ExplanationAggregate the period you intend to compare
- 10ExampleCalculate exposure-adjusted activity
- 11ConnectionSeparate a recurring cycle from change
- 12ChallengeInvestigate a zero before interpreting it
- 13Key InsightAlign customer histories
- 14ChallengeCombine rate and coverage checks
- 15RecapAttach every comparison to its denominator
6Turn exploration into a checkable report
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionHelp a reader assess the result
- 2ExampleWrite a finding with size and scope
- 3ExplanationSeparate the layers of a claim
- 4TipSelect charts around the question
- 5ChallengeRepair an overbroad finding
- 6ExplanationMake the final run the source of the report
- 7ExampleExport current artifacts
- 8ExplanationRecord what a later analyst must match
- 9ChallengeCheck the whole sharing surface
- 10ChallengeResolve a changed output before release
- 11RecapDeliver evidence with a route back to it
Exploratory Data Analysis in Python practice questions
Set 1: Start with trustworthy data10 Practice Questions
Question 1 of 10
len(df) count?Show answerHide answer
Question 2 of 10
Show answerHide answer
Question 3 of 10
Show answerHide answer
Question 4 of 10
clean successfully, but a fresh restart-and-run-all raises NameError at that cell. What should you investigate first?Show answerHide answer
Question 5 of 10
pd.to_numeric(s, errors="coerce"), what needs investigation?Show answerHide answer
Question 6 of 10
s.duplicated(keep=False).sum() return?Show answerHide answer
keep=False includes those first copies so you can inspect the complete conflicts.Question 7 of 10
Show answerHide answer
between returns False for them, mixing two different quality issues.Question 8 of 10
Show answerHide answer
Question 9 of 10
Show answerHide answer
Question 10 of 10
Show answerHide answer
Set 2: Read distributions and investigate extremes10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
value_counts(normalize=True) report?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
bins=[0, 20, 50, np.inf], include_lowest=True and default interval closure. Where do 0, 20 and 50 go?Show answerHide answer
Save this lesson to see this answer.
Set 3: Interpret relationships and group differences10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
min_periods=400. Can its x–y cell validly contain 0.82?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Choose denominators and comparable periods10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 5: Make and share defensible findings10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Assertion
B
Base rate
Baseline
Bin
C
Cardinality
Cohort
Column normalization
Confirmatory analysis
Confounder
Consistency
Correlation matrix
Cross-tabulation
D
Data dictionary
Data type
Data-quality log
Domain rule
Duplicate record
E
ECDF
Ecological fallacy
Environment file
Exploratory data analysis
F
Finding
Frequency table
G
Group size
H
Heaping
Hidden state
Histogram
Hypothesis
I
Interpretation
Interquartile range
J
Jitter
K
Kendall’s tau
Kernel
Kernel density estimate
Key
L
Limitation
Logarithmic scale
M
MAR
Masking
MCAR
Mean
Median
Median absolute deviation
Mediator
Missingness
MNAR
Modified z-score
Monotonic association
N
Nominal variable
Notebook
O
Observation
Ordinal variable
Other category
Outlier
Overplotting
P
Pair plot
Partial period
Pearson correlation
Percentage point
Percentile
Programmatic export
Provenance
R
Random seed
Rate
Recommendation
Relative difference
Reproducibility
Resampling
Reverse causation
Rolling mean
Row normalization
S
Scatter plot
Seasonality
Segment
Sensitivity check
Simpson’s paradox
Skewness
Spearman correlation
Standard deviation
Static report
T
Tukey fences
U
Unit of observation
V
Validity
W
Winsorizing
Z
Z-score
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.