Skip to content

pandas select_dtypes

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.

See it on real data