Pandas
Intermediate 15 min read Pandas

Indexing and Selection

Selecting rows and columns the safe way

.loc and .iloc are the two selection dialects — label-based and position-based — plus boolean masks and .at/.iat for scalars; the 'chained assignment' pitfall lurks for the unwary.

locilocboolean mask.at.iatchained assignment
Prerequisites: Pandas Introduction
Before we start

What you will learn

  • Use .loc for labels and .iloc for positions
  • Filter with boolean masks
  • Avoid chained-assignment mutations
Introduction

What is this?

df.loc[rows, cols] selects by label/index values; df.iloc[...] selects by integer position. Boolean masks (df[df['revenue'] > 10]) filter rows, and explicitly as df.loc[mask, cols] to set values. The setter-with-chained-indexing warning is real: setting through df[a][b] = x is undefined behavior.

Motivation

Why do you need it?

  • Label vs position confusion is the single most common pandas bug.
  • .loc['2026-01':] slices a time index by label — impossible with positions.
  • Safe assignment via .loc prevents the infamous CopyOnWrite warnings and silent drops.
  • Writing selection as .loc[rows, cols] is readable and future-proof.
Think about it

Mental model

Imagine

Calling a person by name vs pointing to their seat number

Think of

.loc vs .iloc

Names (labels) are stable and semantic; seats (positions) are quick but change when the room is rearranged. Pick by what you mean, not what's convenient.

Mechanics

How it works

  • df.loc['2026-03-14', ['revenue']] select with label arguments in row, column order.
  • df.iloc[0, 2] selects the first row's third column by position.
  • Slices in .loc are inclusive of both ends (label semantics); .iloc slices are exclusive like Python.
  • Boolean mask df[mask] returns rows where mask is True; mask can be built from any column comparison.
  • df.at[label, col] / df.iat[position] fetch scalars fastest.

Choosing the selector.

By label/index
By position
By condition
Scalar
Mutation
Hands-on

Examples

Selection dialects in action

Selection dialects in action
import pandas as pd
df = pd.DataFrame({"user": ["a","b","c"], "rev": [10, 20, 30]}, index=[2, 5, 8])
print(df.loc[5]) # the row labeled 5
print(df.iloc[0]) # the first row (position)
big = df.loc[df['rev'] > 15] # mask
df.loc[big.index, 'rev'] = df.loc[big.index, 'rev'] * 1.1 # safe set
print(df)

Labels like 5 and positions like 0 are different concepts — .loc[5] picks the index-labeled row while .iloc[0] picks whatever sits first. Setting via .loc avoids chained-assignment surprises.

Real world

In production

  • A timeseries dashboard slices df.loc['2026-03':] for the current month by label.
  • An ETL job updates only null cells: df.loc[df['x'].isna(), 'x'] = fallback.
  • A partition merge keys rows to frames by user_id labels rather than position.
Guidelines

Best practices

  • Prefer .loc/.iloc over plain df[...] for anything beyond a simple column.
  • Assign through a single .loc call; never df[a][b] = v.
  • Set df.index name (e.g., 'date') and use label slices generously.
Watch out

Common mistakes

  • Writing df[0] expecting the first row — that's a column key error.
  • Slicing .loc[0:2] and assuming it stops at position 2 — it INCLUDES label 2.
  • Setting with chained indexing and getting a SettingWithCopyWarning (or silent no-op).
When things break

Debugging tips

  • Reading a SettingWithCopyWarning? Rewrite the setter as a single .loc call.
  • If selection returns empty, print df.index — the labels don't match what you assumed.
  • For duplicate index labels, .loc returns a DataFrame, not a Series — a common type surprise.
Interviews

Interview checkpoint

Practice

Hands-on lab

Selection Championship
25 min · intermediate
In progress
Objectives
  • Practice .loc vs .iloc on a non-default index
  • Filter with masks and assign safely
  • Catch the chained-assignment trap

Selection Championship

25 min · intermediate

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/2
1

With index [2,5,8], df.iloc[1] returns the row labeled:

2

.loc slices include both endpoints; .iloc slices exclude the stop like Python.

Go deeper

Official documentation