Skip to content
academia.sh

Lesson 01 / 18

Chart Type Selection

The same five regional means are passed through five encoding channels and read back, and the mean read-back error comes out as follows: position 0.070 m³, length 0.183 m³, angle 0.543 m³, color step 0.931 m³, area 2.468 m³. The 4.463 m³ difference between east and north is read back as 2.500 m³ from the area channel and 5.426 m³ from the angle channel, and not a single reading changes. The color step separates four of the five regions, with east and central falling on the same step. Each of the four question types asks for something different to be read back, and the channel pairing follows from that demand.

Published Updated

In this lesson

  • Builds a display from an encoder and decoder function pair and writes the reading resolution as a number
  • Encodes the same value through five encoding channels, reads it back, and sets the five read-back errors side by side
  • Determines which channel a question type requires by checking whether what the question asks for is read back within the decision threshold
Contents

Across thirteen lessons the previous course wrote the margin of variation next to every number, and it closed with this question: all these numbers sit on the analyst’s screen as output blocks. The person who will make the decision saw neither the margin nor the number. When a number leaves the screen and goes to a decision maker, it rides on a display, and what happens there was never asked.

This course opens with that question and changes the measure. In the previous five courses the number varied in the analyst’s hands; here the number varies on its way to the reader. A display is an encoding channel: the number is turned into a length, a position, an angle, an area, or a color step, and the reader turns it back. No picture is produced here; each channel is built with two functions, and both run. The number of the lesson is what the same five regional means look like when read back after passing through five channels.

  • CD1. A display is an encoding channel. A channel is defined by two functions: the encoder turns the number into the channel, and the decoder reads the number back from the channel. Both sit in the code and both run.
  • CD2. Reading resolution is a number. The reader does not read a channel with infinite precision; each channel has a number of steps the reader can tell apart. These numbers are a model assumption written under the output; what is measured is not the number of steps but the difference that comes out of it.
  • CD3. The measure is the difference. The difference between decoder(encoder(x)) and x is the read-back error. The number in the source never changes; what varies is the number that reaches the reader.
  • CD4. The decoder of the area channel does not read area. The reader reads the linear measure that carries the shape and reports the value in proportion to it. This is a deviation independent of resolution, produced by the channel itself.
  • CD5. Color looks continuous but is stepped. The value falls into one of a limited number of steps of a legend scale and is read back as the midpoint of that step. Here color is not a name but a step number.
  • CD6. A channel’s success depends on the question. The same channel can have the absolute value read back badly and the share well. A channel cannot be chosen without writing which question is asked.
  • CD7. The decision threshold is written. A read-back error is large or small only when it is compared with a threshold, and the threshold comes from the decision the report will drive.

Channel, Encoder and Decoder

# channel.py — MODEL: the measurement network of K03-K05 (a fictional setup) is
# regenerated with the same seed (a 3,199-row long-format readings table, 1,329
# subscribers, three periods, five regions). No chart is drawn: each display
# consists of one encoder and one decoder.
import math

SEED, RAW, M32 = 20260218, 1400, 0xFFFFFFFF
PERIODS = ["2026-01", "2026-02", "2026-03"]
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


_kept, _dropped = [], []
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])
    (_dropped if r() < 0.046 else _kept).append(
        {"subscriber_id": 10001 + i, "region": b[0], "base": b[2]})
VALID = {a["subscriber_id"] for a in _kept}

