Selecting Columns and Rows

Reviewed & published by Brayan K

Selecting in pandas means pulling out the specific columns or rows you care about from a DataFrame using square-bracket indexing — the everyday way you narrow a table down to just the data you need.

Part of the free Pandas course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Master single-column vs multi-column selection, row slicing, and the attribute-access shortcut — the foundation of every analysis you'll do.

What You'll Learn in This Lesson

1 Selecting a Single Column — df["col"]

To grab one column, put its name in square brackets after the DataFrame: df["age"]. This returns a Series — a one-dimensional labeled array, basically a single column with its index attached.

Let's build a small DataFrame from a dictionary and pull out one column:

import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Carol"],
    "age":  [25, 30, 35],
    "city": ["NYC", "LA", "Chicago"],
})

ages = df["age"]   # a Series
print(type(ages))  # <class 'pandas.core.series.Series'>
import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Carol"],
    "age":  [25, 30, 35],
    "city": ["NYC", "LA", "Chicago"],
})

# Select the "age" column -> returns a Series
ages = df["age"]
print(ages)

print(type(ages))   # <class 'pandas.core.series.Series'>
print(ages.mean())  # 30.0  -> you can call methods on a Series

# ✅ Expected output:
# 0    25
# 1    30
# 2    35
# Name: age, dtype: int64
# <class 'pandas.core.series.Series'>
# 30.0

2 Selecting Multiple Columns — df[["a", "b"]]

To select more than one column, pass a list of column names inside the brackets. Because you pass a list, you end up with double brackets: df[["name", "city"]]. The result is a DataFrame, not a Series.

subset = df[["name", "city"]]   # list inside brackets -> DataFrame
print(type(subset))             # <class 'pandas.core.frame.DataFrame'>

🧠 The bracket rule in one line:

You also control column order this way — the columns appear in the order you list them, which is handy for reshaping output.

import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Carol"],
    "age":  [25, 30, 35],
    "city": ["NYC", "LA", "Chicago"],
})

# Pass a LIST of columns -> get a DataFrame back
subset = df[["city", "name"]]   # note: reordered on purpose
print(subset)

print(type(subset))   # <class 'pandas.core.frame.DataFrame'>

# Double brackets keep DataFrame shape even for ONE column
print(df[["age"]])    # still a DataFrame, with the "age" header

# ✅ Expected output:
#       city   name
# 0      NYC  Alice
# 1       LA    Bob
# 2  Chicago  Carol
# <class 'pandas.core.frame.DataFrame'>
#    age
# 0   25
# 1   30
# 2   35

3 Selecting Rows by Slice — df[0:3]

Square brackets behave differently when you pass a slice. df[0:3] selects rows by position (0, 1, 2) — just like slicing a Python list. The end index is exclusive.

print(df[0:2])   # first two rows (positions 0 and 1)
print(df[1:])    # everything from row 1 onward
import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Carol", "Dan"],
    "age":  [25, 30, 35, 40],
})

# A SLICE selects rows by position (end is exclusive)
print(df[0:2])

print(df[2:])

# ✅ Expected output:
#     name  age
# 0  Alice   25
# 1    Bob   30
#     name  age
# 2  Carol   35
# 3    Dan   40

⚡ Shortcut: Attribute Access (df.col)

Pandas lets you read a column as if it were an attribute: df.age instead of df["age"]. It's a nice shortcut while exploring, but it has real limits.

import pandas as pd

df = pd.DataFrame({
    "product": ["Pen", "Notebook", "Eraser"],
    "price":   [1.50, 3.00, 0.75],
    "stock":   [120, 80, 200],
})

# 🎯 YOUR TURN:
# 1) Select just the "price" column as a Series.
# 2) Select "product" and "stock" together as a DataFrame.
# Replace the ___ blanks below.

price_series = df[___]            # hint: single brackets, one name
two_cols     = df[___]            # hint: a LIST of two names

print(price_series)
print(two_cols)

# Expected output:
# 0    1.50
# 1    3.00
# 2    0.75
# Name: price, dtype: float64
#     product  stock
# 0       Pen    120
# 1  Notebook     80
# 2    Eraser    200

📋 Quick Reference

GoalSyntaxReturns
One columndf["age"]Series
One column (keep frame)df[["age"]]DataFrame
Many columnsdf[["age", "city"]]DataFrame
Rows by positiondf[0:3]DataFrame
Attribute shortcutdf.ageSeries (limited)

