Skip to content
academia.sh

Lesson 12 / 18

Building Narrative with Data

When one step is removed from the five-step chain that runs from finding to recommendation, the premises the reader is forced to complete on their own are counted: removing the finding leaves 4 inferences open, removing the comparison 3, removing the candidate cause 2, removing the uncertainty 3, removing the recommendation 3. Of the eleven wrong defaults the chain carries, nine strengthen the recommendation and two weaken it — the gaps are not filled at random. The place of the margin of variation of the Statistical Analysis and Exploratory Data Analysis course is settled: if the margin is never written, 4 inferences and 3 wrong defaults remain; if it is written only at the end in a limitations paragraph, 1 inference and 1 wrong default remain; when it is written next to the finding and in the uncertainty step, both go to zero.

Published Updated

In this lesson

  • Writes the narrative that runs from finding to recommendation as a five-step chain of premises and separates the premises each step carries
  • Counts the inference the reader is forced to make when a step is removed and measures how many of these inferences fall in the wrong direction
  • Settles at which step of the narrative the margin of variation must stand by comparing three placements
Contents

The previous three lessons fixed the number: the layout was split to support the decision, the definition of the metric was made ungameable, and the data cut-off and the refresh lag were written next to the number. What the reader holds is still a number. For a number to turn into a decision it has to sit in a chain, and if the links of the chain are missing, the reader completes them on their own.

The measure of this lesson is the gap that a skipped step leaves: the number of inferences the reader is forced to make when a link is removed. A second number is written next to it: how many of the defaults of these inferences are wrong with respect to the dataset, and how many of those wrong defaults strengthen the recommendation.

  • RP19. The dataset is inherited from the Data Collection and Preparation course and is fictional: a reading table of 3,199 rows, 1,329 subscribers, three periods, five regions. The seed is written into the code.
  • RP20. The finding comes from the Statistical Analysis and Exploratory Data Analysis course: the east mean is 24.674 m³ (557 readings), the north 20.210 m³ (921 readings), the gap 4.463 m³. The test computation is not repeated here; the numbers are read from there.
  • RP21. Both numbers of the margin are read from the Statistical Analysis and Exploratory Data Analysis course: when the region labels were reshuffled 2,000 times, the observed gap was not exceeded in any run; the spread of the four defensible decisions on mass, sample, zero readings and the number of periods is 0.206 m³.
  • RP22. The narrative chain is fictional: five steps, sixteen premises, fifteen of them needed to defend the recommendation.
  • RP23. When a step is removed, the reader is assumed to complete the premises of that step on their own. The default of each premise and the direction of the default are written in the lesson.
  • RP24. The recommendation is also fictional: a review of the tariff tier boundary in the east region.

The Chain

# narrative.py — MODEL: the fictional dataset from K03 is generated with the same seed; on top
# of it a five-step narrative chain that runs from finding to recommendation is built.
import math

SEED, RAW, M32 = 20260218, 1400, 0xFFFFFFFF
REGION = [("north", 0.28, 21), ("south", 0.22, 17), ("east", 0.18, 26),
          ("west", 0.14, 14), ("central", 0.18, 23)]


