isin, between & str.contains
Shortcut filters: isin for 'one of these values', between for numeric ranges, and a first look at str.contains / startswith for text matching.
Some filters come up so often that writing them with | and & gets tedious. "Players from MI, CSK or KKR" needs three == joined by |. "Runs between 200 and 450" needs two comparisons joined by &. "Names containing Sharma" can't be written with == at all. Pandas has a dedicated method for each.
Definition
Series.isin(values)returns a boolean mask that isTruewhere the element is one of the given values.Series.between(left, right)returnsTruewhere the element lies within the rangeleft…right(both ends included by default).Series.str.contains(pattern)returnsTruewhere a text value contains the pattern.
- All three return masks, so they plug straight into
df[...]ordf.loc[...]and combine with&,|,~. isinis the pandas version of SQL'sIN (...);betweenis SQL'sBETWEEN ... AND ...;str.containsis like SQL'sLIKE '%...%'..stris the string accessor — a gateway to dozens of text methods (lower,startswith,replace,split…) that work on every value of a text column at once. It gets its own chapter later; here we use it for filtering.
Syntax
python
s.isin(values) # values: list, set, Series...
s.between(left, right, inclusive="both") # "both", "neither", "left", "right"
s.str.contains(pat, case=True, na=None, regex=True)
s.str.startswith(prefix), s.str.endswith(suffix)| Parameter | Meaning | Default |
|---|---|---|
values (isin) | the allowed values (list / set / Series) | required |
left, right (between) | lower and upper bounds | required |
inclusive (between) | which ends count: "both", "neither", "left", "right" | "both" |
pat (str.contains) | text (or regular expression) to look for | required |
case (str.contains) | False → ignore upper/lower case | True |
na (str.contains) | value to use for missing text (e.g. False) | None |
regex (str.contains) | treat pat as a regular expression | True |
Example — isin (and NOT isin)
python
print(players[players["team"].isin(["MI", "CSK"])][["player", "team"]])
print(players[~players["team"].isin(["MI", "CSK"])][["player", "team"]])text
player team
1 Rohit Sharma MI
2 MS Dhoni CSK
3 Jasprit Bumrah MI
4 Hardik Pandya MI
5 Ravindra Jadeja CSK
player team
0 Virat Kohli RCB
6 Shubman Gill GT
7 Rashid Khan GTHow it works: isin(["MI", "CSK"]) is exactly (team == "MI") | (team == "CSK"), but shorter and easy to extend to 10 teams. Putting ~ in front gives NOT IN — pandas has no notin method, so ~...isin(...) is the standard idiom.
Example — between
python
print(players[players["runs"].between(200, 450)][["player", "runs"]])
print(players[players["runs"].between(200, 426, inclusive="left")][["player", "runs"]])text
player runs
1 Rohit Sharma 417
4 Hardik Pandya 216
5 Ravindra Jadeja 267
6 Shubman Gill 426
player runs
1 Rohit Sharma 417
4 Hardik Pandya 216
5 Ravindra Jadeja 267How it works: between(200, 450) = (runs >= 200) & (runs <= 450) — both ends included. In the second call the upper bound is exactly Gill's 426, and inclusive="left" means "include 200 but exclude 426", so Gill drops out. between also works on dates (df["date"].between("2024-04-01", "2024-04-30")) and on strings (alphabetical range).
Example — str.contains teaser
python
print(players[players["player"].str.contains("Sharma")])
print(players[players["player"].str.contains("sh", case=False)][["player"]])
print(players[players["player"].str.startswith("R")][["player"]])text
player team role runs wickets strike_rate
1 Rohit Sharma MI Batter 417 0 150.0
player
1 Rohit Sharma
6 Shubman Gill
7 Rashid Khan
player
1 Rohit Sharma
5 Ravindra Jadeja
7 Rashid KhanHow it works: str.contains("Sharma") checks whether each name contains the text anywhere. With case=False the search for "sh" also matches the capital Sh in "Sharma" and "Shubman" and the "sh" in "Rashid". str.startswith("R") matches only names beginning with R. If the column has missing values, add na=False so those rows count as "no match".
Comparison — long form vs shortcut
| Question | Long form | Shortcut |
|---|---|---|
| team is MI or CSK | (t == "MI") | (t == "CSK") | t.isin(["MI", "CSK"]) |
| team is not MI or CSK | (t != "MI") & (t != "CSK") | ~t.isin(["MI", "CSK"]) |
| 200 ≤ runs ≤ 450 | (r >= 200) & (r <= 450) | r.between(200, 450) |
| name contains "Sharma" | — (== can't do it) | n.str.contains("Sharma") |
| name starts with "R" | — | n.str.startswith("R") |
Common mistakes
- ❌
players["team"].isin("MI")→TypeError(a single string is not a list of values). ✅isin(["MI"]). - ❌ Looking for a
notinmethod. ✅ Use~s.isin([...]). - ❌ Forgetting that
betweenincludes both ends. ✅ Useinclusive="neither"/"left"/"right"when an end must be excluded. - ❌
str.contains("(c)")matching unexpected rows. ✅patis a regular expression by default; passregex=Falsefor plain text with special characters like( . + ?. - ❌
ValueError: Cannot mask with non-boolean array containing NA / NaN values. ✅ The text column has missing values — usestr.contains(..., na=False).
Interview tip
Q: How do you filter rows whose value is NOT in a list, in pandas?
A: Negate isin with ~: df[~df["team"].isin(["MI", "CSK"])]. There is no notin method; the tilde inverts the boolean mask element-wise.
Key points to remember
- s.isin([...]) is True where the value is one of the given values — the pandas version of SQL IN.
- ~s.isin([...]) gives NOT IN — pandas has no notin method.
- s.between(a, b) includes both ends by default; inclusive='neither'/'left'/'right' changes that.
- s.str.contains('text') matches text anywhere in the value; case=False ignores case, na=False handles missing values.
- str.contains treats the pattern as a regular expression unless regex=False.
- str.startswith / str.endswith match the beginning or end of the text.
- All of them return boolean masks, so they combine with & | ~ and work inside df[...] and df.loc[...].
Try it yourself
isin and NOT isin
between with different inclusive options
str.contains, startswith and missing values
Practice: build a selection shortlist
Revision Flashcards
Tap a card to flip it and see the answer.
