One method, three behaviors, decided entirely by what is inside the cell.
What it does
Why does a method that lives on the .str accessor work perfectly well on a column that contains no strings at all?
Because .str.get() is not really a string method. It indexes whatever the cell holds, exactly as square brackets would in Python: the nth character of a string, the nth element of a list, the value for that key in a dictionary. As I put it while solving Bamboo Weekly #30, the .str accessor "actually works on any kind of Python object, assuming that we invoke a method that the object supports."
Official documentation: Series.str.get
The argument
There is one, called i, and it is required. Pass an integer for a string or a list, positive from the front or negative from the back; pass a key for a dictionary. There is no default parameter, and asking for one is a TypeError — because a miss is not an error here in the first place.
A worked example, on real data
The USGS earthquake catalog answers a plain HTTP request with GeoJSON that has all three cell types in it. Here is every magnitude 2.5 or greater event in July 2026:
import pandas as pd
import requests
url = ('https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson'
'&starttime=2026-07-01&endtime=2026-08-01&minmagnitude=2.5')
df = pd.DataFrame(requests.get(url).json()['features'])
df.dtypes
type str
properties object
geometry object
id str
dtype: object
2,611 rows, and two of the four columns are object because every cell in them is a Python dictionary:
df['geometry'].iloc[0]
{'type': 'Point', 'coordinates': [-168.013, 52.225, 21.7]}
.str.get() reaches into that dictionary by key:
df['properties'].str.get('place').head(3)
0 98 km SE of Nikolski, Alaska
1 91 km SE of Nikolski, Alaska
2 36 km SE of Sulangan, Philippines
Name: properties, dtype: object
The value under coordinates is a list of longitude, latitude and depth, so a second .str.get() — this one with an integer — takes the depth out of it:
df['geometry'].str.get('coordinates').str.get(2).head(3)
0 21.700
1 5.965
2 10.069
Name: geometry, dtype: float64
One method, one chain, indexing a dictionary and then a list. The whole unpacking is a single assign:
quakes = (
df
.assign(place=lambda df_: df_['properties'].str.get('place'),
mag=lambda df_: df_['properties'].str.get('mag'),
lon=lambda df_: df_['geometry'].str.get('coordinates').str.get(0),
lat=lambda df_: df_['geometry'].str.get('coordinates').str.get(1),
depth=lambda df_: df_['geometry'].str.get('coordinates').str.get(2))
[['place', 'mag', 'lon', 'lat', 'depth']]
)
quakes.head(3)
place mag lon lat depth
0 98 km SE of Nikolski, Alaska 3.0 -168.0130 52.2250 21.700
1 91 km SE of Nikolski, Alaska 2.9 -167.9262 52.3510 5.965
2 36 km SE of Sulangan, Philippines 4.5 126.0982 10.7465 10.069
Five keys, five calls, no ambiguity about what came from where. Note that place came back as object rather than str, since Pandas cannot know in advance what a dictionary lookup will hand it; astype fixes that. And when the nesting runs deeper, or you want every key at once, json_normalize flattens the lot in one call instead.
place is now an ordinary text column, and the method changes meaning a third time: .str.get(0) is the first character of each name, .str.get(-1) the last. Neither is worth much here — but split the column and .str.get(-1) becomes the country. That pairing, after str.split, is by a wide margin the most common use of this method in real code:
quakes['place'].str.split(', ').str.get(-1).value_counts().head(6)
place
Alaska 795
Mexico 192
Indonesia 154
Philippines 121
Japan 90
Puerto Rico 81
Name: count, dtype: int64
Alaska had 795 of the world's 2,611 recorded quakes that month, four times the runner-up.
Four mistakes people make
Expecting an out-of-range index to raise. It returns a missing value instead, and that is the most expensive thing about this method. Take the region column from above and pull its third character:
region = quakes['place'].str.split(', ').str.get(-1)
region.str.get(2).isna().sum()
72
region[region.str.get(2).isna()].value_counts()
place
CA 67
MX 5
Name: count, dtype: int64
Nothing is wrong with those 72 rows. They are two characters long while the other 2,539 are four or more, because the USGS abbreviates US states and spells out countries. Build a three-letter tag with region.str.get(0) + region.str.get(1) + region.str.get(2) and those 72 come back as NaN — California silently deleted from the middle of a clean column, nothing raised, nothing warned. .str.slice(0, 3) truncates gracefully instead, with no missing values at all: CA for California, Ind for Indonesia.
Expecting .str.get(0) after a split to be a string. It is one only if the split produced a list that long, and splits are not uniform:
quakes['place'].str.split(', ').str.len().value_counts()
place
2 2266
1 308
3 33
4 4
Name: count, dtype: int64
308 places have no comma at all — Kermadec Islands region, south of the Fiji Islands — so their split is a one-element list and .str.get(1) is NaN for every one. Counting from the other end fixes it: .str.get(-1) returns the whole string for those rows, which is the right answer. Before indexing a split result from the front, run .str.len().value_counts() on it.
Using it to read a fixed-width field that is not fixed width. Distances in place look like two leading digits, so two calls would seem to get them:
(quakes['place'].str.get(0) + quakes['place'].str.get(1)).head(4).tolist()
['98', '91', '36', '18']
The fourth row is 189 km SSE of Unalaska, Alaska, and it just became 18 km. 912 of these places carry a three-digit distance, and every one comes back wrong by a factor of ten. .str.split().str.get(0) reads the actual field, and fails honestly on the rows with no distance: astype(float) raises ValueError: could not convert string to float: 'south' instead of inventing a number. Reach for str.split when there is a delimiter and str.strip and friends when there is not.
Confusing it with .get() on the series itself. The series method looks up an index label, not a position, and returns one value:
quakes['place'].get(0)
'98 km SE of Nikolski, Alaska'
That is the entire first place name, because 0 is an index label here. Change the index and it vanishes: pd.Series(['alpha', 'beta'], index=[10, 20]).get(0) returns None, while .str.get(0) on the same series returns a and b. One asks about rows, the other about the contents of every row.
.str[n] is the same operation
Square brackets on the accessor call .str.get() underneath, on every cell type:
(quakes['place'].str[0] == quakes['place'].str.get(0)).all()
True
df['properties'].str['place'] works too. Brackets do one thing the method cannot, which is take a slice — region.str[0:3] is .str.slice(0, 3). Use whichever reads better; inside a long assign chain I find the named method easier to follow, and it is what the Bamboo Weekly archive uses.
Where it shows up in Bamboo Weekly
14 Bamboo Weekly solutions use .str.get(), across 93 call sites. Four worth reading, all free:
Bamboo Weekly #30: Uncertainty is split-then-get at its purest. An economic policy spreadsheet labels its rows 1990q1, 1990q2 and so on, so df_['year'].str.split('q').str.get(0).astype(np.int16) is the year and .str.get(1).astype(np.int8) the quarter.
Bamboo Weekly #48: Aviation accidents runs list indexing and key lookup through one chain. cm_vehicles holds a list of aircraft per accident; .str.get(0) takes the first one out of the list, and because that aircraft is itself a dictionary, .str.get('make') and .str.get('model') become columns.
Bamboo Weekly #78: Stock markets is the plain character case. Trading volumes end in a single-letter abbreviation, and df_['Vol.'].str.get(-1).map(factors) turns that last character into a multiplier from {'M': 1_000_000, 'K': 1000, 'B': 1_000_000_000} — see map for that half.
Bamboo Weekly #34: House of Representatives shows that the three behaviors are really one. A groupby on state and party followed by idxmax returns a series of tuples like ('ALABAMA', 'REPUBLICAN') — not strings, not lists, not dictionaries — and .str.get(1) takes the party out of each anyway.
Practice it
Work through a .str.get() exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/str-get/
Go deeper
.str.get() almost never appears alone. Upstream of it are str.split, which creates the lists it indexes, and json_normalize, the alternative once dictionaries nest more than a level or two. Downstream is astype, because whatever you pulled out of a string is still a string, and explode when you want every element of the list rather than one. Running before all of it is the cleanup pass: str.strip, str.lower, str.len and str.slice.
More Pandas videos on Python and Pandas with Reuven Lerner.
Bamboo Weekly is the practice. If you want the structured version — full courses with downloadable Jupyter notebooks, plus live Pandas office hours when you get stuck — that is LernerPython+Data. A paid Bamboo Weekly subscription is included with it.
Related methods
.str.slice()— when you want a run of characters rather than a single onepd.json_normalize()— when the whole nested structure should become columns.str.split()— when you need to break the string apart before indexing into it.str.len()— when you want to know how many pieces the split produced
See it on real data
Below are the 14 Bamboo Weekly exercises that use str.get on real-world data — try each one, then study the worked solution.
- Bamboo Weekly #181: Housing costs
- Bamboo Weekly #173: IPOs
- Bamboo Weekly #172: World Cup
- Bamboo Weekly #160: Strait of Hormuz
- Bamboo Weekly #134: Taiwan weather
- Bamboo Weekly #120: Pennies
- Bamboo Weekly #102: WordPress
- Bamboo Weekly #78: Stock markets
- Bamboo Weekly #73: Avocado hand
- Bamboo Weekly #48: Aviation accidents
- Bamboo Weekly #42: Plant hardiness
- Bamboo Weekly #34: House of Representatives
- Bamboo Weekly #30: Uncertainty
- Bamboo Weekly #3: Earthquake
Part of the Pandas Methods Index. See also practice by skill.