Skip to main content

Python Pandas: How to Fix "IndexError: single positional indexer is out-of-bounds"

The IndexError: single positional indexer is out-of-bounds is a fundamental error encountered when working with Pandas DataFrames (or Series) and attempting to access data using integer-based positional indexing via .iloc. This error unequivocally means you've tried to retrieve an element, row, or column using an integer position that simply doesn't exist within the dimensions of your DataFrame.

This guide will thoroughly explore why this IndexError occurs, demonstrate common scenarios that trigger it, and equip you with the knowledge to correctly use positional indexing, check DataFrame dimensions, and handle potential out-of-bounds access gracefully.

Understanding the "Out-of-Bounds" IndexError in Pandas

This IndexError specifically relates to positional indexing, which in Pandas is primarily handled by the .iloc accessor. It means the integer position you've supplied to .iloc is outside the valid range of indices for the rows or columns of your DataFrame.

Zero-Based Positional Indexing with .iloc

It's crucial to remember that Python and Pandas use zero-based indexing. This means:

  • For a DataFrame with N rows, the valid row indices are 0, 1, ..., N-1.
  • For a DataFrame with M columns, the valid column indices are 0, 1, ..., M-1.

Attempting to access df.iloc[N] for rows or df.iloc[:, M] for columns will result in the "out-of-bounds" error.

Let's use a sample DataFrame:

import pandas as pd

df = pd.DataFrame({
'employee_name': ['Alice', 'Bob', 'Charlie', 'Diana'], # 4 rows
'department': ['HR', 'IT', 'Sales', 'HR'], # 3 columns
'salary': [70000, 85000, 92000, 72000]
})

print("Original DataFrame:")
print(df)

Output:

Original DataFrame:
employee_name department salary
0 Alice HR 70000
1 Bob IT 85000
2 Charlie Sales 92000
3 Diana HR 72000

This DataFrame has 4 rows (indices 0, 1, 2, 3) and 3 columns (indices 0, 1, 2).

Common Scenarios Causing the Error

Accessing a Non-Existent Column Index with .iloc

If you try to access a column by a position that is equal to or greater than the number of columns.

import pandas as pd

df = pd.DataFrame({
'employee_name': ['Alice', 'Bob', 'Charlie', 'Diana'], # 4 rows
'department': ['HR', 'IT', 'Sales', 'HR'], # 3 columns
'salary': [70000, 85000, 92000, 72000]
})

try:
# ⛔️ Incorrect: DataFrame has 3 columns (indices 0, 1, 2). Accessing column at index 3 is out-of-bounds.
non_existent_column = df.iloc[:, 3]
print(non_existent_column)
except IndexError as e:
print(f"Error accessing column: {e}")
# Output: Error accessing column: single positional indexer is out-of-bounds

Output:

Error accessing column: single positional indexer is out-of-bounds

Accessing a Non-Existent Row Index with .iloc

If you try to access a row by a position that is equal to or greater than the number of rows.

import pandas as pd

df = pd.DataFrame({
'employee_name': ['Alice', 'Bob', 'Charlie', 'Diana'], # 4 rows
'department': ['HR', 'IT', 'Sales', 'HR'], # 3 columns
'salary': [70000, 85000, 92000, 72000]
})


try:
# ⛔️ Incorrect: DataFrame has 4 rows (indices 0, 1, 2, 3). Accessing row at index 4 is out-of-bounds.
# Using df.iloc[[4]] to request a DataFrame slice containing that row.
non_existent_row_slice = df.iloc[[4]]
print(non_existent_row_slice)
except IndexError as e:
print(f"Error accessing row slice: {e}")
# Output: Error accessing row slice: positional indexers are out-of-bounds

try:
# Accessing a single row
non_existent_single_row = df.iloc[4]
print(non_existent_single_row)
except IndexError as e:
print(f"Error accessing single row: {e}")
# Output: Error accessing single row: single positional indexer is out-of-bounds

Output:

