Skip to content

Indexing & Selecting Data

Selecting specific subsets of data (rows, columns, or individual values) is a fundamental operation in data analysis. Pandas offers powerful and optimized methods for this, primarily through the .loc and .iloc indexers.

While basic Python/NumPy indexing ([]) and attribute access (.) can work for simple cases, they can sometimes be ambiguous, especially when dealing with integer labels or slicing DataFrames. For clarity, performance, and avoiding potential pitfalls in production code, it’s strongly recommended to use the dedicated .loc and .iloc accessors.

Pandas provides two main indexers for selecting data:

IndexerSelection MethodDescription
1.loc[]Label-based selection. Selects data based on index labels (row names) and column names.
2.iloc[]Integer position-based selection. Selects data based on integer positions (0-based indexing, like Python lists).

Important Note: The .ix[] indexer, which attempted to handle both label and integer-based indexing, is deprecated due to its potential for ambiguity and confusion. You should always use .loc for label-based indexing and .iloc for integer position-based indexing.

.loc[] is used for selecting data by labels (index names, column names). It accepts various inputs:

  • A single label (e.g., 'a' or 5 if the index contains the integer 5 as a label).
  • A list or array of labels (e.g., ['a', 'b', 'c']).
  • A slice object with labels (e.g., 'a':'f'). Crucially, when slicing with labels using .loc, both the start and stop bounds are included.
  • A boolean array (of the same length as the axis being indexed).
  • A callable function with one argument (the calling Series or DataFrame) that returns valid output for indexing.

.loc typically takes two arguments separated by a comma: .loc[row_indexer, column_indexer]. You can use : to select all rows or all columns.

Let’s create a sample DataFrame:

import pandas as pd
import numpy as np
# Sample DataFrame with string index labels
index_labels = list('abcdefgh')
df = pd.DataFrame(np.random.randn(8, 4),
index=index_labels,
columns=['A', 'B', 'C', 'D'])
print("Sample DataFrame:")
print(df)

(Output will vary due to random numbers)

Example 1: Select a specific column (returns a Series)

Section titled “Example 1: Select a specific column (returns a Series)”
# Select all rows for column 'A'
print("\nColumn 'A':")
print(df.loc[:, 'A'])

Output (example):

Column 'A':
a -0.58
b 1.23
c -0.15
d 0.48
e -1.09
f 0.21
g 0.88
h -1.52
Name: A, dtype: float64

Example 2: Select multiple columns (returns a DataFrame)

Section titled “Example 2: Select multiple columns (returns a DataFrame)”
# Select all rows for columns 'A' and 'C'
print("\nColumns 'A' and 'C':")
print(df.loc[:, ['A', 'C']])

Output (example):

Columns 'A' and 'C':
A C
a -0.58 0.77
b 1.23 -0.31
c -0.15 1.50
d 0.48 -0.99
e -1.09 0.25
f 0.21 1.11
g 0.88 -0.40
h -1.52 0.65

Example 3: Select specific rows and columns

Section titled “Example 3: Select specific rows and columns”
# Select rows 'a', 'b', 'f', 'h' and columns 'A', 'C'
print("\nSpecific rows and columns:")
print(df.loc[['a', 'b', 'f', 'h'], ['A', 'C']])

Output (example):

Specific rows and columns:
A C
a -0.58 0.77
b 1.23 -0.31
f 0.21 1.11
h -1.52 0.65

Example 4: Select a range of rows (label slicing, inclusive)

Section titled “Example 4: Select a range of rows (label slicing, inclusive)”
# Select rows from 'c' to 'f' (inclusive) for all columns
print("\nRow slice 'c' to 'f':")
print(df.loc['c':'f'])

Output (example):

Row slice 'c' to 'f':
A B C D
c -0.15 0.91 1.50 -0.22
d 0.48 -0.55 -0.99 1.05
e -1.09 0.33 0.25 -0.78
f 0.21 -1.20 1.11 0.14

Example 5: Select based on a boolean condition

Section titled “Example 5: Select based on a boolean condition”
# Select rows where column 'A' is greater than 0
print("\nRows where A > 0:")
print(df.loc[df['A'] > 0])

Output (example):

Rows where A > 0:
A B C D
b 1.23 -0.67 -0.31 0.50
d 0.48 -0.55 -0.99 1.05
f 0.21 -1.20 1.11 0.14
g 0.88 0.10 -0.40 -0.95
# Get the value at row 'b', column 'B'
value = df.loc['b', 'B']
print(f"\nValue at ('b', 'B'): {value:.2f}") # Formatted output

Output (example):

Value at ('b', 'B'): -0.67

.iloc[] (Integer Position-based Selection)

Section titled “.iloc[] (Integer Position-based Selection)”

.iloc[] is used for selecting data by integer positions (from 0 to length-1). It works similarly to indexing lists or NumPy arrays.

  • An integer (e.g., 0 for the first row/column).
  • A list or array of integers (e.g., [0, 2, 3]).
  • A slice object with integers (e.g., 1:5). Unlike .loc label slicing, integer slicing with .iloc excludes the stop bound. So 1:5 selects positions 1, 2, 3, 4.
  • A boolean array (of the same length as the axis being indexed).
  • A callable function (similar to .loc).

.iloc also takes arguments as .iloc[row_indexer, column_indexer].

Let’s use a DataFrame with default integer index for clarity:

import pandas as pd
import numpy as np
df_int = pd.DataFrame(np.random.randn(8, 4), columns=['A', 'B', 'C', 'D'])
print("Sample DataFrame (integer index):")
print(df_int)

