Searching Text (contains, startswith, match)
Find rows whose text contains, starts with or ends with a pattern, locate and count substrings, and control case, missing values and regex behaviour.
"Show me every match played at a stadium with Stadium in its name", "all players whose name starts with R", "every team name containing Super". These are text searches, and they are how you filter rows by text in pandas. The searching methods return a True/False Series, which you drop straight into df[...] — exactly like a numeric condition df[df["runs"] > 50].
Definition
String-search methods test or locate a pattern inside each string. Test methods (
contains,startswith,endswith,match,fullmatch) return a boolean Series —Truewhere the string has the pattern — that can be used as a row filter. Locate methods (find,count) return numbers: the position of the first match, or how many times it occurs.
- Pattern — a plain substring (
"Super") or a regular expression ("^R","Kings|Royals"). - Row filter —
df[df["team"].str.contains("Kings")]keeps matching rows. - Position — counted from 0;
-1means "not found".
Syntax
python
s.str.contains(pat, case=True, flags=0, na=<default>, regex=True)
s.str.startswith(pat, na=<default>) # pat can be a tuple of strings
s.str.endswith(pat, na=<default>)
s.str.match(pat, case=True, na=<default>) # regex must match at the START
s.str.fullmatch(pat, case=True, na=<default>) # regex must match the WHOLE string
s.str.find(sub, start=0, end=None) # position of first occurrence, -1 if none
s.str.count(pat) # number of (regex) occurrences| Parameter | Meaning | Default |
|---|---|---|
pat | substring or regex pattern to look for | required |
case | True = case-sensitive; False = ignore case | True |
na | value to use for missing strings | False for the pandas 3 str dtype |
regex | True = treat pat as a regular expression; False = plain text | True (for contains) |
flags | regex flags such as re.IGNORECASE | 0 |
sub, start, end (find) | substring and optional search window | — |
Example — contains as a filter
python
teams = pd.DataFrame({
"team": ["Chennai Super Kings", "Mumbai Indians", "Kings XI Punjab",
"Rajasthan Royals", "Royal Challengers Bangalore", None],
"titles": [5, 5, 0, 1, 0, 0],
})
print(teams["team"].str.contains("Kings"))
print(teams[teams["team"].str.contains("Kings")])text
0 True
1 False
2 True
3 False
4 False
5 False
Name: team, dtype: bool
team titles
0 Chennai Super Kings 5
2 Kings XI Punjab 0How it works: contains("Kings") returns True for rows where the substring appears anywhere. The missing name gives False, so the mask is a clean boolean filter.
Example — case=False and na=
python
print(teams[teams["team"].str.contains("royal", case=False)])
print(teams["team"].str.contains("Kings", na=True).tolist())text
team titles
3 Rajasthan Royals 1
4 Royal Challengers Bangalore 0
[True, False, True, False, False, True]How it works: case=False matches Royals and Royal regardless of capital letters. na=True decides what missing values become in the mask — here they would be kept by a filter.
Note (pandas 3): for the new
strdtype, missing values giveFalseby default. In pandas 2 (objectdtype) they gaveNaN, anddf[mask]failed with "Cannot mask with non-boolean array containing NA / NaN values" — which is why old code often writesna=False. Writingna=Falseexplicitly is still a good habit.
Example — regex power: OR, start, end
contains treats the pattern as a regular expression by default:
python
t = teams["team"]
print(t[t.str.contains("Kings|Royals", na=False)].tolist()) # | means OR
print(t[t.str.contains("^R", na=False)].tolist()) # ^ = starts with
print(t[t.str.contains("s$", na=False)].tolist()) # $ = ends withtext
['Chennai Super Kings', 'Kings XI Punjab', 'Rajasthan Royals']
['Rajasthan Royals', 'Royal Challengers Bangalore']
['Chennai Super Kings', 'Mumbai Indians', 'Rajasthan Royals']| Regex | Meaning | Example match |
|---|---|---|
a|b | a or b | Kings|Royals |
^ | start of string | ^R → Rajasthan, Royal |
$ | end of string | s$ → ends in s |
. | any one character | K.ngs |
\d | a digit | \d+ → 45 |
\s | whitespace | \s |
+ / * / ? | one-or-more / zero-or-more / optional | \d+ |
[abc] | any one of a, b, c | [KR] |
Example — special characters: regex=False
python
venues = pd.Series(["Eden Gardens", "Feroz Shah Kotla (Arun Jaitley)", "Wankhede Stadium"])
print(venues.str.contains("(", regex=False).tolist())
print(venues.str.contains(".", regex=False).tolist())
print(venues.str.contains(".").tolist())text
[False, True, False]
[False, False, False]
[True, True, True]How it works: in regex, ( starts a group and . means "any character". With regex=False they are matched literally. Without it, "." matches every non-empty string — a classic bug.
Example — startswith / endswith / match
python
players = pd.Series(["Rohit Sharma", "Rishabh Pant", "Virat Kohli", "Ravindra Jadeja", "KL Rahul"])
print(players[players.str.startswith("R")].tolist())
print(players[players.str.startswith(("V", "K"))].tolist()) # tuple = any of these
print(players[players.str.endswith("a")].tolist())
print(players.str.match(r"R\w+ [A-Z]").tolist())text
['Rohit Sharma', 'Rishabh Pant', 'Ravindra Jadeja']
['Virat Kohli', 'KL Rahul']
['Rohit Sharma', 'Ravindra Jadeja']
[True, True, False, True, False]startswith / endswith take plain text (no regex), and accept a tuple for several options. match is regex-based but anchored at the start; fullmatch requires the whole string to match.
Example — find and count
python
print(pd.DataFrame({
"player": players,
"find_a": players.str.find("a"),
"count_a": players.str.count("a"),
"words": players.str.count(" ") + 1,
}))text
player find_a count_a words
0 Rohit Sharma 8 2 2
1 Rishabh Pant 4 2 2
2 Virat Kohli 3 1 2
3 Ravindra Jadeja 1 4 2
4 KL Rahul 4 1 2How it works: find("a") gives the index of the first lowercase a (-1 if none — Virat has a lowercase a at position 3). count("a") counts every occurrence (it is regex-based, so escape special characters).
Comparison — which search method?
| Need | Method | Regex? |
|---|---|---|
| pattern anywhere | contains | yes (default) |
| starts / ends with fixed text | startswith / endswith | no |
| regex from the start | match | yes |
| whole string must fit a pattern | fullmatch | yes |
| exact value from a list | isin(["MI", "CSK"]) | no (whole value) |
| where is it? / how many? | find / count | find no, count yes |
Common mistakes
- ❌
df[df["team"] == "Kings"]expecting partial matches. ✅==compares whole values; usestr.contains("Kings"). - ❌
contains("(")→ regex error;contains(".")→ matches everything. ✅regex=Falsefor literal text. - ❌ Forgetting
case=Falseand missing"royal challengers". ✅contains("royal", case=False). - ❌
startswith("R|V")—startswithis not regex. ✅startswith(("R", "V")). - ❌ Using
~(NOT) on a mask withNaNfrom old object columns. ✅ Passna=Falsefirst.
Interview tip
Q: Difference between str.contains, str.match and str.fullmatch?
A: contains finds the pattern anywhere, match requires it at the start of the string, fullmatch requires the entire string to match.
Key points to remember
- Search methods return a boolean Series that filters rows: df[df['team'].str.contains('Kings')].
- contains() treats the pattern as a regex by default; use regex=False for literal '(', '.', '+' …
- case=False ignores capital letters; na= sets what missing values become (False by default in pandas 3).
- startswith/endswith use plain text and accept a tuple of options.
- match() anchors the regex at the start; fullmatch() requires the whole string to match.
- find() returns the position of the first match (-1 if absent); count() counts occurrences.
- == and isin() compare whole values; .str.contains() finds partial text.
Try it yourself
Filter teams by text
startswith, endswith, find and count
Practice: find venues in brackets or with 'Stadium'
Revision Flashcards
Tap a card to flip it and see the answer.
