Choose columns by what they hold, not by what they are called.
How do you sum every numeric column in a file you have never opened? Naming them is out — you do not know the names yet, and next month's download may carry different ones. select_dtypes answers by type instead: hand it a dtype and it hands you back a new data frame containing only the columns that match.
The dtypes it matches against are the ones info prints. The point of this method is to act on that column of the report rather than reading it.
Official documentation: DataFrame.select_dtypes.
The arguments that earn their keep
df.select_dtypes('number') # include is the first positional argument
df.select_dtypes(include='str') # text, in the Pandas 3 spelling
df.select_dtypes(include=['int', 'bool']) # a list, when one family is not enough
df.select_dtypes(exclude='number') # everything that is not a number
df.select_dtypes('number').columns # often what you really wanted
There are only two arguments, and both take a dtype or a list of them. 'number' is the useful shorthand: it covers every integer and float width at once, and it covers nothing else — not bool, not datetime64, and not a column of numbers you have already converted to a category.
include='str' is the Pandas 3 spelling for text. include=object still selects string columns, but it now raises a Pandas4Warning asking you to say which you meant, so write 'str' in new code. And you must pass one of the two: df.select_dtypes() with neither raises ValueError: at least one of include or exclude must be nonempty.
A worked example, on real data
Every motor vehicle collision reported to the NYPD, from the city's open data portal. This is the first 50,000 logged since the start of 2025:
import pandas as pd
url = ('https://data.cityofnewyork.us/resource/h9gi-nx95.csv'
'?$where=crash_date>="2025-01-01"'
'&$order=collision_id&$limit=50000')
df = pd.read_csv(url)
df.select_dtypes('number').columns
Index(['zip_code', 'latitude', 'longitude', 'number_of_persons_injured',
'number_of_persons_killed', 'number_of_pedestrians_injured',
'number_of_pedestrians_killed', 'number_of_cyclist_injured',
'number_of_cyclist_killed', 'number_of_motorist_injured',
'number_of_motorist_killed', 'collision_id'],
dtype='str')
Twelve numeric columns out of 29, found without typing a single name. Now watch what happens if you trust all twelve:
df.select_dtypes('number').sum().astype('int64')
zip_code 434518854
latitude 1966541
longitude -3569795
number_of_persons_injured 28590
number_of_persons_killed 137
number_of_pedestrians_injured 5103
number_of_pedestrians_killed 71
number_of_cyclist_injured 2959
number_of_cyclist_killed 12
number_of_motorist_injured 19701
number_of_motorist_killed 50
collision_id 240436059232
dtype: int64
Eight of those numbers are findings. Four are nonsense: the ZIP codes add up to 434,518,854, the collision ID numbers to 240 billion, and the coordinate columns to a latitude and a longitude that describe no place on earth. All four are valid arithmetic on genuinely numeric columns — a ZIP code and a database key are labels that happen to be spelled with digits. select_dtypes reports storage, which is all it promises. Deciding which of those columns is a measurement is still your job.
Narrow it down and the aggregation is worth reading:
(df
.select_dtypes('number')
.filter(like='killed')
.groupby(df['borough'])
.sum()
.rename(columns=lambda col: col.removeprefix('number_of_').removesuffix('_killed'))
.T)
borough BRONX BROOKLYN MANHATTAN QUEENS STATEN ISLAND
persons 12 45 17 20 3
pedestrians 8 31 8 12 2
cyclist 1 0 6 1 0
motorist 3 13 2 6 1
Note df['borough'] passed to groupby as a series. The frame coming down the chain no longer has a borough column — select_dtypes dropped it, being text — and grouping by an outside series that shares the index is how you get it back.
Three mistakes people make
Reading 'number' as "the measurements." It means int and float, nothing more. Identifiers, ZIP codes, years, latitudes and record numbers all live in numeric columns, and every one of them will be summed and averaged along with your real data without complaint.
Categorizing first, selecting second. Converting text columns to categories to save memory is good practice, and it takes them out of 'str'. A numeric column converted to a category leaves 'number' too. Whatever order your pipeline runs, select_dtypes('category') is the only thing that will find them afterward.
Assigning into the result. select_dtypes returns a new frame, not a view. df.select_dtypes('number')['zip_code'] = 0 raises a ChainedAssignmentError warning and changes nothing at all. Take the names instead and assign through the original: df[df.select_dtypes('number').columns].
Where it shows up in Bamboo Weekly
Bamboo Weekly #25: Entrepreneurship is the shortest possible case, and free to read. df.corr() fails on a frame with text columns in it, so the whole fix is one call: df.select_dtypes('float64').corr().
Bamboo Weekly #64: Coal power runs it the other way around, looping over df.select_dtypes(exclude='float64').columns and calling astype('category') on each one. That is the memory idiom in three lines, and also free.
Bamboo Weekly #97: Drones uses it as the first link of a chain that answers a different question: .select_dtypes(include='number').isna().sum().sort_values(ascending=False) ranks the numeric columns by how many values are missing, before deciding which of them are worth interpolating.
Practice it
Work through a select_dtypes exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/select-dtypes/
Go deeper
info is where you read the dtypes this method acts on, astype is how you change them, and memory_usage is why you would bother. For picking columns by name rather than by type, that is filter, and describe takes the same include= vocabulary — see describe.
More Pandas videos on Python and Pandas with Reuven Lerner.
Bamboo Weekly is the practice. If you want the structured version — full courses with downloadable Jupyter notebooks, plus live Pandas office hours when you get stuck — that is LernerPython+Data. A paid Bamboo Weekly subscription is included with it.
Related methods
.astype()— which is how a column got the dtype you are now selecting on.filter()— when you want columns by name rather than by dtype