(Output will vary due to random numbers)

# Select rows from index 0 up to (but not including) 4
print("\nFirst 4 rows:")
print(df_int.iloc[0:4]) # Equivalent to df_int.iloc[:4]

Output (example):

First 4 rows:
A B C D
0 0.69 0.25 -1.27 -0.64
1 -0.68 0.89 -0.81 0.63
2 -0.78 -0.53 0.02 0.23
3 0.53 -1.28 0.82 -0.02

Example 2: Select rows and columns by integer position slice

Section titled “Example 2: Select rows and columns by integer position slice”
# Select rows 1, 2, 3, 4 and columns 2, 3
print("\nSlice [1:5, 2:4]:")
print(df_int.iloc[1:5, 2:4])

Output (example):

Slice [1:5, 2:4]:
C D
1 -0.81 0.63
2 0.02 0.23
3 0.82 -0.02
4 1.42 1.13

Example 3: Select specific rows and columns by integer lists

Section titled “Example 3: Select specific rows and columns by integer lists”
# Select rows 1, 3, 5 and columns 1, 3
print("\nRows [1, 3, 5], Columns [1, 3]:")
print(df_int.iloc[[1, 3, 5], [1, 3]])

Output (example):

Rows [1, 3, 5], Columns [1, 3]:
B D
1 0.89 0.63
3 -1.28 -0.02
5 -0.51 -0.51

Example 4: Select specific rows or columns using slices

Section titled “Example 4: Select specific rows or columns using slices”
# Select rows 1 and 2 for all columns
print("\nRows 1 and 2 (all columns):")
print(df_int.iloc[1:3, :])
# Select all rows for columns 1 and 2
print("\nColumns 1 and 2 (all rows):")
print(df_int.iloc[:, 1:3])

Output (example):

Rows 1 and 2 (all columns):
A B C D
1 -0.68 0.89 -0.81 0.63
2 -0.78 -0.53 0.02 0.23
Columns 1 and 2 (all rows):
B C
0 0.25 -1.27
1 0.89 -0.81
2 -0.53 0.02
3 -1.28 0.82
4 -0.46 1.42
5 -0.51 0.58
6 -1.20 0.09
7 -0.94 0.64

Example 5: Get a single scalar value by position

Section titled “Example 5: Get a single scalar value by position”
# Get the value at row index 0, column index 1
value_iloc = df_int.iloc[0, 1]
print(f"\nValue at [0, 1]: {value_iloc:.2f}")

Output (example):

Value at [0, 1]: 0.25

Basic Indexing ([]) and Attribute Access (.)

Section titled “Basic Indexing ([]) and Attribute Access (.)”

Pandas also supports simpler indexing methods, but use them with caution:

The [] operator works differently for Series and DataFrames:

  • For Series: [] primarily works like .loc[] (label-based) but falls back to .iloc[] if the label is not found and the input is an integer. Slicing usually works positionally.
  • For DataFrames:
    • Passing a single label or list of labels selects columns: df['A'] or df[['A', 'B']].
    • Passing a slice (:) selects rows: df[0:3] (integer position slice, exclusive of end). This row slicing behavior with [] can be confusing and is often discouraged in favor of .iloc.
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randn(8, 4), columns = ['A', 'B', 'C', 'D'])
# Select column 'A' using []
print("Column 'A' using []:")
print(df['A'])
# Select columns 'A', 'B' using []
print("\nColumns 'A', 'B' using []:")
print(df[['A', 'B']])
# Select first 3 rows using [] slice (integer position)
print("\nRows 0-2 using []:")
print(df[0:3])

Output (example):

Column 'A' using []:
0 -0.47
1 0.39
2 0.33
3 -1.05
4 -0.16
5 -0.32
6 0.56
7 -0.75
Name: A, dtype: float64
Columns 'A', 'B' using []:
A B
0 -0.47 -0.60
1 0.39 -0.94
2 0.33 0.09
3 -1.05 -0.01
4 -0.16 1.55
5 -0.32 -0.22
6 0.56 -0.31
7 -0.75 -0.37
Rows 0-2 using []:
A B C D
0 -0.47 -0.60 -0.11 0.92
1 0.39 -0.94 1.10 -0.71
2 0.33 0.09 -0.05 0.58

If a column name is a valid Python identifier (no spaces, doesn’t clash with DataFrame methods), you can access it as an attribute:

import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randn(8, 4), columns = ['A', 'B', 'C', 'D'])
# Select column 'A' using attribute access
print("Column 'A' using attribute access:")
print(df.A)

Output (example):

Column 'A' using attribute access:
0 -0.47
1 0.39
2 0.33
3 -1.05
4 -0.16
5 -0.32
6 0.56
7 -0.75
Name: A, dtype: float64

Warning: While convenient, attribute access (.) has limitations. It doesn’t work for column names that aren’t valid identifiers (e.g., ‘column name with spaces’) or that conflict with existing DataFrame methods (like df.count). Using [] (e.g., df['count']) or .loc is safer and more general for column selection.

For robust and clear data selection in Pandas:

  • Use .loc[] for selecting data based on index and column labels.
  • Use .iloc[] for selecting data based on integer positions.
  • Avoid the deprecated .ix[] indexer.
  • Use [] primarily for selecting columns by name(s). Be cautious when using [] for row slicing due to potential ambiguity.
  • Use attribute access (.) only for convenient interactive access to columns with valid identifier names, but prefer [] or .loc in reusable code.