Course index · Previous · Next

Write Decision Memos and Share Analysis Safely

Module 7: Reliable Analysis Workflows · Lecture 29 of 32

A useful report connects evidence to an appropriately limited decision. Separate findings from explanations, preserve uncertainty, and deliver only the data needed by the audience.

What you will learn

Understand the problem

Structure the memo around the decision

Start with the question, population, period, and metric. Then present the two or three findings that change what a reader should do next. Attach the supporting table and exact calculation rather than repeating every intermediate step in the main narrative. A technical appendix can hold the audit, schema, and reproducibility command.

Use calibrated language

“The paid channel has the largest recorded net revenue in this export” is descriptive. “Paid advertising caused growth” is causal and unsupported here. A useful recommendation might be to obtain spend and exposure data before judging incremental return. Name what information would change the conclusion. For synthetic course data, every result must remain explicitly illustrative.

Limit the delivery population

A summary report rarely needs individual customer identifiers. Remove unnecessary fields, aggregate at the decision grain, and inspect small groups that could reveal individuals in real data. This course uses fictional IDs, but learning the habit matters before using real exports. Hashing a stable personal identifier is not automatically anonymization.

Output formats have behavior

Escape dynamic text before inserting it into HTML. A CSV opened by a spreadsheet may interpret text beginning with formula characters as executable formulas. Prefixing a text value with an apostrophe is a common consumer-dependent mitigation, not a universal guarantee. Keep numeric columns typed, separate machine-readable data from spreadsheet-facing text, and verify behavior in the intended consumer. Do not alter signed numeric values by treating every minus sign as malicious text.

Run the worked example

Download and extract the course kit linked below. Open a terminal in the extracted folder, complete the setup in Lecture 2, and run python run_course.py --lesson 29 --examples-only. Each script starts from the shared dataset; it does not depend on a notebook cell from another lecture. All retail records are fictional and amounts are integer paise unless a chart explicitly says INR.

from pathlib import Path
import numpy as np
import pandas as pd
from course_data import load_orders, load_raw, load_customers, clean_orders, ROOT, OUT

import html
orders = load_orders()
channel = orders.groupby('channel')['net_paise'].sum().sort_values(ascending=False)
leader = str(channel.index[0])
statement = f'The largest recorded channel total in this fictional export is {leader}.'
unsafe_label = '<script>alert(1)</script>'
escaped = html.escape(unsafe_label)
assert '<script>' not in escaped
notes = pd.Series(['ordinary note', '=1+1', '  @SUM(A1:A2)'], dtype='string')
def spreadsheet_text(value):
    return "'" + value if value.lstrip().startswith(('=', '+', '-', '@')) else value
safe_notes = notes.map(spreadsheet_text)
print(statement)
print('Limit: channel attribution is descriptive; spend and randomization are absent.')
print(safe_notes.to_string(index=False))
assert safe_notes.iloc[1].startswith("'")

Verified output

The largest recorded channel total in this fictional export is organic.
Limit: channel attribution is descriptive; spend and randomization are absent.
 ordinary note
         '=1+1
'  @SUM(A1:A2)

Walk through the result

The finding reports only an observed ranking and immediately names its main limitation. html.escape converts markup characters into display text, preventing the example label from becoming an HTML element. The spreadsheet transformation applies only to declared text, leaving integer monetary columns untouched. A real delivery process should test the actual spreadsheet software and keep an unmodified source record separately.

Common mistakes and diagnosis

Do not use a dramatic headline that outruns the evidence. Do not hide rejected rows or anonymous-customer exclusions in a footnote readers cannot find. Do not publish raw real customer data merely because a chart is public. A report's pretty formatting does not compensate for a wrong denominator. Avoid claiming a certificate, accreditation, or job guarantee that the course does not provide.

Practice assignment

Write a five-sentence memo: question, one measured finding, one limitation, one next investigation, and one reproducibility instruction. Add a check that no customer_id column appears in the summary table you plan to share.

Worked solution and explanation

Add the following code below the worked example, or run the matching file in the solutions folder. Compare the result with the business definition before trusting a passing assertion.

public_summary = channel.rename('net_paise').reset_index()
assert 'customer_id' not in public_summary.columns
memo = {'question': 'Compare net merchandise revenue by recorded channel.',
        'finding': statement,
        'limitation': 'Fictional, observational data with no advertising spend.',
        'next': 'Request cost and exposure data before evaluating incremental return.',
        'reproduce': 'python pipeline.py --out outputs/reviewed-report'}
assert len(memo) == 5

Check your understanding

What makes a recommendation useful when the data cannot support a causal conclusion?

Answer guide

It identifies the next decision-relevant evidence to collect, explains how that evidence would resolve uncertainty, and avoids pretending the current descriptive comparison proves an intervention's effect. A disciplined request for spend, coverage, identity, or randomized exposure data can be more actionable than an unsupported instruction to increase a channel budget.

Reference and next step

Download the complete course kit: datasets, all examples, solutions, and capstone.

Return to the full Data Analysis with Python and pandas curriculum.

Course index · Previous · Next