Filtering Groups (HAVING)

Python · GroupBy & SQL-Style Analytics

Theory

SQL's HAVING filters groups after aggregation, for example 'cities with total sales above 2000'. pandas has two ways. The first mirrors SQL: aggregate, then filter the result with a boolean mask - totals = df.groupby('city')['amount'].sum(); totals[totals > 2000]. With named aggregation you can filter on any summary column: summary.query('orders >= 2 and total > 2000').

The second is groupby().filter(func), which keeps or drops the original rows of whole groups: df.groupby('city').filter(lambda g: g['amount'].sum() > 2000) returns every order from cities whose total exceeds 2000, in their original order. The function receives each group as a small DataFrame and must return a single True or False. An equivalent vectorised version uses transform: df[df.groupby('city')['amount'].transform('sum') > 2000], which is usually faster on large data. WHERE-style row filters go before groupby; HAVING-style filters go after.

Why we use it

Many questions are about qualifying groups - customers with more than 3 orders, products with total sales above a target, cities with at least 2 stores. HAVING-style filtering answers them without manual loops.

When to use it

Filter the aggregated result when you want the summary table. Use groupby().filter or a transform mask when you want the original detail rows of qualifying groups. Apply row-level conditions with a normal mask before grouping.

How it works

  1. Rows of df
  2. Optional WHERE: filter rows with a mask
  3. groupby('city') and compute the group measure
  4. Group measure passes the condition? Yes: Keep the group. No: Drop the group.
  5. Need detail rows? Yes: groupby().filter(...) or transform mask. No: Use the filtered summary.
  6. Qualifying groups only

Syntax

g = df.groupby('key')['col'].sum(); g[g > x]
df.groupby('key').agg(...).query('total > x')
df.groupby('key').filter(lambda g: cond)
df[df.groupby('key')['col'].transform('sum') > x]

Example

totals = df.groupby('city')['amount'].sum()
print(totals[totals > 2000])
print(df.groupby('city').filter(lambda g: len(g) >= 2))

Key points

Python topics

Getting Started

Variables & Data Types

Operators

Strings

Lists

Tuples

Sets

Dictionaries

Control Flow

Loops

Functions & OOP

Working with Data

Tooling & Errors

File Handling

Exception Handling

NumPy Library

Pandas Basics

Data Cleaning

Combining DataFrames (SQL Joins & Unions)

GroupBy & SQL-Style Analytics

Reshaping & Workflow

Data Visualization

Machine Learning (Scikit-Learn)

Advanced Python & AI

T
Tech Town
Log InSign Up Free
Home SQL Python DSA Power BI Snowflake Databricks Spark Gen AI Practice Problems Contest Blog

Python for Data Analysts

From basics to business insights — the complete step-by-step Python roadmap. Click any topic in the sidebar to open it right here.

{{ sectionCount }}
Sections
{{ topicCount }}
Topics
Free
Forever
Hands-on practice Practice Python Online 141 Python questions for data analysts — strings, records, dates, statistics and regex — with hidden tests and instant verdicts. Start practising →
Section {{ sec.num }}

{{ sec.label }}

Python  /  {{ current.sectionLabel }}  /  {{ current.name }}

{{ current.name }}

Theory

{{ current.def }}

Why we use it

{{ rich.why }}

When to use it

{{ rich.when }}

Diagram

{{ rich.dg.caption }}

{{ tb.title }}
{{ c.v }}
{{ rich.dg.op }}
➜
{{ rich.dg.output.title }}
{{ c.v }}
{{ nd.label }}
{{ nd.sub }}
{{ nd.label }}
{{ nd.sub }}
{{ rich.dg.left.title }}
{{ it }}
{{ rich.dg.right.title }}
{{ it }}
Flowchart — how it works
{{ nd.text }}
{{ nd.text }}
◇ {{ nd.text }}
Yes → {{ nd.yes }}
No → {{ nd.no }}
Syntax
{{ current.syntax }}
Example
{{ current.example }}
▶ Try It Live — runs real Python 3.12 in your browser (first run loads the engine)
{{ liveOutput }}
{{ liveError }}
Key Points — click to check off as you learn
  • {{ pt.mark }} {{ pt.text }}

Share this topic