Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Pandas›Selecting Columns and Rows

Selecting and Filtering

Selecting Columns and Rows

Selecting data is the foundational operation in Pandas for isolating relevant features and observations from large datasets. Mastering these selection methods allows you to transform raw inputs into focused subsets required for analysis and modeling. You will reach for these tools whenever you need to explore specific variables or perform conditional filtering to clean your data.

Selecting Columns via Bracket Notation

The most straightforward way to interact with a DataFrame is by using bracket notation to select specific columns, which returns a Series. When you pass a string to the DataFrame, Pandas interprets it as a label lookup within the column index. Understanding why this works requires recognizing that a DataFrame is essentially a collection of Series objects sharing a common index. By requesting a single column, you are extracting one of these Series objects. This is the idiomatic way to perform simple lookups, though it is limited to column access because rows are not indexed by labels in this syntax. If you need multiple columns at once, you can pass a list of labels inside the brackets, which prompts Pandas to return a new DataFrame containing only those specified columns, preserving the original row index while restructuring the column set for your immediate needs.

import pandas as pd

# Create a sample DataFrame of sales data
df = pd.DataFrame({'product': ['A', 'B'], 'sales': [100, 200], 'region': ['North', 'South']})

# Select a single column as a Series
sales_series = df['sales']

# Select multiple columns as a new DataFrame
subset = df[['product', 'region']]

Accessing by Label with .loc

The .loc accessor is label-based, meaning you provide the names of rows or columns to retrieve data. This is fundamentally different from index-based selection because it relies on the actual values found in the DataFrame index and column headers. The syntax follows a strict 'row, column' pattern, where you can select a single label, a list of labels, or a slice of labels. A critical nuance of .loc is that slice endpoints are inclusive, which is unusual in many programming contexts but consistent with the idea of labeling data ranges. By using .loc, you decouple your code from the integer position of the data, ensuring that if your underlying dataset is sorted or reordered, your selection logic remains robust and anchored to the specific identifying labels you intend to capture for your analysis tasks.

df = df.set_index('product') # Set index to use labels

# Select row 'A' and columns 'sales' and 'region'
result = df.loc['A', ['sales', 'region']]

# Slice from 'A' to 'B' (inclusive)
full_range = df.loc['A':'B', :]

Accessing by Position with .iloc

When the order of data matters more than the descriptive labels, .iloc provides integer-based positional selection. This works identically to standard list slicing in Python, where the first element is at index zero and the end-bound of a slice is exclusive. The reasoning behind using .iloc is to gain complete control over the physical storage layout of the DataFrame. Because datasets often come from external files where columns might be added or renamed, relying on positional index can prevent brittle code during automated data processing pipelines. By addressing rows and columns by their integer coordinates, you guarantee that you are pulling exactly the nth row or column regardless of what label it happens to hold, which is essential for mathematical operations that require rigid structure and predefined dimensionality when processing tensors or arrays.

# Select the first row and all columns
first_row = df.iloc[0, :]

# Select first two rows and first two columns
subset = df.iloc[0:2, 0:2]

Boolean Masking for Filtering

Boolean masking is the primary method for filtering data based on conditional logic. When you apply a comparison operator to a column, such as df['sales'] > 150, Pandas creates a Series of True and False values. When you pass this Series back into the DataFrame using square brackets, Pandas keeps only the rows corresponding to True. This mechanism works because the boolean series is aligned with the DataFrame's index. The power of this approach lies in its readability and the ability to combine multiple conditions using logical operators like the ampersand for 'and' or the vertical bar for 'or'. It is important to wrap each individual condition in parentheses to ensure correct evaluation order, as the bitwise operators used in Pandas have different precedence rules than standard comparison operators, which is a common source of errors for beginners.

# Filter rows where sales are greater than 150
high_sales = df[df['sales'] > 150]

# Filter with multiple conditions
filtered = df[(df['sales'] > 50) & (df['region'] == 'South')]

Advanced Selection and .query

