The structural survey: what your columns are, rather than what your values are.
Which of these columns can I actually use? That is the first honest question after reading a file, and it is not about the numbers. It is about the container: how many rows arrived, what each column is called, what type Pandas decided it is, and how many of its cells are empty. info prints that and nothing else.
It is the twin of describe, and the pair is argued for on the describe page, along with the memory story. This page is the rest of what info knows.
Official documentation: DataFrame.info and Series.info.
The arguments that earn their keep
df.info() # the whole survey
df.info(show_counts=True) # insist on the non-null column
df.info(verbose=True) # insist on per-column detail
df.info(memory_usage='deep') # measure strings, not pointers
df.info(buf=open('schema.txt', 'w')) # send it somewhere other than the screen
A worked example, on real data
OurAirports publishes every airfield, heliport and seaplane base it knows about, and rebuilds the file every day, so your counts will differ a little from mine:
import pandas as pd
url = 'https://davidmegginson.github.io/ourairports-data/airports.csv'
df = pd.read_csv(url)
df.info()
<class 'pandas.DataFrame'>
RangeIndex: 85944 entries, 0 to 85943
Data columns (total 19 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 id 85944 non-null int64
1 ident 85944 non-null str
2 type 85944 non-null str
3 name 85944 non-null str
4 latitude_deg 85944 non-null float64
5 longitude_deg 85944 non-null float64
6 elevation_ft 71042 non-null float64
7 continent 46206 non-null str
8 iso_country 85641 non-null str
9 iso_region 85944 non-null str
10 municipality 81225 non-null str
11 scheduled_service 85944 non-null str
12 icao_code 10467 non-null str
13 iata_code 9054 non-null str
14 gps_code 44397 non-null str
15 local_code 36033 non-null str
16 home_link 4752 non-null str
17 wikipedia_link 16734 non-null str
18 keywords 21667 non-null str
dtypes: float64(3), int64(1), str(15)
memory usage: 19.1 MB
That middle column is a map of what you are allowed to ask. Anything keyed on iata_code covers 9,054 of 85,944 rows, so a join against airline data will drop 89 percent of this file and will not mention it. elevation_ft has 14,902 gaps, so a mean elevation is a mean over the airports that bothered to report one.
And continent is missing 39,738 times, which is 46 percent of the world's airfields and cannot possibly be true. It is not:
df.loc[df['continent'].isna(), 'iso_country'].value_counts().head(3)
iso_country
US 32494
CA 3359
MX 2696
Name: count, dtype: int64
North America. Its code in this file is NA, which is on the list of strings read_csv reads as missing. Load it with keep_default_na=False, na_values=[''] and the column is complete: 39,738 rows marked NA, no gaps at all. One number in the info block was the only sign.
The first two lines are the part nothing else prints. <class 'pandas.DataFrame'> says you have a frame and not a series, which matters when a long chain has quietly collapsed to one column. RangeIndex: 85944 entries, 0 to 85943 says the index is still the row numbers Pandas invented at load time. Run df.set_index('ident').info() and that line becomes Index: 85944 entries, 00A to ZZ-0004 — the type changed, and so did the labels .loc[] accepts.
The trap: the counts vanish on big data
show_counts defaults to counting, but only up to a point. Above pd.options.display.max_info_rows, which is 1,690,785, Pandas decides the walk is too expensive and drops the column — silently, with no note, no ellipsis, nothing:
big = pd.DataFrame({'a': range(1_700_000), 'b': 1.0})
big.info()
<class 'pandas.DataFrame'>
RangeIndex: 1700000 entries, 0 to 1699999
Data columns (total 2 columns):
# Column Dtype
--- ------ -----
0 a int64
1 b float64
dtypes: float64(1), int64(1)
memory usage: 25.9 MB
The output still looks complete, and it is missing the one thing you opened it for — on exactly the files where the gaps are hardest to spot by eye. Pass show_counts=True and the column comes back. Width throws the same switch: past 100 columns you lose the per-column block entirely, and it takes verbose=True, show_counts=True together to get it back.
Three mistakes people make
Expecting a value back. info returns None. It prints. A chain ending in .info() ends in nothing at all, and df.info().shape raises AttributeError. When you want these facts as data rather than as text, they are three ordinary calls: df.dtypes, df.count(), and df.memory_usage(deep=True). When you want the text itself — to log it, or to diff today's schema against last week's — pass buf=.
Reading a non-null count as a row count. They agree only for columns with no gaps. len(df) is the row count, and the interesting thing about the block above is that a dozen different numbers appear in one column.
Assuming the memory line is undercounting. It used to be, always. With PyArrow installed, df.info() and df.info(memory_usage='deep') both report 19.1 MB here, because Arrow-backed strings know their own size. Pass deep anyway: it is either free or it is the only correct answer. The per-column version is on memory_usage.
Where it shows up in Bamboo Weekly
Bamboo Weekly #2: Egg prices is info doing its actual job, in one sentence: the Date column "contains text, as you'll see if you run either df.info() or df.dtypes." Everything that follows — pd.to_datetime, then the date arithmetic — starts from that one glance. Free to read.
Bamboo Weekly #10: Oil prices walks through the memory line in the old world and the new one: 822.6+ KB with the plus sign, 4.7 MB once memory_usage='deep' counts the Python strings, and 728.3 KB reading the same file through PyArrow, where "we don't need to do anything special to get the true memory usage." Also free.
Bamboo Weekly #140: Stack Overflow survey is the width limit in the wild. With 170 columns the printed output is the collapsed form — Columns: 170 entries, ResponseId to JobSat, no per-column detail at all. What it does show is 356.8 MB, which astype('category') then cuts to 66.4 MB.
Practice it
Work through an info exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/info/
Go deeper
describe covers the values, memory_usage breaks the last line down per column, and select_dtypes acts on the dtype column rather than reading it. When the dtypes are wrong the fix usually belongs in read_csv rather than in astype afterward, and when the gaps are the story, that is isna.
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
.describe()— for what the values are, rather than what the columns are.memory_usage()— for the per-column breakdown rather than one total