Index & Labels
Access Series values by label or by position, slice with .loc and .iloc, filter with conditions, and get the index and values out.
Watch the lesson
1 video on CodeWithMunnaX
A Series stores two things side by side: the index (labels) and the values. Because there are two ways to point at a value — "Virat's runs" (a label) or "the 2nd value" (a position) — pandas gives you two accessors, .loc and .iloc. Understanding the difference here, on a simple Series, makes DataFrame selection much easier later.
Definition
The index of a Series is an immutable sequence of labels, one per value, used to look values up, align data and display rows. Label-based access (
.loc) finds values by these labels; position-based access (.iloc) finds values by their integer position0 … n-1, exactly like a Python list.
- Immutable — you cannot change one label in place (
s.index[0] = "x"raisesTypeError), but you can replace the whole index (s.index = [...]) or userename. - Label — any hashable value: strings, integers, dates. Labels do not have to be unique (but unique is best).
- Position — always
0, 1, 2, …from the start, and-1, -2, …from the end, no matter what the labels are.
Syntax
python
s["label"] # label (for a non-integer index)
s.loc["label"] # label — always
s.iloc[0] # position — always
s.loc["start":"end"] # label slice: END INCLUDED
s.iloc[0:3] # position slice: end excluded
s[["a", "b"]] / s.iloc[[0, 2]] # several labels / positions -> a Series
s[s > 50] # boolean filter
s.get("label", default) # safe lookup: returns default if missing
s.index / s.values / s.to_numpy()| Accessor | Looks up by | Slice end | Missing key |
|---|---|---|---|
s.loc[...] | label | included | KeyError |
s.iloc[...] | integer position | excluded | IndexError |
s[...] | label (positions only for slices) | depends | KeyError |
s.get(key, default=None) | label | — | returns default |
Example — label vs position
python
import pandas as pd
runs = pd.Series([45, 78, 12, 101, 33],
index=["Rohit", "Virat", "Gill", "Pant", "Hardik"],
name="runs")
print(runs["Virat"])
print(runs.loc["Virat"])
print(runs.iloc[1])text
78
78
78All three point at the same value: label "Virat" sits at position 1.
Example — slicing with .loc vs .iloc
python
print(runs.loc["Virat":"Pant"])
print(runs.iloc[1:3])text
Virat 78
Gill 12
Pant 101
Name: runs, dtype: int64
Virat 78
Gill 12
Name: runs, dtype: int64How it works: .loc["Virat":"Pant"] includes the end label Pant (labels have no natural "one past the end"). .iloc[1:3] behaves like a Python list slice — positions 1 and 2, excluding 3. Steps work too: runs.iloc[::-1] reverses the Series.
Example — several values and boolean filtering
python
print(runs[["Rohit", "Pant"]])
print(runs.iloc[[0, -1]])
print(runs[runs > 40])
print(runs[(runs > 30) & (runs < 100)])text
Rohit 45
Pant 101
Name: runs, dtype: int64
Rohit 45
Hardik 33
Name: runs, dtype: int64
Rohit 45
Virat 78
Pant 101
Name: runs, dtype: int64
Rohit 45
Virat 78
Hardik 33
Name: runs, dtype: int64How it works: a list inside [] returns several values as a new Series. A boolean condition like runs > 40 produces a True/False Series; using it inside [] keeps only the True rows. Combine conditions with & (and), | (or), ~ (not), and always wrap each condition in parentheses.
Example — the integer-index trap
When the index itself contains integers, [] with a single number means label, not position:
python
by_year = pd.Series([10, 20, 30], index=[2021, 2022, 2023])
print(by_year[2022]) # label 2022
print(by_year.iloc[0]) # first position
by_year[0] # KeyError: 0 (there is no LABEL 0)text
20
10
KeyError: 0The same KeyError: 0 happens with a string index: runs[0] fails because 0 is not a label. Old tutorials used s[0] to mean "first value" — that fallback was removed.
Note (pandas 3):
s[i]with an integer is always a label lookup. For "the first value" writes.iloc[0]. Use.loc/.ilocwhenever there is any doubt.
Example — getting the index and the values out
python
print(runs.index)
print(runs.values)
print(runs.to_numpy())
print(runs.index.tolist())
print(runs.tolist())
print("Virat" in runs, 78 in runs, 78 in runs.values)text
Index(['Rohit', 'Virat', 'Gill', 'Pant', 'Hardik'], dtype='str')
[ 45 78 12 101 33]
[ 45 78 12 101 33]
['Rohit', 'Virat', 'Gill', 'Pant', 'Hardik']
[45, 78, 12, 101, 33]
True False TrueHow it works: .index returns an Index object. .values and .to_numpy() return the data as a NumPy array — prefer .to_numpy() (it is explicit and always returns NumPy). .tolist() gives a plain Python list. Note that in checks the index labels, not the values — 78 in runs is False!
Changing values and labels
python
runs.loc["Gill"] = 60 # update one value by label
runs.loc["Dhoni"] = 7 # a NEW label appends a new value
runs.index = ["RS", "VK", "SG", "RP", "HP", "MSD"] # replace the whole index
runs.get("Kohli", 0) # 0 instead of KeyError when the label is missingComparison
| Question | .loc | .iloc |
|---|---|---|
| Uses | labels | positions 0…n-1 |
["Virat"] / [1] | ✅ .loc["Virat"] | ✅ .iloc[1] |
| Slice end included? | yes | no |
| Negative numbers | only if they are labels | count from the end |
| Boolean mask | ✅ | ✅ (pass mask.to_numpy()) |
Common mistakes
- ❌
runs[0]to get the first value of a labelled Series →KeyError. ✅runs.iloc[0]. - ❌ Expecting
runs.loc["Virat":"Pant"]to stop beforePant. ✅ Label slices include both ends. - ❌
runs[runs > 30 & runs < 100]. ✅runs[(runs > 30) & (runs < 100)]— parentheses are required, and use¬and. - ❌
78 in runsto check values. ✅78 in runs.values(inchecks labels). - ❌
runs.index[0] = "RS". ✅ The index is immutable — assign a new full index or useruns.rename({"Rohit": "RS"}).
Interview tip
Q: What is the difference between .loc and .iloc?
A: .loc selects by label and its slices include the end label; .iloc selects by integer position and its slices exclude the end, like Python lists. With an integer index, plain s[2] means label 2 — so use .iloc when you mean position.
Key points to remember
- The index holds one label per value; it is immutable but can be replaced as a whole.
- .loc selects by label (slice end included); .iloc selects by position (slice end excluded).
- In pandas 3, s[i] with an integer is always a label lookup — use s.iloc[i] for positions.
- A list of labels/positions returns a Series; a boolean condition filters values.
- Combine conditions with &, |, ~ and wrap each one in parentheses.
- .to_numpy() gives the values as a NumPy array; .index gives the labels; .tolist() a Python list.
- `label in series` checks the index, not the values.
Try it yourself
Label vs position access
Filtering and fancy selection
Index, values and modifying
Practice: pick your batters
Revision Flashcards
Tap a card to flip it and see the answer.
