Writing Up a Finding
Overview
A number, what it is compared with, and what it does not prove.
What You Will Learn in This Lesson
By the end of this lesson, you will be able to:
- Write a claim in three parts: the number, the comparison, and the limit.
- Report the sample size next to every difference between groups.
- Tell an observed correlation apart from a causal claim.
- Build the sentence from the data, so it cannot drift from the numbers.
Why This Matters
The write-up is the only part of an analysis most people ever see. A correct table under an overstated sentence is still a wrong analysis, because the sentence is what gets repeated. Writing the claim from the data, with its size and its limits attached, is what makes the number something you can defend when somebody asks.
Step 1: Three parts to a claim
A finding that survives a reader has three parts. Leave out the second and it is trivia; leave out the third and it is overstated.
The number
Start with the value, at a precision the data supports. "Team a averages 85.0" is a number; it is not yet a finding, because nothing says whether 85 is good.
import pandas as pd
df = pd.DataFrame({"team": ["a", "a", "b"], "score": [90.0, 80.0, 70.0]})
means = df.groupby("team")["score"].mean()
print(means.round(1))
The comparison
A number means something next to another number: the other team, last term, the target. The gap is usually the thing the reader actually wants, so compute it rather than making them subtract.
import pandas as pd
df = pd.DataFrame({"team": ["a", "a", "b"], "score": [90.0, 80.0, 70.0]})
means = df.groupby("team")["score"].mean()
gap = means.max() - means.min()
print(f"{means.idxmax()} leads {means.idxmin()} by {gap:.1f} points")
The limit
Say how much data the claim rests on and what period it covers. This is not modesty; it is the information a reader needs to decide how far to trust it.
import pandas as pd
df = pd.DataFrame({"team": ["a", "a", "b"], "score": [90.0, 80.0, 70.0]})
sizes = df["team"].value_counts()
print(f"based on {len(df)} scores: a has {sizes['a']}, b has {sizes['b']}")
Mini Practice #1: Add the comparison
Try It YourselfThe code prints the top team's mean on its own. Change the print so it also says which team it beats and by how many points.
Step 2: Build the sentence from the data
A sentence typed by hand is a second copy of the result, and second copies drift. The next time the data changes the table updates and the sentence does not.
An f-string over the computed values
Every number in the sentence comes from a variable. If the data changes, the sentence changes with it, and nobody has to remember to edit the report.
import pandas as pd
def headline(df):
means = df.groupby("team")["score"].mean()
gap = float(means.max() - means.min())
return f"{means.idxmax()} scores {gap:.1f} above {means.idxmin()} (n={len(df)})"
print(headline(pd.DataFrame({"team": ["a", "b"], "score": [85.0, 70.0]})))
Which n is the right n
The number of rows and the number of groups are both counts, and they say very different things. "n=2" after a comparison of two teams reads as two data points. Report the rows the means were built from.
import pandas as pd
df = pd.DataFrame({"team": ["a", "b", "b", "b"], "score": [10.0, 4.0, 4.0, 4.0]})
means = df.groupby("team")["score"].mean()
print("groups:", len(means))
print("rows:", len(df))
Key idea: When one group is far smaller than the other, say so in the sentence. "a leads b (a: 1 score, b: 300)" is honest; "a leads b (n=301)" hides the problem inside a big number.
Mini Practice #2: The right n
Try It YourselfPrint the sentence twice: once with the number of groups as n, once with the number of rows. Read both aloud and decide which one a reader would misunderstand.
Step 3: Moving together is not causing
The most common overstatement in a write-up is turning "these two go up together" into "this one makes that one go up".
Measure the correlation
Series.corr gives a number between -1 and 1 for how closely two columns move together. Close to 1 means they rise together in this data. That is the whole of what it says.
import pandas as pd
hours = pd.Series([1.0, 2.0, 3.0, 4.0])
score = pd.Series([60.0, 68.0, 81.0, 88.0])
print(round(float(hours.corr(score)), 2))
Say what it does not show
Students who study more may also sleep more, attend more, or already be stronger. The table cannot separate those. The honest sentence reports the association and names that it is one.
import pandas as pd
hours = pd.Series([1.0, 2.0, 3.0, 4.0])
score = pd.Series([60.0, 68.0, 81.0, 88.0])
r = float(hours.corr(score))
print(f"hours and score move together (r={r:.2f}); this data cannot say whether one causes the other")
Step 4: When to Use It
Reporting a comparison
The number, what it is compared with, and how much data it rests on, built from the values.
f"{top} scores {gap:.1f} above {bottom} (n={len(df)})"
Describing an association
Say the columns move together, give r, and say what the data cannot show.
When Not to Use It
Do not write “causes”, “drives” or “leads to” about a correlation, and do not type numbers into a sentence by hand. Both drift from what the data says.
End-of-Lesson Exercises
Exercise 1: A finding with its n
Write finding(df) returning a sentence of the form "a scores 15.0 above b (n=3)", comparing the highest and lowest team means and reporting how many rows the whole comparison rests on.
Group to means, take idxmax and idxmin, subtract for the gap, and len(df) for n. Reporting n is what stops a difference between two rows reading like a result.
Exercise 2: An association, stated honestly
Write association(df, x, y) that returns "x and y move together (r=0.99)" when the correlation between the two columns is at least 0.5, "x and y move in opposite directions (r=-0.99)" when it is at most -0.5, and "no clear link between x and y (r=0.10)" otherwise. Use the column names given, and two decimal places for r.
r = float(df[x].corr(df[y])). Compare with 0.5 and -0.5 using >= and <=, and format r with :.2f inside each sentence. None of the three sentences says "causes".