The .query() method offers a syntax-heavy alternative for filtering that allows you to write conditional statements as strings, which can improve readability for complex operations. Internally, the query string is evaluated against the DataFrame's columns, allowing you to reference them by name without repeating the variable identifier. This works by utilizing the Python engine to perform the comparison on the data in a cleaner, more descriptive fashion. While slightly slower than direct boolean masking for massive datasets, it is highly useful when your conditions become deep and nested, as it reduces the amount of character boilerplate required to build logic. It is especially effective when used within chains of operations, keeping your data transformation pipeline clean and centered on the logic being performed rather than the repetitive structural access patterns required by standard bracket-based filtering syntax.

# Use query to filter data concisely
high_sales = df.query('sales > 150 and region == "South"')

# Using a variable inside a query
min_val = 100
filtered = df.query('sales > @min_val')

Key points

  • Bracket notation is the most common way to select columns by their labels.
  • The .loc accessor is used for selection based on index or column labels.
  • The .iloc accessor retrieves data based on integer positional coordinates.
  • Slices in .loc are inclusive of the end boundary, unlike standard list slicing.
  • Boolean masking allows you to filter rows based on conditional expressions.
  • Multiple conditions in boolean masking must be wrapped in parentheses and combined with bitwise operators.
  • The .query() method provides a cleaner string-based interface for filtering rows.
  • Using @ within a query allows you to reference external variables easily during filtering.

Common mistakes

  • Mistake: Using bracket notation df['col1', 'col2'] to select multiple columns. Why it's wrong: Pandas interprets this as looking for a single column with a tuple name. Fix: Use a list of column names: df[['col1', 'col2']].
  • Mistake: Mixing up loc and iloc. Why it's wrong: loc uses labels, while iloc uses integer positions. Fix: Use loc for label-based selection (including the endpoint) and iloc for positional-based slicing.
  • Mistake: Attempting to modify a slice without using .loc. Why it's wrong: It often triggers a SettingWithCopyWarning because it's unclear if you are modifying a view or a copy. Fix: Use .loc[row_indexer, col_indexer] = value to modify the DataFrame directly.
  • Mistake: Thinking df[0] selects the first row. Why it's wrong: In Pandas, the bracket operator on a DataFrame directly accesses columns. Fix: Use df.iloc[0] to select the first row by position.
  • Mistake: Forgetting that .loc is inclusive of the stop index. Why it's wrong: Unlike standard Python slicing where the stop index is exclusive, .loc includes the stop label. Fix: Remember that df.loc['a':'c'] includes rows 'a', 'b', and 'c'.

Interview questions

How do you select a single column from a Pandas DataFrame?

To select a single column, you can use the bracket notation with the column name as a string, such as df['column_name']. Alternatively, you can use dot notation, like df.column_name, though this is less robust if your column name has spaces or matches a method name. Selecting a column returns a Series object. This approach is fundamental because it allows for rapid data exploration and allows you to perform vectorized operations on entire datasets efficiently without needing explicit loops.

What is the primary difference between using .loc and .iloc for row selection?

The .loc accessor is label-based, meaning you select data using the explicit index labels defined in the DataFrame. Conversely, .iloc is integer-based, selecting data strictly by the numeric position regardless of the label. You use .loc when you know the specific index identifier, whereas .iloc is superior when you need to grab the first, last, or Nth row of a dataset regardless of how the index is named or structured, ensuring your code remains predictable.

How do you select multiple columns at once in Pandas?

To select multiple columns, you pass a list of column names inside the brackets, such as df[['col1', 'col2']]. This operation returns a new DataFrame containing only the specified columns in the order provided. This is a critical workflow when you want to subset your data for a machine learning model or when you need to perform analysis on a specific group of features while ignoring extraneous data to save memory and improve computational speed.

How can you select rows based on a specific condition?

You select rows based on conditions using Boolean masking. By applying a comparison operator to a column, such as df[df['age'] > 30], Pandas generates a series of True/False values. When this mask is passed back into the DataFrame, it returns only the rows where the condition is True. This is an essential filtering technique because it allows you to clean data or focus on subsets of interest without manually iterating through individual records.