Error accessing row slice: positional indexers are out-of-bounds
Error accessing single row: single positional indexer is out-of-bounds

Preventing the Error: Verifying DataFrame Dimensions

Before attempting positional indexing, especially if the indices are dynamically generated or uncertain, it's wise to check the DataFrame's dimensions.

import pandas as pd

df = pd.DataFrame({
'employee_name': ['Alice', 'Bob', 'Charlie', 'Diana'], # 4 rows
'department': ['HR', 'IT', 'Sales', 'HR'], # 3 columns
'salary': [70000, 85000, 92000, 72000]
})

num_rows = len(df) # Or len(df.index)
num_cols = len(df.columns) # Or df.shape[1]

print(f"DataFrame has {num_rows} rows (valid indices 0 to {num_rows - 1}).")
# Output: DataFrame has 4 rows (valid indices 0 to 3).
print(f"DataFrame has {num_cols} columns (valid indices 0 to {num_cols - 1}).")
# Output: DataFrame has 3 columns (valid indices 0 to 2).

# Using df.shape
rows, cols = df.shape
print(f"Using df.shape: Rows={rows}, Columns={cols}")
# Output: Using df.shape: Rows=4, Columns=3

Output:

DataFrame has 4 rows (valid indices 0 to 3).
DataFrame has 3 columns (valid indices 0 to 2).
Using df.shape: Rows=4, Columns=3

Knowing these bounds can help you avoid specifying an out-of-range index.

Correct Positional Indexing Techniques with .iloc

Accessing Existing Rows and Columns by Position

Ensure your integer index is within the valid range 0 to length-1.

import pandas as pd

df = pd.DataFrame({
'employee_name': ['Alice', 'Bob', 'Charlie', 'Diana'], # 4 rows
'department': ['HR', 'IT', 'Sales', 'HR'], # 3 columns
'salary': [70000, 85000, 92000, 72000]
})


# ✅ Access the first column (index 0)
first_column = df.iloc[:, 0]
print("First column ('employee_name'):")
print(first_column)
print()

# ✅ Access the third row (index 2) as a DataFrame slice
third_row_slice = df.iloc[[2]]
print("Third row (index 2) as a slice:")
print(third_row_slice)
print()

# ✅ Access the third row (index 2) as a Series
third_row_series = df.iloc[2]
print("Third row (index 2) as a Series:")
print(third_row_series)
print()

# ✅ Access a specific cell: row index 1, column index 2 (salary of Bob)
bob_salary = df.iloc[1, 2]
print(f"Bob's salary (cell at [1, 2]): {bob_salary}") # Output: Bob's salary (cell at [1, 2]): 85000

Output:

First column ('employee_name'):
0 Alice
1 Bob
2 Charlie
3 Diana
Name: employee_name, dtype: object

Third row (index 2) as a slice:
employee_name department salary
2 Charlie Sales 92000

Third row (index 2) as a Series:
employee_name Charlie
department Sales
salary 92000
Name: 2, dtype: object

Bob's salary (cell at [1, 2]): 85000

Accessing the Last Row or Column Using Negative Indexing

.iloc supports negative indexing, similar to Python lists, where -1 refers to the last element, -2 to the second last, and so on.

import pandas as pd

df = pd.DataFrame({
'employee_name': ['Alice', 'Bob', 'Charlie', 'Diana'], # 4 rows
'department': ['HR', 'IT', 'Sales', 'HR'], # 3 columns
'salary': [70000, 85000, 92000, 72000]
})

# ✅ Access the last column ('salary')
last_column = df.iloc[:, -1]
print("Last column using -1:")
print(last_column)
print()

# ✅ Access the last row
last_row_slice = df.iloc[[-1]] # As a DataFrame slice
print("Last row using -1 (as slice):")
print(last_row_slice)

Output:

Last column using -1:
0 70000
1 85000
2 92000
3 72000
Name: salary, dtype: int64

Last row using -1 (as slice):
employee_name department salary
3 Diana HR 72000

Accessing the Last Row or Column Using Shape/Length