def rng(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def next_value():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return next_value


def pick(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


SUBSCRIBERS, DISCARDED = [], []
for i in range(RAW):
    r = rng(SEED + i)
    b = REGION[pick(r(), [x[1] for x in REGION])]
    pick(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    pick(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    (DISCARDED if r() < 0.046 else SUBSCRIBERS).append(
        {"subscriber_id": 10001 + i, "region": b[0], "base": b[2]})
VALID = {k["subscriber_id"] for k in SUBSCRIBERS}
READINGS = {}
for k in SUBSCRIBERS + DISCARDED:
    r = rng(SEED + 7000 + k["subscriber_id"])
    for d in range(pick(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(
            k["base"] * math.exp((r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100
            + 0.5) / 100
        if k["subscriber_id"] in VALID:
            READINGS.setdefault(k["subscriber_id"], {})[d + 1] = t
        r()

MEANS = {}
for b, _, _ in REGION:
    v = [t for k in SUBSCRIBERS if k["region"] == b
         for t in READINGS.get(k["subscriber_id"], {}).values()]
    MEANS[b] = (sum(v) / len(v), len(v))
GAP = MEANS["east"][0] - MEANS["north"][0]
WIDEST = max(MEANS.values())[0] - min(MEANS.values())[0]
ORDERED = sorted(MEANS, key=lambda b: -MEANS[b][0])

# Narrative chain: each step carries a set of premises. Premise fields: code, step,
# content, the default the reader falls to if the step is missing, whether the default is wrong
# for the dataset, whether it is needed to defend the recommendation, whether the default
# strengthens the recommendation (+1/-1).
PREMISES = [
    ("O1", "finding", f"east mean {MEANS['east'][0]:.3f} m3",
     "number unknown", False, True, 0),
    ("O2", "finding", f"north mean {MEANS['north'][0]:.3f} m3",
     "number unknown", False, True, 0),
    ("O3", "finding", f"numbers rest on {MEANS['east'][1]} and {MEANS['north'][1]} readings",
     "observation count is large", True, True, 1),
    ("O4", "finding", "the number in the finding was written with its margin",
     "number is exact", True, True, 1),
    ("O5", "comparison", f"gap {GAP:.3f} m3", "gap unknown", False, True, 0),
    ("O6", "comparison", f"gap is {GAP / MEANS['north'][0] * 100:.1f} percent of north",
     "gap is small", True, True, -1),
    ("O7", "comparison",
     f"widest gap of the five regions {WIDEST:.3f} m3 ({ORDERED[0]}-{ORDERED[-1]})",
     "gap is the only important gap", True, True, 1),
    ("O8", "candidate cause", "candidate: east base consumption is higher",
     "no cause exists", False, True, 0),
    ("O9", "candidate cause", "rival candidate: reading coverage gap",
     "a single cause exists", True, True, 1),
    ("O10", "candidate cause", "rival candidate: tariff tier distribution",
     "a single cause exists", True, False, 1),
    ("O11", "uncertainty", "K05: not exceeded in 2,000 reshuffles", "margin not measured", False, True, 0),
    ("O12", "uncertainty", "K05: spread of four decisions 0.206 m3", "margin is zero", True, True, 1),
    ("O13", "uncertainty", "what the margin does not cover was named",
     "margin covers everything", True, True, 1),
    ("O14", "recommendation", "recommended action was written", "action is the reader's choice", True, True, -1),
    ("O15", "recommendation", "reversibility of the action was written",
     "action is reversible", True, True, 1),
    ("O16", "recommendation", "which metric the action will be measured by", "no measurement will be done", True, True, 1),
]
STEPS = ["finding", "comparison", "candidate cause", "uncertainty", "recommendation"]
print(f"fictional dataset (seed {SEED}): {len(SUBSCRIBERS)} subscribers, "
      f"{sum(len(v) for v in READINGS.values())} readings")
print(f"finding: east {MEANS['east'][0]:.3f} m3 ({MEANS['east'][1]} readings), "
      f"north {MEANS['north'][0]:.3f} m3 ({MEANS['north'][1]} readings), gap {GAP:.3f} m3")
print(f"chain: {len(STEPS)} steps, {len(PREMISES)} premises, "
      f"{sum(1 for o in PREMISES if o[5])} of them needed to defend the recommendation")
for a in STEPS:
    print(f"  {a:<15} " + ", ".join(o[0] for o in PREMISES if o[1] == a))
fictional dataset (seed 20260218): 1329 subscribers, 3199 readings
finding: east 24.674 m3 (557 readings), north 20.210 m3 (921 readings), gap 4.463 m3
chain: 5 steps, 16 premises, 15 of them needed to defend the recommendation
  finding         O1, O2, O3, O4
  comparison      O5, O6, O7
  candidate cause O8, O9, O10
  uncertainty     O11, O12, O13
  recommendation  O14, O15, O16

Here the narrative is not a style but a set of premises. If the recommendation is to be defensible, the premises standing behind it must have been given to the reader; every premise that is not given is produced by the reader. What the model does is count this production.

The name of the third step is also deliberately not “cause” but candidate cause. The report writes that the base consumption of the east region is higher as a candidate, puts the two rival candidates beside it, and does not decide among them; there is no mechanism in the dataset that would make this decision. Writing the candidates is not an evasion; it is the only way to keep the reader from falling onto a single candidate.

The Skipped Step

# One step is removed from the chain and the premises the reader is forced to complete on
# their own are counted as needed. Wrong default: a completion that is not true for the dataset.
def inference_gap(removed=None, dropped=frozenset()):
    missing = [o for o in PREMISES if o[5] and (o[1] == removed or o[0] in dropped)]
    wrong = [o for o in missing if o[4]]
    return missing, wrong, sum(1 for o in wrong if o[6] > 0)


print(f"{'removed step':<22}{'reader inference':>20}{'wrong default':>19}"
      f"{'strengthening':>21}")
for a in [None] + STEPS:
    e, y, g = inference_gap(a)
    print(f"{a or 'none (full chain)':<22}{len(e):>20}{len(y):>19}{g:>21}")
print(f"\nall wrong defaults in the whole chain and their directions")
for o in PREMISES:
    if o[4]:
        print(f"  {o[0]:<4} {o[1]:<16} the reader falls to this default: {o[3]:<30}"
              f"{'strengthens the recommendation' if o[6] > 0 else 'weakens the recommendation'}")
every_wrong = [o for o in PREMISES if o[4]]
print(f"of {len(every_wrong)} wrong defaults in total, {sum(1 for o in every_wrong if o[6] > 0)} "
      f"strengthen the recommendation, {sum(1 for o in every_wrong if o[6] < 0)} weaken it")
removed step              reader inference      wrong default        strengthening
none (full chain)                        0                  0                    0
finding                                  4                  2                    2
comparison                               3                  2                    1
candidate cause                          2                  1                    1
uncertainty                              3                  2                    2
recommendation                           3                  3                    2

all wrong defaults in the whole chain and their directions
  O3   finding          the reader falls to this default: observation count is large    strengthens the recommendation
  O4   finding          the reader falls to this default: number is exact               strengthens the recommendation
  O6   comparison       the reader falls to this default: gap is small                  weakens the recommendation
  O7   comparison       the reader falls to this default: gap is the only important gap strengthens the recommendation
  O9   candidate cause  the reader falls to this default: a single cause exists         strengthens the recommendation
  O10  candidate cause  the reader falls to this default: a single cause exists         strengthens the recommendation
  O12  uncertainty      the reader falls to this default: margin is zero                strengthens the recommendation
  O13  uncertainty      the reader falls to this default: margin covers everything      strengthens the recommendation
  O14  recommendation   the reader falls to this default: action is the reader's choice weakens the recommendation
  O15  recommendation   the reader falls to this default: action is reversible          strengthens the recommendation
  O16  recommendation   the reader falls to this default: no measurement will be done   strengthens the recommendation
of 11 wrong defaults in total, 9 strengthen the recommendation, 2 weaken it

The first row of the table confirms the definition of the chain: in the full chain no inference is left to the reader, because all fifteen of the fifteen premises needed to defend the recommendation have been written. This row is not a measure of success but the measuring instrument’s own zero point; the remaining five rows are read against it.

None of the five steps is free. When the finding is removed, four inferences are left to the reader; when the uncertainty or the recommendation is removed, three each; when the comparison is removed, three; when the candidate cause is removed, two. The numbers are close to each other and, read alone, do not say which step is more expensive to skip.

The second and the third columns make the distinction. Of the eleven wrong defaults, nine strengthen the recommendation and only two weaken it. The gaps are not filled at random: when the reader cannot see a margin next to a number, the reader counts the margin as zero; when the reader cannot see a rival candidate cause, the reader assumes there is a single cause; when the number of observations is not written, the reader accepts that the number comes from a large dataset. All three make the recommendation look sturdier than it is.

The result is a methodological rule. Skipping a narrative step makes the recommendation easier to defend, and this ease is independent of the author’s intent. The two weakening defaults do not break this rule; on the contrary, they explain it: when the reader does not see the size of the gap, the reader counts it as small, and when the recommended action is not written, the reader chooses the action. These two are not skipped, because they do not serve the author; the steps that are skipped are the steps that serve once they are skipped.

The Place of the Margin

At this point the place of the margin of variation of the Statistical Analysis and Exploratory Data Analysis course in the chain can be settled. Three placements are tried.

# The place of the margin in the narrative is tried with three placements. The margin was
# measured in K05 and is not recomputed here; what is asked is at which step of the chain it stands.
PLACEMENT = [("margin not written at all", {"O4", "O11", "O12", "O13"}),
             ("margin only at the end, in a limitations paragraph", {"O4"}),
             ("margin next to the finding and in the uncertainty step", set())]
print(f"{'placement of the margin':<58}{'inference':>9}{'wrong':>8}{'strengthening':>15}")
for name, dropped in PLACEMENT:
    e, y, g = inference_gap(dropped=dropped)
    print(f"{name:<58}{len(e):>9}{len(y):>8}{g:>15}")

print("\n--- chain of the third placement ---------------------------------")
for a in STEPS:
    print(f"  [{a}]")
    for o in PREMISES:
        if o[1] == a:
            print(f"     {o[0]:<4} {o[2]}{'' if o[5] else '   (not needed)'}")
placement of the margin                                   inference   wrong  strengthening
margin not written at all                                         4       3              3
margin only at the end, in a limitations paragraph                1       1              1
margin next to the finding and in the uncertainty step            0       0              0

--- chain of the third placement ---------------------------------
  [finding]
     O1   east mean 24.674 m3
     O2   north mean 20.210 m3
     O3   numbers rest on 557 and 921 readings
     O4   the number in the finding was written with its margin
  [comparison]
     O5   gap 4.463 m3
     O6   gap is 22.1 percent of north
     O7   widest gap of the five regions 10.896 m3 (east-west)
  [candidate cause]
     O8   candidate: east base consumption is higher
     O9   rival candidate: reading coverage gap
     O10  rival candidate: tariff tier distribution   (not needed)
  [uncertainty]
     O11  K05: not exceeded in 2,000 reshuffles
     O12  K05: spread of four decisions 0.206 m3
     O13  what the margin does not cover was named
  [recommendation]
     O14  recommended action was written
     O15  reversibility of the action was written
     O16  which metric the action will be measured by

The second placement is the common one, and it leaves one inference open. The margin is written, but it is written at the end: while reading the finding the reader saw no interval next to the number and had already formed the “the number is exact” default. The limitations paragraph that comes at the end does not take this default back; it only clears the author’s record. The margin has not one place but two places, and the first is next to the number in the finding.

The second is the uncertainty step, and there the margin says two different things. The gap that was not exceeded in the 2,000 reshuffles of the Statistical Analysis and Exploratory Data Analysis course closes a single question: would this gap come out if the region labels were distributed at random. The 0.206 m³ spread of the four defensible decisions is an entirely different uncertainty, and the reshuffling margin does not measure it at all. This is why a third premise stands in the chain: what the margin does not cover is named. When it is not named, the default the reader falls to is “the margin covers everything”, and this default too is among those that strengthen the recommendation.

The last three premises of the chain tie the recommendation to an action: the action is written, whether it is reversible is written, the metric by which it will be measured is written. The last one ties the previous two lessons to this point. If the metric is not written, the recommendation is never tested; if the metric is written, its definition has to be tight, otherwise the number that evaluates the recommendation can be moved without moving what it measures.

Summary

  • The narrative is not a style but a five-step chain of premises: finding, comparison, candidate cause, uncertainty, recommendation. The chain carries sixteen premises and fifteen of them are needed to defend the recommendation.
  • When a step is removed, the number of inferences left to the reader is between 2 and 4; this number alone does not say which step is more expensive.
  • Of the chain’s eleven wrong defaults, nine strengthen the recommendation and two weaken it; skipping a step makes the recommendation easier to defend, and this ease is independent of the author’s intent.
  • The margin of variation has not one place but two: next to the number in the finding and in the uncertainty step. A separate limitations paragraph at the end leaves 1 inference open, and that inference is the “the number is exact” default.
  • The uncertainty step also names what the margin does not cover: the 2,000-reshuffle margin of the Statistical Analysis and Exploratory Data Analysis course closes only the question of random distribution of the labels, and does not measure the 0.206 m³ spread of the four decisions.
  • The third step writes not the cause but the candidate causes; the last three premises of the chain tie the recommendation to an action, to the reversibility of the action and to the metric that will test it.

Next Step

The report now stands on a chain: the numbers are in a layout that can be read back, the definitions of the metrics are tight, the cut-off of the source is written, the links of the narrative are complete. All of this concerns producing a single report once. Whether the same report can be produced again three months later, by another person, so that it gives the same numbers, was never asked. The next lesson counts this: how many items have to be fixed to produce the same report a second time, how much the number moves when one is missing, and how many items the report adds on top of the items counted by the Data Collection and Preparation course and the Data Analysis with Python course.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close