Compare the use of .loc for filtering versus using standard bracket boolean indexing.

While df[mask] and df.loc[mask] often produce identical results for simple row filtering, .loc is generally preferred in professional workflows because it allows you to simultaneously filter rows and select specific columns in one step, such as df.loc[df['val'] > 0, ['col_A', 'col_B']]. Furthermore, .loc is explicitly designed to avoid the 'SettingWithCopy' warning that often arises when modifying subsets, making your code significantly more robust, readable, and less prone to unexpected side effects during data transformation.

Explain how to perform complex row selection using multiple boolean conditions.

To perform complex selection, you must combine multiple conditions using bitwise operators like '&' for 'AND' and '|' for 'OR', while wrapping each condition in parentheses. For example, df[(df['col1'] > 10) & (df['col2'] == 'Target')]. The parentheses are strictly required because bitwise operators have higher precedence than comparison operators in Python. This technique allows for sophisticated data partitioning, enabling analysts to isolate precise edge cases or specific cohorts within vast datasets that would be otherwise impossible to identify.

All Pandas interview questions →

Check yourself

1. Given a DataFrame 'df', which code correctly selects the 'Name' column and returns a Series?

  • A.df[['Name']]
  • B.df.loc[:, 'Name']
  • C.df.iloc[:, 'Name']
  • D.df['Name',]
Show answer

B. df.loc[:, 'Name']
df.loc[:, 'Name'] correctly uses labels to select all rows in the 'Name' column. df[['Name']] returns a DataFrame, not a Series. df.iloc[:, 'Name'] fails because iloc requires integer positions for columns. df['Name',] is invalid syntax.

2. If you want to select rows by integer position 5 through 10 (excluding 10), which syntax is correct?

  • A.df.iloc[5:10]
  • B.df.loc[5:10]
  • C.df[5:10]
  • D.df.iloc[5:11]
Show answer

A. df.iloc[5:10]
df.iloc[5:10] follows standard Python integer slicing rules where the start is inclusive and the stop is exclusive. df.loc uses labels, not positions. df[5:10] is ambiguous for row selection and df.iloc[5:11] would include index 10.

3. What happens when you execute df[['A', 'B']]?

  • A.A Series containing columns A and B is returned.
  • B.A DataFrame containing columns A and B is returned.
  • C.An error is raised because double brackets are not supported.
  • D.Only column A is returned.
Show answer

B. A DataFrame containing columns A and B is returned.
Passing a list of column names inside brackets returns a new DataFrame containing only those columns. Option 1 is wrong because a list of columns results in a DataFrame, not a Series. Options 3 and 4 are factually incorrect regarding Pandas syntax.

4. When using df.loc['row1':'row3', 'col1'], what is the result?

  • A.Only row1 and row3 are selected.
  • B.Rows 'row1', 'row2', and 'row3' are included if they exist.
  • C.Only row1, row2, and row3 are selected, and the result is a DataFrame.
  • D.An error occurs because labels cannot be sliced.
Show answer

B. Rows 'row1', 'row2', and 'row3' are included if they exist.
Label-based slicing with .loc is inclusive of the end point, so it retrieves rows from 'row1' through 'row3'. It returns a Series because only one column is selected. Option 1 ignores 'row2'. Option 3 is wrong because it returns a Series. Option 4 is false because label slicing is valid.

5. How do you select rows where the 'Age' column is greater than 30 using boolean indexing?

  • A.df.loc[df['Age'] > 30]
  • B.df.iloc[df['Age'] > 30]
  • C.df[df.Age > 30]
  • D.Both 1 and 3 are correct.
Show answer

D. Both 1 and 3 are correct.
Both df.loc[df['Age'] > 30] and df[df.Age > 30] are valid and common ways to filter rows based on a condition. df.iloc cannot handle boolean Series directly as it expects integer positions. Therefore, both 1 and 3 are correct ways to achieve the result.

Take the full Pandas quiz →

← PreviousWriting and Exporting DataNext →loc vs iloc — Label vs Position Indexing

Pandas

34 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app