READINGS = []
for a in _kept + _dropped:
    r = rng(SEED + 7000 + a["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(
            a["base"] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100
        if a["subscriber_id"] not in VALID:
            r()
            continue
        READINGS.append({"subscriber_id": a["subscriber_id"],
                         "region": a["region"], "period": PERIODS[d],
                         "consumption_m3": t})
        r()

NAMES = [b[0] for b in REGION]
MEAN, OBS = {}, {}
for name in NAMES:
    v = [o["consumption_m3"] for o in READINGS if o["region"] == name]
    MEAN[name], OBS[name] = sum(v) / len(v), len(v)
TOTAL = sum(MEAN.values())
LOW, HIGH = 0.0, 25.0

# Reading resolution: the number of steps the reader can tell apart in that
# channel. The numbers are an assumption of the model; what is measured is the
# difference that comes out of this assumption.
RES = {"position": 100, "length": 40, "angle": 36, "area": 20, "color": 7}


def snap(t, n):
    return round(t * n) / n


def encode_position(v):
    return (v - LOW) / (HIGH - LOW)


def decode_position(p):
    return LOW + (HIGH - LOW) * snap(p, RES["position"])


def encode_length(v):
    return (v - LOW) / (HIGH - LOW)


def decode_length(u):
    return LOW + (HIGH - LOW) * snap(u, RES["length"])


def encode_angle(v):
    return 360.0 * v / TOTAL


def decode_angle(a):
    return TOTAL * snap(a / 360.0, RES["angle"])


def encode_area(v):
    return (v - LOW) / (HIGH - LOW)


def decode_area(ar):
    # The reader reads not the area but the linear measure that carries the
    # shape: the radius.
    return LOW + (HIGH - LOW) * snap(math.sqrt(ar), RES["area"])


def encode_color(v):
    return (v - LOW) / (HIGH - LOW)


def decode_color(t):
    n = RES["color"]
    step = min(n - 1, int(t * n))
    return LOW + (HIGH - LOW) * (step + 0.5) / n


CHANNELS = [("position", encode_position, decode_position),
            ("length", encode_length, decode_length),
            ("angle", encode_angle, decode_angle),
            ("area", encode_area, decode_area),
            ("color", encode_color, decode_color)]

print(f"fictional dataset: {len(READINGS)} readings, {len(VALID)} subscribers, "
      f"{len(NAMES)} regions")
print("region means: " + ", ".join(f"{n} {MEAN[n]:.3f}" for n in NAMES) + " m3")
print(f"total {TOTAL:.3f} m3, value axis {LOW:.0f}-{HIGH:.0f} m3")
print("reading resolution (steps): " + ", ".join(f"{k} {v}" for k, v in RES.items()))
fictional dataset: 3199 readings, 1329 subscribers, 5 regions
region means: north 20.210, south 16.602, east 24.674, west 13.778, central 22.396 m3
total 97.659 m3, value axis 0-25 m3
reading resolution (steps): position 100, length 40, angle 36, area 20, color 7

The dataset is fictional and is the same regional measurement network the previous three courses used: the same seed, the same 3,199 rows, the same 1,329 subscribers. The region means are the same too; east’s 24.674 m³ and north’s 20.210 m³ were computed earlier and are taken here as input.

The five resolutions were written for these reasons. Position is read on a shared axis and a scale stands next to the reader; 100 steps along the axis were taken as distinguishable. Length is measured from its own baseline, and the comparison is made against the neighboring bar, not the scale; 40 steps were written. Angle is read against a right angle and its subdivisions; a precision of one ninth of it was assumed and the circle was divided into 36 steps. For area there are 20 steps, but the real problem is elsewhere: the decoder reads not the area but the linear measure that carries it. Color is read from a legend scale, and 7 steps were taken as distinguishable on the scale.

These five numbers are debatable and vary from reader to reader. The claim of the lesson is not the numbers themselves but that the ordering comes out of them: when the same value passes through these five channels, the numbers read back separate, and the direction of the separation stays the same whatever the resolution choice.

The Same Number Passes Through Five Channels

# readback.py — the same five numbers pass through five channels and are read
# back. The measure is the difference between decoder(encoder(x)) and x.
G = 40


def bar(v):
    return "#" * round(G * (v - LOW) / (HIGH - LOW))


print("length channel, 40 steps from the baseline (what the reader sees):")
for name in NAMES:
    print(f"{name:8s}{MEAN[name]:7.3f} |{bar(MEAN[name]):<40s}| "
          f"read back {decode_length(encode_length(MEAN[name])):6.3f}")

print(f"\n{'region':8s}{'count':>7s}{'source':>9s}"
      + "".join(f"{ch[0]:>10s}" for ch in CHANNELS))
err = {ch[0]: [] for ch in CHANNELS}
for name in NAMES:
    x = MEAN[name]
    line = f"{name:8s}{OBS[name]:7d}{x:9.3f}"
    for cname, enc, dec in CHANNELS:
        g = dec(enc(x))
        err[cname].append(abs(g - x))
        line += f"{g:10.3f}"
    print(line)
print(f"{'|error|':8s}{sum(OBS.values()):7d}{'':9s}"
      + "".join(f"{sum(err[ch[0]]) / len(NAMES):10.3f}" for ch in CHANNELS))
length channel, 40 steps from the baseline (what the reader sees):
north    20.210 |################################        | read back 20.000
south    16.602 |###########################             | read back 16.875
east     24.674 |####################################### | read back 24.375
west     13.778 |######################                  | read back 13.750
central  22.396 |####################################    | read back 22.500

region    count   source  position    length     angle      area     color
north       921   20.210    20.250    20.000    18.989    22.500    19.643
south       645   16.602    16.500    16.875    16.277    20.000    16.071
east        557   24.674    24.750    24.375    24.415    25.000    23.214
west        476   13.778    13.750    13.750    13.564    18.750    12.500
central     600   22.396    22.500    22.500    21.702    23.750    23.214
|error|    3199              0.070     0.183     0.543     2.468     0.931

The bars are the lesson’s whole display apparatus: the reader sees an end point on a line of forty steps and derives a number from that end. North’s 20.210 m³ fills thirty-two of the forty steps, and thirty-two steps read back as 20.000 m³; the 0.210 m³ in between is lost because there is no place between the thirty-second and thirty-third steps.

The mean read-back error of the five channels ranges from 0.070 to 2.468 m³; the best and worst channels differ by a factor of thirty-five. The order is this: position, length, angle, color step, area. The difference between the first two comes from resolution alone. Everything after angle says something else.

The area channel deserves its own look, because raising the resolution cannot reduce its error. West’s 13.778 m³ is read back as 18.750, that is, it rises by 4.972 m³; east’s 24.674 m³ stays almost where it is. The deviation is in the same direction for every value, and it looks large because the values are small. The cause is CD4: area grows in proportion to the value, whereas the reader reads not the area but the linear measure that carries it. This is not a misuse of the channel; it is the channel itself.

The color step gets stuck in a different way. East is read as 23.214 and central is also read as 23.214 — the two regions fell on the same step, and for the reader they are one number, not two. On a seven-step scale the 25 m³ axis is divided into slices of 3.571 m³, and every difference narrower than this slice disappears.

The previous course’s headline number is tested here. The difference between east and north was 4.463 m³ and had been reported together with its margin of variation. The same difference is read back as 2.500 m³ from the area channel and as 5.426 m³ from the angle channel. Not a single reading in the source has changed; only the channel the number rode did. The summary-and-cleanup/04 lesson of the Analysis with Spreadsheets course had measured a variation of the same kind with axis truncation, and what was measured there was the legitimacy of the display: whether this chart is acceptable at this scale. The measure here is different and works even when the display is entirely legitimate — a pie chart, a bubble chart, and a heatmap are all within the rules, and they still move the number.

The Question Type Selects the Channel

# question.py — four question types, four separate read-backs. A channel's
# fitness for a question is whether what the question asks for is read back
# within the decision threshold.
THRESHOLD_DIFF, THRESHOLD_SHARE = 0.5, 1.0
diff = MEAN["east"] - MEAN["north"]
share = {name: MEAN[name] / TOTAL for name in NAMES}
print(f"source difference east-north {diff:.3f} m3; decision threshold "
      f"{THRESHOLD_DIFF} m3, share threshold {THRESHOLD_SHARE} points")
print(f"{'channel':9s}{'read-back difference':>22s}{'error':>8s}{'distinct':>10s}"
      f"{'largest share error':>21s}")
result = {}
for cname, enc, dec in CHANNELS:
    g = {name: dec(enc(MEAN[name])) for name in NAMES}
    gd = g["east"] - g["north"]
    t = sum(g.values())
    gs = max(abs(100 * g[name] / t - 100 * share[name]) for name in NAMES)
    distinct = len({round(g[name], 6) for name in NAMES})
    result[cname] = (abs(gd - diff) <= THRESHOLD_DIFF,
                     distinct == len(NAMES), gs <= THRESHOLD_SHARE)
    print(f"{cname:9s}{gd:22.3f}{abs(gd - diff):8.3f}{distinct:8d}/5{gs:21.2f}")

QUESTIONS = [("comparison", "difference of two values"),
             ("distribution", "order of five values"),
             ("composition", "share of each value")]
print(f"\n{'question':16s}{'must be read back':24s}"
      + "".join(f"{ch[0]:>9s}" for ch in CHANNELS))
for j, (name, what) in enumerate(QUESTIONS):
    print(f"{name:16s}{what:24s}"
          + "".join(f"{'yes' if result[ch[0]][j] else 'no':>9s}" for ch in CHANNELS))
print(f"{'relationship':16s}{'two values at once':24s}"
      + "".join(f"{'yes' if ch[0] == 'position' else 'no':>9s}" for ch in CHANNELS))
source difference east-north 4.463 m3; decision threshold 0.5 m3, share threshold 1.0 points
channel    read-back difference   error  distinct  largest share error
position                  4.500   0.037       5/5                 0.12
length                    4.375   0.088       5/5                 0.31
angle                     5.426   0.962       5/5                 0.69
area                      2.500   1.963       5/5                 2.94
color                     3.571   0.892       4/5                 1.60

question        must be read back        position   length    angle     area    color
comparison      difference of two values      yes      yes       no       no       no
distribution    order of five values          yes      yes      yes      yes       no
composition     share of each value           yes      yes      yes       no       no
relationship    two values at once            yes       no       no       no       no

Two thresholds were written and both come from the report. 0.5 m³ comes from the fact that the region difference is written into the report with two decimals and that the decision on which region the field team will visit is made on a difference of one m³. 1.0 point comes from the share being written in the report as a whole-number percentage: a one-point error changes the written digit. Without a written threshold, no read-back error is large or small.

The first row of the table is the expected one: the comparison question is carried only by position and length. The second row passes most of the channels, because preserving the order is the lightest job that can be asked of a channel; only the color step fails, for exactly the reason seen above: two regions fell on the same step.

The third row is unexpected. The angle channel passes the share but not the difference. The cause is in the encoder: angle already encodes the share, not the absolute value. The resolution of angle is spent on the share, and while the share error stays at 0.69 points, the absolute difference error climbs to 0.962 m³. The reverse also holds: of the four questions, the area channel passes only the order, because the CD4 deviation pushes every value in the same direction and the order is preserved, but it breaks both the difference and the share, and breaks the share most, by 2.94 points.

The fourth row is not measured, it is counted. The relationship question asks for two values to be read back at the same time, and for this a second independent copy of the channel is needed. Only position has two copies, the horizontal and the vertical axis; length, angle, area, and color have no second copy. So the relationship question is not a channel preference but a channel requirement.

The rule that comes out of this is the concrete form of CD6: the channel is chosen from the question, not from the data. The same five numbers answer “which is larger” from four channels, “how much larger” from two, and “what part of the total” from three.

Summary

  • A display is modeled with two functions: the encoder turns the number into the channel, the decoder reads it back; the measure is the read-back error between their composition and the source number.
  • Reading resolution is written as a number — position 100, length 40, angle 36, area 20, color 7 steps — and these are a model assumption; what is measured is the difference they produce.
  • The mean read-back error of the same five regional means comes out as 0.070 for position, 0.183 for length, 0.543 for angle, 0.931 for the color step, and 2.468 m³ for area.
  • The error of area cannot be reduced by resolution, because the decoder reads not the area but the linear measure; the color step separates only four of the five regions, and east and central fall on the same step.
  • The 4.463 m³ difference between east and north is read back as 2.500 m³ from area and 5.426 m³ from angle, and not a single reading in the source changes.
  • The channel is chosen from the question, not from the data: angle passes the share but not the difference, while the relationship question asks for a second independent copy of the channel, and that copy exists only in position.

Next Step

In this lesson every region was reduced to a single number, and the channel carried it. Some of the questions in the report, however, are not answered by a single number: how the 921 readings of north are laid out, whether there are two peaks or one, where the extreme values stand. The next lesson applies the same measure to a distribution and compares three displays — bin counts, the box summary, and the density display. What is read back is no longer a number but a distributional feature, and the measure is which display reads back the second peak. That the bin width is a decision, and that this decision moves the number of peaks read back, is shown with numbers.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close