| class | sex | age | survived | n |
|---|---|---|---|---|
| 1st | Male | Child | No | 0 |
| 2nd | Male | Child | No | 0 |
| 3rd | Male | Child | No | 35 |
| Crew | Male | Child | No | 0 |
| 1st | Female | Child | No | 0 |
30 Flow
Where did each part go? Some tables record movement: each row says that some amount started in one place and finished in another. A flow diagram draws that movement. The places are sorted into stages that stand side by side, and bands carry each amount from stage to stage. The wider the band, the larger the amount it carries. The world calls this picture a Sankey diagram, or an alluvial diagram, and as with the pie there is no atom that spells either name.
A stage is one of the table’s categorical columns. Its distinct values are the possible places at that step, and each row sits in one of them. The flow transform computes the whole diagram from one table of such columns. This chapter builds the diagram one layer at a time, using the passengers and crew of the Titanic. titanic holds one row per group of people. The columns are class, sex, age (child or adult) and survived. The last column, n, is how many people the group held. The table has one row for every combination of those four columns, so some rows count zero people:
30.1 One row is one path
The first question of the Titanic table is who survived, and whether class changed the chances. That is a movement from a class to an outcome, which is a flow with two stages. Name the stages in the atom, left to right, and bind the count to y:
data(titanic) + ribbon * flow(class, survived) + y(n) +
title("Who survived, by class")(data(titanic) + ribbon * flow(col["class"], col.survived) + y(col.n) +
title("Who survived, by class"))data(titanic) + ribbon * flow(:class, :survived) + y(:n) +
title("Who survived, by class")plot(data(titanic), layer(ribbon, flow(col.class, col.survived)),
y(col.n), title("Who survived, by class"))“Given titanic: ribbons derived by flow through class and survived, y is n.”
Each band is one path through the stages. This plot draws no names, so find a path by its position: the lowest band leaves the 1st slot and arrives at the Yes slot. It carries the 203 first-class passengers who survived, and it holds that thickness at both ends. Rows that share a path are added together, so the four 1st rows that end in Yes, one per sex and age, become one band. The same addition combines rows that differ only in the columns the atom does not name, sex and age here.
The stages stand on the horizontal axis, named by their columns. x() has nothing left to set: the atom’s argument order is the order the stages are drawn in. The vertical axis is the count, with the same ticks any other plot would get. The bands at each stage stack from zero with no gaps, so the top edge of any band gives the total of that band and every band under it.
30.2 Adding a stage
The next question is whether women survived more often than men, which the two-stage diagram could not answer. Add a stage by adding a column. Each path now crosses three stages, and each band still carries one path’s count at both of its ends:
data(titanic) + ribbon * flow(class, sex, survived) + y(n) +
title("Class, then sex, then survival")(data(titanic) + ribbon * flow(col["class"], col.sex, col.survived) + y(col.n) +
title("Class, then sex, then survival"))data(titanic) + ribbon * flow(:class, :sex, :survived) + y(:n) +
title("Class, then sex, then survival")plot(data(titanic), layer(ribbon, flow(col.class, col.sex, col.survived)),
y(col.n), title("Class, then sex, then survival"))“Given titanic: ribbons derived by flow through class, sex and survived, y is n.”
The three-stage picture answers a question the two-stage one could not: how many female passengers in first class survived? Follow any band and you follow one group of people. You see their class, whether they were male or female, and whether they survived.
30.3 Color comes from a stage
With three stages the bands cross one another, and a path is hard to follow through a crossing unless something marks it. A band runs through every stage, so any stage can color it. Write color() after the ribbon layer and name the stage whose value gives each band its color:
data(titanic) + ribbon * flow(class, sex, survived) + y(n) + color(class) +
title("The same paths, colored by class")(data(titanic) + ribbon * flow(col["class"], col.sex, col.survived) + y(col.n) + color(col["class"]) +
title("The same paths, colored by class"))data(titanic) + ribbon * flow(:class, :sex, :survived) + y(:n) +
color(:class) + title("The same paths, colored by class")plot(data(titanic), layer(ribbon, flow(col.class, col.sex, col.survived)),
y(col.n), color(col.class), title("The same paths, colored by class"))“Given titanic: ribbons derived by flow through class, sex and survived, y is n, color by class.”
Coloring by class gives each band the color of the slot it starts from. You can then see which classes meet in each later slot. Coloring by survived would instead color every band by how its path ends. Only a stage can color a band, because a band is a whole path, and only a stage holds a value every path carries.
30.4 The slots and their names
So far you have found a path by its position, because no slot carries a name. Two more layers finish the diagram. A zone layer draws each stage’s slots, the stacked blocks the bands leave and arrive at. A text layer names them. The three layers read one computation, so they always agree about where everything sits:
data(titanic) + y(n) +
ribbon * flow(class, sex, survived) + color(class) +
zone * flow(class, sex, survived) + style(color = "gray") +
text * flow(class, sex, survived) + label(name) +
title("The full diagram: bands, slots, names")(data(titanic) + y(col.n) +
ribbon * flow(col["class"], col.sex, col.survived) + color(col["class"]) +
zone * flow(col["class"], col.sex, col.survived) + style(color = "gray") +
text * flow(col["class"], col.sex, col.survived) + label(col.name) +
title("The full diagram: bands, slots, names"))data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
color(:class) + zone * flow(:class, :sex, :survived) +
style(color = "gray") + text * flow(:class, :sex, :survived) +
label(:name) + title("The full diagram: bands, slots, names")plot(data(titanic), y(col.n),
layer(ribbon, flow(col.class, col.sex, col.survived)), color(col.class),
layer(zone, flow(col.class, col.sex, col.survived)),
style({ color: "gray" }),
layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
title("The full diagram: bands, slots, names"))“Given titanic: y is n, ribbons derived by flow through class, sex and survived, color by class, and also zones derived by flow through class, sex and survived, and text derived by flow through class, sex and survived, label by name.”
The slots take their color from style(), gray here so that only the bands carry color. The names come from label(name): the transform writes each slot’s name into a column called name, exactly as partition does.
The count on y is optional. Bind nothing and every row counts as 1, so the diagram counts rows instead of summing a column. partition uses that default too, and count tallies rows the same way.
30.5 What flow refuses
A flow needs at least two stages, because movement needs a place to start and a place to finish:
data(titanic) + y(n) + ribbon * flow(class)data(titanic) + y(col.n) + ribbon * flow(col["class"])data(titanic) + y(:n) + ribbon * flow(:class)plot(data(titanic), y(col.n), layer(ribbon, flow(col.class)))Error:
! gog: `flow()` needs at least two stage columns, in reading order — `flow(class, sex, survived)` runs each row from its `class` to its `survived`. One column has no between.
The stage axis is drawn from the atom’s own columns, so a bound x has nothing left to set:
data(titanic) + x(n) + ribbon * flow(class, survived)data(titanic) + x(col.n) + ribbon * flow(col["class"], col.survived)data(titanic) + x(:n) + ribbon * flow(:class, :survived)plot(data(titanic), x(col.n),
layer(ribbon, flow(col.class, col.survived)))Error:
! gog: under `flow` the stage axis is drawn from the atom's own columns, so `x(...)` has nothing left to say. Remove it — to reorder the stages, reorder `flow(...)`'s arguments.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Only three marks can read what flow computes, and the refusal lists all three when another mark is used:
data(titanic) + y(n) + bar * flow(class, survived)data(titanic) + y(col.n) + bar * flow(col["class"], col.survived)data(titanic) + y(:n) + bar * flow(:class, :survived)plot(data(titanic), y(col.n), layer(bar, flow(col.class, col.survived)))Error:
! gog: `flow` lays a path's magnitude through its stages — an interval at each stage, and a band between adjacent ones — and `bar` has no reading for that. Three marks do: `ribbon * flow(<a>, <b>)` draws the bands, `zone * flow(<a>, <b>)` draws each stage's slots, and `text * flow(<a>, <b>) + label(name)` names them. Bind `y(<amount>)` to weigh the paths, or bind nothing and each row counts 1.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
And the column that colors a band must be one of the stages. A column outside the stages holds no value a whole path shares:
data(titanic) + y(n) + ribbon * flow(class, survived) + color(n)data(titanic) + y(col.n) + ribbon * flow(col["class"], col.survived) + color(col.n)data(titanic) + y(:n) + ribbon * flow(:class, :survived) + color(:n)plot(data(titanic), y(col.n),
layer(ribbon, flow(col.class, col.survived)), color(col.n))Error:
! gog: `color(n)` under `flow` must name one of the atom's stages — a band is a path through them, and only a stage holds a value every band carries. Name one of `class`, `survived`, or set the paint for the whole layer with `style()`.
gog: `color(n)` maps a continuous (numeric) column, but `color` on `ribbon` needs a categorical (text) column.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
30.6 Where the limits are
Every mark that draws on the page also bends into a circle, so the flow is a natural thing to try there. Bending a flow into polar() is valid grammar. The bands bent around a circle are the chord diagram, which this engine does not draw:
data(titanic) + y(n) + ribbon * flow(class, survived) + polar()data(titanic) + y(col.n) + ribbon * flow(col["class"], col.survived) + polar()data(titanic) + y(:n) + ribbon * flow(:class, :survived) + polar()plot(data(titanic), y(col.n),
layer(ribbon, flow(col.class, col.survived)), polar())Error:
! gog: `flow` in `polar()` is valid grammar — the bands bent round a rim are the chord diagram — but this engine does not draw it yet. Draw the flow flat, or wait for the feature.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The input has a limit too. A flow reads stage columns from one table, one row per path. Some tools accept a different shape: a list of nodes, which are these slots, and a list of links between them. That shape can also describe flows that merge, skip a stage, or return to a stage they already visited. gog does not read it. The stage-column shape settles three things by itself. Every path passes through every stage, the totals match at each one, and no path can return to a stage it already visited.