What kind of thing does a column hold, and can you choose by that alone? It is the other question you can ask about a column without looking at a single row. kind is one of "number", "text", "truth" or "date". A timestamp column answers as a date, because a timestamp is a date carrying a time.
That one line does not name a column at all, so it keeps working when the table gains one. Inside the calculation, value stands for each column the condition matched, and the next chapter is built on that word.
26.1 Case, and the two words that handle it
The name tests match exactly, so name starts "q" does not find a column called Q1_score. Mixed-case names are ordinary enough that you meet this early.
There is no flag for it, because a name is text and text has a case. Fold the case and ask the question.
mixed names one column Q1_score and the next q2_score:
run(' mixed then keep where (lower([Region]) is "west")')
Q1_score
q2_score
Region
4
5
West
mixed |>keep(tolower(Region) =="west")
Q1_score
q2_score
Region
4
5
West
mixed >> keep(lower(col.Region) =="west")
Q1_score
q2_score
Region
4
5
West
Folding the case of every name automatically would have been the other option, and it is wrong. Two columns can differ only by case, and a grammar that pretends otherwise cannot treat them as the two columns they are. Asking for it is one word.
26.2 Why you have to write name
The same three words also test what is inside a column, and there the subject is the column itself.
run(' survey then keep where ([region] starts "W")')
respondent
name
region
joined
q1_score
q2_score
q3_score
q4_score
q5_score
q6_score
q7_score
q8_score
1
ana
West
2026-01-12
4
2
5
3
4
5
2
3
4
dee
West
2026-03-15
4
3
5
2
4
3
5
4
survey |>keep(startsWith(region, "W"))
respondent
name
region
joined
q1_score
q2_score
q3_score
q4_score
q5_score
q6_score
q7_score
q8_score
1
ana
West
2026-01-12
4
2
5
3
4
5
2
3
4
dee
West
2026-03-15
4
3
5
2
4
3
5
4
survey >> keep(col.region.starts("W"))
respondent
name
region
joined
q1_score
q2_score
q3_score
q4_score
q5_score
q6_score
q7_score
q8_score
1
ana
West
2026-01-12
4
2
5
3
4
5
2
3
4
dee
West
2026-03-15
4
3
5
2
4
3
5
4
So starts means one thing, “this text begins with that text”, and the subject says what text is being asked about. Writing it is what keeps the two apart.
The alternative would have been pick(starting("q")), which is shorter and leaves nothing in the sentence to say whether starting is looking at a name or at a value. You would learn it from the position, which is the kind of rule this grammar removes rather than adds.
The tests are the same words in both languages. What differs is how a column’s name is reached, which is the same difference as everywhere else. R writes a bare name and Python writes col.name, so R spells this with base R’s startsWith and Python with a method.
26.3 A percent sign is a percent sign
The three tests read text literally, with no pattern language hiding inside. R borrows grepl’s shape to ask the question, and the reading is still the grammar’s: the marks that pattern languages treat as instructions, % and . among them, here only ever mean themselves.
run(' notes then keep where [note] contains "100%"')
note
grew 100% this year
notes |>keep(grepl("100%", note))
note
grew 100% this year
notes >> keep(col.note.contains("100%"))
note
grew 100% this year
notes then keep where [note] contains "100%"
When the question really is a pattern, it belongs to the host language, a boundary the coverage appendix states with the rest of them.
26.4 One word, one seat
kind asks about a column, so the where that chooses columns is the one seat it has. Written into an ordinary row question, it is refused rather than guessed at:
Error:
!
illegal: there is no column called `kind`. The table has: date, region, product, quantity, revenue, cost
|
2 | then keep where ([kind] is "number")
| ^^^^
try: collect(sales >> keep(kind =="number"))except GodError as refusal:print(refusal)
illegal: `kind` means what a column holds, and the `where` that chooses columns is the one place that asks. Write it as `pick where kind is "number"`
|
2 | then keep where (kind is "number")
| ^^^^
The two messages differ, because a bare kind is not the same thing in the two languages. In Python it is the grammar’s own word, so the message hands the word back to its seat, with the working spelling written out. In R a bare name inside keep is a column, so its message is the one a missing column gets: there is no column called kind.