How do I take part of a string in every row of a pandas column?
Short answer
Use the str accessor with ordinary slicing to take a fixed range of characters from every value. It is vectorised and handles missing values, unlike a loop or an apply.
The short version
Use .str[:3] to take a fixed slice from every value in a column. The .str accessor supports normal Python slicing, applies it to the whole column at once, and returns NaN for missing values rather than raising.
Slicing a column
import pandas as pd
codes = pd.Series(["ABCDEF", "GHIJKL"])
codes.str[:3].tolist() # ['ABC', 'GHI']
codes.str[-2:].tolist() # ['EF', 'KL']
codes.str[2:4].tolist() # ['CD', 'IJ']
The syntax is exactly Python's slicing, applied element by element. Negative indices count from the end, as usual.
Why not apply
codes.apply(lambda s: s[:3]) # works, but slower and fragile
This runs a Python function per row, and it raises TypeError on any missing value, because you cannot slice a NaN. The .str accessor is vectorised and returns NaN for missing entries, so it survives real data.
Getting a single character
.str[0] takes the first character, and .str.get(0) does the same with a clearer name.
codes.str[0].tolist() # ['A', 'G']
codes.str.get(0).tolist() # ['A', 'G']
When the position is not fixed
Fixed slicing assumes every value has the same layout, which real data often breaks. If the part you want is delimited rather than positional, split instead.
pd.Series(["pen-red"]).str.split("-").str[0] # ['pen']
pd.Series(["pen-red"]).str.split("-", expand=True) # two columns
And if the structure varies, use a regular expression with extract, which returns NaN for anything that does not match, telling you which rows break the pattern.
pd.Series(["PEN-001"]).str.extract(r"([A-Z]+)-(\d+)")
The fragility of fixed positions
Slicing by position is the least robust approach, because it silently produces wrong results the moment a value has a different length or layout. A code that gains a prefix, or an occasional short value, will slice into the wrong place and give plausible nonsense.
So use positional slicing only when the format is genuinely fixed and guaranteed (a country code, a fixed-width export). Otherwise prefer splitting on a delimiter or extracting with a pattern, both of which fail visibly rather than silently.
Checking your assumption
Before slicing by position, confirm every value really is the same shape.
codes.str.len().value_counts()
If more than one length appears, positional slicing is the wrong tool and will corrupt some of your rows.