Project: A Reproducible Data Analysis Notebook
Answer one revenue question with NumPy, make every cleaning decision visible, and ship an executed notebook that another person can rerun.
By the end, you’ll be able to…
- Turn a business question into an explicit dataset schema.
- Clean missing and invalid values with documented NumPy masks.
- Group totals with unique labels and bincount weights.
- Present results and limitations in an executed, restart-safe notebook.
01Start from a question and a schema
A notebook is not a pile of cells; it is an argument. Begin with one question a reader can evaluate, such as which region produced the most net revenue after returns. Write down the unit of a row, the currency, the time range, and the meaning of every column before loading any data.
For this project each row is one order line with a region, a unit count, a unit price, and a returned flag. Revenue is units multiplied by price, but a returned line contributes zero net revenue while still costing the gross amount. Naming that rule now prevents a chart later that quietly disagrees with the finance definition.
import numpy as npregions = np.array(["North", "South", "North", "East", "South", "North"])units = np.array([3, 5, 2, 4, 1, 6])prices = np.array([19.95, 12.50, 19.95, 8.00, 12.50, 19.95])returned = np.array([False, False, True, False, True, False])gross = units * pricesnet = np.where(returned, 0.0, gross)print(np.round(net, 2))[59.85 62.5 0. 32. 0. 119.7 ]
02Clean with visible rules
Real extracts contain missing values, negative quantities, and prices recorded as zero. Clean them with boolean masks before computing a total, and report how many rows each rule removed. A silently dropped row is a result nobody can audit.
np.isfinite rejects both NaN and infinity. Combine it with business conditions using & and wrap each comparison in parentheses. The mask is a copy of the decisions, not a hidden mutation of the source arrays. Keep the cleaned arrays and the count of rejected rows for the notebook's methods section.
import numpy as npunits = np.array([3.0, 5.0, np.nan, 4.0, -1.0, 6.0])prices = np.array([19.95, 12.50, 19.95, 8.00, 12.50, np.nan])valid = np.isfinite(units) & np.isfinite(prices) & (units >= 0) & (prices > 0)print(valid)print(int((~valid).sum()))[ True True False True False False] 3
03Group with explicit labels and weights
np.unique with return_inverse gives each region a stable integer code, and np.bincount sums weights by that code. The pair is a compact group-by for small categorical data. Names and totals stay aligned because both come from the same unique call.
State the axis and the unit in the output. A table of totals is easier to trust when the currency and the row count are visible. Sort by the label or by the total deliberately; do not rely on the order in which regions happened to appear in the extract.
import numpy as npregions = np.array(["North", "South", "North", "East", "South", "North"])net = np.array([59.85, 62.50, 0.0, 32.0, 0.0, 119.70])names, codes = np.unique(regions, return_inverse=True)totals = np.bincount(codes, weights=net)for name, total in zip(names, totals): print(f"{name:<6} {total:8.2f}")East 32.00 North 179.55 South 62.50
04Make the notebook reproducible
A reproducible notebook runs from top to bottom in a fresh kernel. Put imports and configuration in the first cell, load data with a relative path or a documented URL, and avoid hidden state from cells executed out of order. Restart and run all before you trust the output.
Use markdown cells to state the question, the schema, the cleaning rule, and the limitation. A chart needs labeled axes, a stated unit, and an honest baseline. Export an executed HTML copy with nbconvert so a reader can see the outputs without installing the environment, and keep the notebook and any helper module under version control.
py -m pip install numpy jupyterpy -m jupyter labpy -m jupyter nbconvert --to html --execute analysis.ipynbThe ideas worth keeping.
- A notebook should answer one question with a documented schema and unit.
- Cleaning rules belong in visible masks with counts, not silent drops.
- Grouped totals need stable labels, an explicit axis, and a restart-safe execution.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Compute average revenue per region
Using the grouped example, divide each region total by its row count. Print the region, count, and average with two decimals.
Explore a worked solution
import numpy as npregions = np.array(["North", "South", "North", "East", "South", "North"])net = np.array([59.85, 62.50, 0.0, 32.0, 0.0, 119.70])names, codes = np.unique(regions, return_inverse=True)totals = np.bincount(codes, weights=net)counts = np.bincount(codes)for name, total, count in zip(names, totals, counts): print(f"{name:<6} {count} {total / count:8.2f}")bincount without weights counts rows per code. Dividing the weighted total by that count gives the average per row, not per region-day; label the unit accordingly.
Report the cost of returns
Given the original arrays, compute gross revenue for returned rows and print the count and the lost amount rounded to two decimals.
Explore a worked solution
import numpy as npunits = np.array([3, 5, 2, 4, 1, 6])prices = np.array([19.95, 12.50, 19.95, 8.00, 12.50, 19.95])returned = np.array([False, False, True, False, True, False])lost = units[returned] * prices[returned]print(int(returned.sum()), round(float(lost.sum()), 2))Boolean indexing selects the returned rows before multiplication. The count and lost amount answer a different question from net revenue, so keep both metrics visible.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Compute average revenue per region
Using the grouped example, divide each region total by its row count. Print the region, count, and average with two decimals.
name:<6 count total / count:8.2f
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
Pause. Put the pieces together.
You’ve reached the end of NumPy and Jupyter Notebooks. Before moving on, use this task to check what you can do independently.
Create a Jupyter notebook that imports NumPy, performs statistical analysis on a dataset, and documents the findings with markdown cells.
Try it in your own environment and write down the result. This is a self-check; reading completion does not evaluate your code.
Review this moduleOne lesson closer.
Reading is a start. Trying it yourself makes the connection.