You can also use the DataFrame's dimensions to calculate the index of the last element.

import pandas as pd

df = pd.DataFrame({
'employee_name': ['Alice', 'Bob', 'Charlie', 'Diana'], # 4 rows
'department': ['HR', 'IT', 'Sales', 'HR'], # 3 columns
'salary': [70000, 85000, 92000, 72000]
})


num_rows = df.shape[0]
num_cols = df.shape[1]

# ✅ Access the last column (index num_cols - 1)
last_col_calculated = df.iloc[:, num_cols - 1]
print("Last column using shape:")
print(last_col_calculated)
print()

# ✅ Access the last row (index num_rows - 1)
last_row_calculated_slice = df.iloc[[num_rows - 1]]
print("Last row using shape (as slice):")
print(last_row_calculated_slice)

Output:

Last column using shape:
0 70000
1 85000
2 92000
3 72000
Name: salary, dtype: int64

Last row using shape (as slice):
employee_name department salary
3 Diana HR 72000

This is equivalent to using -1 but can be more explicit in some contexts.

Handling the IndexError Programmatically (Optional)

If you anticipate that an index might be out of bounds in some situations (e.g., user input, dynamic calculations), you can use a try-except block to catch the IndexError.

import pandas as pd

df = pd.DataFrame({
'employee_name': ['Alice', 'Bob', 'Charlie', 'Diana'], # 4 rows
'department': ['HR', 'IT', 'Sales', 'HR'], # 3 columns
'salary': [70000, 85000, 92000, 72000]
})

column_to_access = 5 # This index is out of bounds for df

try:
selected_column = df.iloc[:, column_to_access]
print(f"Selected column {column_to_access}:\n{selected_column}")
except IndexError:
print(f"Column index {column_to_access} is out of bounds for this DataFrame.")
# Handle the error: e.g., use a default value, log a message, or skip.
# Output:
# Column index 5 is out of bounds for this DataFrame.

Output:

Column index 5 is out of bounds for this DataFrame.

A Note on Empty DataFrames

If your DataFrame is empty (has 0 rows or 0 columns), any attempt to access data using .iloc with a non-empty slice or any integer index (even 0) will result in an IndexError.

import pandas as pd

df_empty_rows = pd.DataFrame(columns=['A', 'B']) # 0 rows, 2 columns
df_empty_cols = pd.DataFrame(index=[0, 1]) # 2 rows, 0 columns
df_fully_empty = pd.DataFrame() # 0 rows, 0 columns

try:
val = df_empty_rows.iloc[0, 0] # Accessing row 0 of an empty-row DataFrame
except IndexError as e:
print(f"Error with df_empty_rows: {e}") # single positional indexer is out-of-bounds

try:
val = df_empty_cols.iloc[0, 0] # Accessing col 0 of an empty-col DataFrame
except IndexError as e:
print(f"Error with df_empty_cols: {e}") # single positional indexer is out-of-bounds

Output:

Error with df_empty_rows: index 0 is out of bounds for axis 0 with size 0
Error with df_empty_cols: index 0 is out of bounds for axis 0 with size 0

Always check if a DataFrame is empty (df.empty) before attempting positional access if its emptiness is a possibility.

Conclusion

The IndexError: single positional indexer is out-of-bounds in Pandas is a direct consequence of attempting to access data via .iloc with an integer position that falls outside the valid zero-based range of your DataFrame's rows or columns. To resolve and prevent this error:

  1. Understand Zero-Based Indexing: The first element is at index 0.
  2. Verify Dimensions: Before indexing, check df.shape, len(df.index), or len(df.columns) to know the valid range of indices.
  3. Use Correct Indices: Ensure the integer positions you use with .iloc are within 0 to N-1 (for N elements).
  4. Leverage Negative Indexing: Use .iloc[:, -1] or .iloc[-1] to reliably access the last column or row.

By adhering to these principles, you can confidently use positional indexing with .iloc and avoid out-of-bounds errors.