Unit 6 • Lesson 1

Filtering with Masks

Overview

The same idea as numpy, applied to whole rows.

Intermediate 20–25 min

What You Will Learn in This Lesson

By the end of this lesson, you will be able to:

  • Filter a table with a condition on one of its columns.
  • Combine conditions with & and |, bracketed.
  • Say what happens to rows whose value is missing.
  • Notice how many rows a filter dropped.

Why This Matters

Filtering is the operation you will reach for more than any other, and it has exactly two traps: and does not work, and rows with NaN fall out of both a condition and its opposite. Both are worth meeting on purpose here rather than in an analysis whose totals do not add up.

Step 1: A condition on a column selects rows

The mask is one True or False per row, and putting it in the brackets keeps the rows it marked — the whole row, not just the column you tested.

1

Mask, then select

Exactly the numpy idea from unit 3, with the difference that the unit of selection is a row.

import pandas as pd

df = pd.DataFrame({"name": ["Ada", "Grace", "Alan"], "score": [92, 88, 79]})
mask = df["score"] > 85
print(mask)
print(df[mask])
2

Count what you dropped

A filter that leaves three rows out of five hundred is either the answer or a bug, and the only way to tell is to look at the number.

import pandas as pd

df = pd.DataFrame({"score": [92, 88, 79, 60]})
kept = df[df["score"] > 85]
print(f"{len(kept)} of {len(df)} rows kept")

Mini Practice #1: How many survived

Try It Yourself

Filter to scores above 70 and print how many rows you kept out of how many you started with.

Press Run to see output

Step 2: Combining conditions

Use & and | rather than and and or, and bracket each condition.

1

Two conditions

The brackets are required because & binds more tightly than the comparison operators.

import pandas as pd

df = pd.DataFrame({"score": [92, 88, 79, 60], "city": ["L", "Y", "L", "Y"]})
print(df[(df["score"] > 70) & (df["city"] == "L")])
2

Negating one

~ flips a mask. It is the readable way to say "everything except", and it is easier to check than rewriting the condition backwards.

import pandas as pd

df = pd.DataFrame({"city": ["L", "Y", "L"]})
print(df[~(df["city"] == "L")])
3

Where the missing rows go

A comparison with NaN is never True, so a row with a missing value falls out of the filter and out of its opposite. The two halves do not add up to the whole table, and nothing says so.

import pandas as pd
import numpy as np

df = pd.DataFrame({"score": [92, np.nan, 60]})
high = df[df["score"] > 80]
low = df[df["score"] <= 80]
print(len(high), "+", len(low), "=", len(high) + len(low), "of", len(df))

If the two halves have to account for every row, filter on .notna() first and report the missing ones separately, rather than letting them vanish.

Mini Practice #2: The rows that vanish

Try It Yourself

Run this and check whether the two halves add up to the whole table. Then fix it so they do.

Press Run to see output

Step 3: When to Use It

Keeping rows by a condition

A boolean Series selects whole rows, with every column coming along.

df[(df["score"] >= 60) & (df["subject"] == "maths")]

Counting what matched

mask.sum() counts the rows a condition keeps, which is the check after every filter.

When Not to Use It

Do not assume a filter and its opposite cover every row. Rows with NaN fail both, so count the missing ones separately.

End-of-Lesson Exercises

Exercise 1: The names that passed

Write passing_names(df, mark) returning a list of names whose score is at least mark, in the order they appear.

Mask the table, then take the name column of what survives and turn it into a list with .tolist().

Press Run to try it, then Check my work to mark it.

Exercise 2: Account for every row

Write split_counts(df, mark) returning a three-element list of [at or above the mark, below it, missing a score]. The three must add up to the number of rows.

Count the missing ones with .isna().sum() first, then the two comparisons. A comparison is never True for NaN, so those rows fall out of both and have to be counted separately.

Press Run to try it, then Check my work to mark it.