Which of the year’s orders happened in the West? Most questions about a table begin this way, by wanting fewer rows than you have. keep is the verb: it takes a condition, asks it of every row, and keeps the rows where the answer is yes.
run(' sales then keep where ([region] is "West")')
date
region
product
quantity
revenue
cost
2025-11-03
West
Widget
4
100
40
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-04-11
West
Widget
2
50
20
2026-07-08
West
Sprocket
6
90
72
2026-10-02
West
Widget
5
125
50
sales |>keep(region =="West")
date
region
product
quantity
revenue
cost
2025-11-03
West
Widget
4
100
40
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-04-11
West
Widget
2
50
20
2026-07-08
West
Sprocket
6
90
72
2026-10-02
West
Widget
5
125
50
sales >> keep(col.region =="West")
date
region
product
quantity
revenue
cost
2025-11-03
West
Widget
4
100
40
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-04-11
West
Widget
2
50
20
2026-07-08
West
Sprocket
6
90
72
2026-10-02
West
Widget
5
125
50
sales then keep where [region] is "West"
Six of the fifteen rows survive. The other nine were not deleted from anything; sales is untouched, and the pipeline handed back a new, smaller table. Every verb in this grammar works that way, so no step can damage the table a colleague is also using.
3.1 Combining conditions
Conditions join with and and or, and a negation reverses one. Ask for the larger orders that were still cheap to fulfill:
Python needs the parentheses and R does not, because Python’s & binds more tightly than its ==. That rule is Python’s rather than the grammar’s: the preface holds the short list of rules Python brings of its own, and that list is the whole of the subject.
3.2 The words where the hosts disagree
Look at what the text form did to the R sentences above: == became is, and & became and. That is not a style choice; it is a decision about which words can be in a grammar at all.
A symbol that means different things in different tools is a word this grammar refuses to own. = assigns in one language, compares in another, and half the world’s beginners have confused the two. So the text form spells every contested symbol as the English word that means only one thing, and each binding translates its host’s habit into that word:
you write in R
you write in Python
the grammar’s word
==
==
is
!=
!=
is not
&
&
and
\|
\|
or
!
~
not
%in%
.is_in([...])
in { }
is.na(x)
.is_missing()
[x] is missing
You keep writing your language’s habits, and the grammar keeps one meaning under them. When a pipeline moves to a database cell or a colleague’s screen, it is the unambiguous spelling that travels.
3.3 One verb, both directions
There is no separate verb for dropping rows. Removing the rows where something is true is keeping the rows where it is false, and one verb says both:
run(' sales then keep where (not ([product] is "Widget"))')
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-02-14
North
Doohickey
2
80
50
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-06-19
East
Sprocket
10
150
120
2026-07-08
West
Sprocket
6
90
72
2026-09-15
East
Gadget
1
60
40
sales |>keep(!(product =="Widget"))
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-02-14
North
Doohickey
2
80
50
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-06-19
East
Sprocket
10
150
120
2026-07-08
West
Sprocket
6
90
72
2026-09-15
East
Gadget
1
60
40
sales >> keep(~(col.product =="Widget"))
date
region
product
quantity
revenue
cost
2025-11-17
East
Gadget
2
120
80
2025-12-05
West
Doohickey
3
120
75
2026-01-26
West
Gadget
5
300
200
2026-02-14
North
Doohickey
2
80
50
2026-03-03
East
Doohickey
5
200
125
2026-05-06
North
Gadget
4
240
160
2026-06-19
East
Sprocket
10
150
120
2026-07-08
West
Sprocket
6
90
72
2026-09-15
East
Gadget
1
60
40
This is the first appearance of a rule you will meet on every page: if the grammar can already say something, it does not grow a second way to say it. A drop verb would cost a word, and every reader who met drop(x) beside keep(!x) would have to learn, and then remember, that they are the same thing. The grammar spends its words on meanings, not on synonyms.
3.4 What travels with it
where is how the text form marks the condition, as in the gloss above. You never type it in R or Python; the condition sits inside the verb.
The condition language travels. A when (choosing a value by a question) takes the conditions keep takes, unchanged. A pick where (choosing columns by a question) joins and negates its questions the same way, and asks them about a column’s name and kind rather than its values.
and, or, not, is, in, missing are the grammar’s own words for the contested symbols, and the table above is their whole story.
3.5 What it refuses
A condition that can never be true is a mistake, not a preference, and the grammar says so before anything runs. Text is never greater than a number:
Error:
!
illegal: this compares text with a number, which can never match. Convert one of them first
|
2 | then keep where ([region] > 100)
| ^^^^^^^^^^^^^
try: collect(sales >> keep(col.region >100))except GodError as refusal:print(refusal)
illegal: this compares text with a number, which can never match. Convert one of them first
|
2 | then keep where ([region] > 100)
| ^^^^^^^^^^^^^
Nothing was scanned and no rows came back empty. The pipeline was refused whole, at the step that is wrong, with the fix in the message. A tool that instead answered with zero rows would have told you something false about your data, quietly, and you would have believed it.