🎯 Mini Challenge: Build and Slice a Table

Build a DataFrame of three movies, then practice every selection style you learned.

import pandas as pd

movies = pd.DataFrame({
    "title":  ["Inception", "Coco", "Parasite"],
    "year":   [2010, 2017, 2019],
    "rating": [8.8, 8.4, 8.5],
})

# 1) rating as a Series + average
print(movies["rating"].mean())     # 8.566666666666666

# 2) two columns as a DataFrame
print(movies[["title", "rating"]])

# 3) first two rows
print(movies[0:2])

# ✅ Expected output:
# 8.566666666666668
#        title  rating
# 0  Inception     8.8
# 1       Coco     8.4
# 2   Parasite     8.5
#        title  year  rating
# 0  Inception  2010     8.8
# 1       Coco  2017     8.4

❓ Frequently Asked Questions

Lesson complete — you can carve up any DataFrame!

You now know that single brackets give a Series, a list of names gives a DataFrame, and a slice selects rows. You also know when the handy df.col shortcut breaks down.

Practice quiz

What type does df['a'] return for a single column?

  • A Series
  • A DataFrame
  • A list
  • A dict

Answer: A Series. Single-bracket access of one column returns a Series.

What does df[['a']] (double brackets) return?

  • A Series
  • A DataFrame with one column
  • A NumPy array
  • A scalar value

Answer: A DataFrame with one column. Passing a list of names returns a DataFrame, even for one column.

How do you select columns 'a' and 'c' together?

  • df['a', 'c']
  • df.a.c
  • df[['a', 'c']]
  • df.cols('a','c')

Answer: df[['a', 'c']]. Pass a list of names: df[['a', 'c']].

Which accessor selects by LABEL?

  • df.iloc[...]
  • df.at_pos[...]
  • df.get[...]
  • df.loc[...]

Answer: df.loc[...]. loc is label-based; iloc is integer-position based.

Which accessor selects by INTEGER POSITION?

  • df.iloc[...]
  • df.loc[...]
  • df.label[...]
  • df.pos[...]

Answer: df.iloc[...]. iloc uses integer positions, e.g. df.iloc[0, 1].

What does df.loc[df['a'] > 1] do?

  • Sorts by column a
  • Keeps rows where column a exceeds 1
  • Drops column a
  • Renames a

Answer: Keeps rows where column a exceeds 1. A boolean mask inside loc filters rows by condition.

How do you select a single cell at row 'r', column 'b' by label?

  • df['r']['b']
  • df.cell('r','b')
  • df.loc['r', 'b']
  • df.get('r','b')

Answer: df.loc['r', 'b']. df.loc['r', 'b'] returns the scalar at that label intersection.

What does df.iloc[0:2] return?

  • The first column
  • Every other row
  • Nothing
  • The first two rows

Answer: The first two rows. Integer slicing 0:2 selects rows at positions 0 and 1.

Which is the recommended way to set a value to avoid the chained-assignment warning?

  • df.loc[mask, 'col'] = value
  • df[mask]['col'] = value
  • df['col'][mask] = value
  • df[mask, 'col'] = value

Answer: df.loc[mask, 'col'] = value. Use a single .loc call so pandas writes to the original frame.

What does df.filter(like='date') select?

  • Columns whose name contains 'date'
  • Rows equal to 'date'
  • The first date column only
  • Cells matching 'date'

Answer: Columns whose name contains 'date'. filter(like=...) keeps labels that contain the substring.

Continue this course

Frequently asked questions

What is the difference between df["col"] and df[["col"]]?

df["col"] returns a one-dimensional Series, while df[["col"]] returns a DataFrame with a single column. Use double brackets when you want to keep the DataFrame shape or select multiple columns at once.

Why does df[0:3] select rows but df[0] raises an error?

When you pass a slice like 0:3 to a DataFrame, pandas treats it as row selection by position. A bare integer like df[0] is interpreted as a column label lookup, so it fails unless a column is literally named 0.

Should I use df.column attribute access?

Attribute access (df.age) is convenient for quick exploration, but it only works when the column name has no spaces, does not clash with a DataFrame method, and is a valid Python identifier. Bracket access df["age"] always works and is the safer habit.