Pandas info(): Inspect Missing Values, Dtypes and Memory

Use df.info() to inspect a DataFrame before cleaning or analysis. Check its row and column structure, non-missing values, data types and memory estimate. Then inspect questionable values before deciding what to convert, fill or remove.

Print a DataFrame summary 🔝

Call df.info() directly. The summary describes the index, columns, non-missing counts, dtypes and estimated memory use. It does not calculate means or percentiles; use describe() for those statistics.

import pandas as pd
import io

def student_data():
    return pd.DataFrame({
        "NAME": ["Ravi", "Raju", "Alex"],
        "ID": [1, 2, 3],
        "MATH": [30, 40, 50],
        "ENGLISH": [20, 30, 40]
    })

df = student_data()
df.info()

Expected output

<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)
memory usage: 228.0 bytes

Why does print(df.info()) display None? 🔝

The method prints a report and returns None. Wrapping it in print() adds that return value after the report. Store the report through buf, rather than assigning the method result as if it were a DataFrame.

df = student_data()
result = df.info()
print("Return value:", result)
assert result is None
# print(df.info()) also prints an additional None.

Expected output

<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)
memory usage: 228.0 bytes
Return value: None

Read non-null counts alongside row counts 🔝

Non-null means non-missing, rather than nonzero. Zero scores are valid observations. Here MATH has two known values in three rows. An empty string is also non-missing; clean placeholder text explicitly when appropriate.

df = student_data()
df.loc[1, "MATH"] = float("nan")
df.loc[2, "ENGLISH"] = 0
df.info(show_counts=True)
print("Rows:", len(df))
print("Missing per column:")
print(df.isna().sum())
assert df["MATH"].count() == 2
assert df["ENGLISH"].count() == 3

Expected output

<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   NAME     3 non-null      str    
 1   ID       3 non-null      int64  
 2   MATH     2 non-null      float64
 3   ENGLISH  3 non-null      int64  
dtypes: float64(1), int64(2), str(1)
memory usage: 228.0 bytes
Rows: 3
Missing per column:
NAME       0
ID         0
MATH       1
ENGLISH    0
dtype: int64

Choose full or compact output with verbose 🔝

verbose=False omits the individual column details. verbose=True requests them. Compact output is useful for wide tables but does not show which specific columns contain missing values.

df = student_data()
print("Compact:")
df.info(verbose=False)
print("Full:")
df.info(verbose=True, show_counts=True)

Expected output

Compact:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Columns: 4 entries, NAME to ENGLISH
dtypes: int64(3), str(1)
memory usage: 228.0 bytes
Full:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)
memory usage: 228.0 bytes

Control truncation with max_cols 🔝

When verbose is left at its default, more columns than max_cols causes truncation. Four columns with max_cols=3 produces compact output; max_cols=4 allows full output. Explicit verbose=True can request full details regardless of that automatic threshold.

df = student_data()
print("Four columns, limit three:")
df.info(max_cols=3)
print("Four columns, limit four:")
df.info(max_cols=4)
print("Explicit full output:")
df.info(max_cols=3, verbose=True)

Expected output

Four columns, limit three:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Columns: 4 entries, NAME to ENGLISH
dtypes: int64(3), str(1)
memory usage: 228.0 bytes
Four columns, limit four:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)
memory usage: 228.0 bytes
Explicit full output:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Dtype
---  ------   -----
 0   NAME     str  
 1   ID       int64
 2   MATH     int64
 3   ENGLISH  int64
dtypes: int64(3), str(1)
memory usage: 228.0 bytes

Show or hide non-missing counts 🔝

Use the current parameter show_counts. Older examples may use null_counts, which is not supported by the current API. Counting values can cost time on a large table, so disable it when only labels and dtypes are needed.

df = student_data()
print("Counts enabled:")
df.info(verbose=True, show_counts=True)
print("Counts disabled:")
df.info(verbose=True, show_counts=False)

Expected output

Counts enabled:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)
memory usage: 228.0 bytes
Counts disabled:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Dtype
---  ------   -----
 0   NAME     str  
 1   ID       int64
 2   MATH     int64
 3   ENGLISH  int64
dtypes: int64(3), str(1)
memory usage: 228.0 bytes

Inspect display options without changing global settings 🔝

Default verbosity and count display depend on the max_info_columns and max_info_rows options. Use option_context() for a temporary change; the previous settings are restored after the block. See Pandas display options.

df = student_data()
print("Column threshold:", pd.options.display.max_info_columns)
print("Row threshold:", pd.options.display.max_info_rows)
before = pd.options.display.max_info_columns
with pd.option_context("display.max_info_columns", 2):
    df.info()
assert pd.options.display.max_info_columns == before

Expected output

Column threshold: 100
Row threshold: 1690785
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Columns: 4 entries, NAME to ENGLISH
dtypes: int64(3), str(1)
memory usage: 228.0 bytes

Show, hide or inspect memory more deeply 🔝

memory_usage=True shows an estimate, False suppresses it, and "deep" inspects object contents more thoroughly. The report includes the index. It is not the total memory used by the Python process. Deep inspection takes extra work and exact byte counts vary by dtype backend and environment.

df = student_data()
print("Estimate:")
df.info(memory_usage=True)
print("Hidden:")
df.info(memory_usage=False)
print("Deep inspection:")
df.info(memory_usage="deep")

Expected output

Estimate:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)
memory usage: 228.0 bytes
Hidden:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)Deep inspection:
<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)
memory usage: 363.0 bytes

Get memory figures as data rather than printed text 🔝

Use memory_usage() to obtain a Series of bytes by column and index. For object strings, deep inspection can report more than the shallow estimate. Do not parse the text produced by info() when a structured API already provides the measurements.

texts = pd.DataFrame({"description": pd.Series(["Notebook for classroom use", "Blue pen for office use", "Notebook for classroom use"], dtype="object")})
shallow = texts.memory_usage(index=True, deep=False)
deep = texts.memory_usage(index=True, deep=True)
print("Shallow bytes:")
print(shallow)
print("Deep bytes:")
print(deep)
print("Total deep bytes:", deep.sum())
assert deep.sum() >= shallow.sum()

Expected output

Shallow bytes:
Index          132
description     24
dtype: int64
Deep bytes:
Index          132
description    222
dtype: int64
Total deep bytes: 354

Capture the report with buf and StringIO 🔝

Pass a writable buffer to collect the text. Use its getvalue() method after info() has written the report. You can display it, save it or include it in a diagnostic log.

df = student_data()
buffer = io.StringIO()
df.info(buf=buffer, verbose=True, show_counts=True, memory_usage="deep")
report_text = buffer.getvalue()
print(report_text.rstrip())
assert "MATH" in report_text
# Optional file save in Colab:
# with open("student_info.txt", "w", encoding="utf-8") as file:
#     file.write(report_text)

Expected output

<class 'pandas.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype
---  ------   --------------  -----
 0   NAME     3 non-null      str  
 1   ID       3 non-null      int64
 2   MATH     3 non-null      int64
 3   ENGLISH  3 non-null      int64
dtypes: int64(3), str(1)
memory usage: 363.0 bytes

Practical inspection: a messy sales extract 🔝

A non-null count cannot detect invalid numeric text or a missing-value placeholder such as an empty string. Inspect actual values and convert deliberately. The sample is synthetic; it includes blank customers, invalid dates and one invalid amount.

def sales_data():
    return pd.DataFrame({
        "order_id": [101, 102, 103, 104],
        "customer": ["Alex", "", None, "Ravi"],
        "order_date": ["2026-01-01", "2026-01-02", "bad-date", None],
        "amount": ["12.50", "20", "unknown", "0"]
    })

sales = sales_data()
sales.info(show_counts=True)
print(sales)
assert sales["amount"].count() == 4

Expected output

<class 'pandas.DataFrame'>
RangeIndex: 4 entries, 0 to 3
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype
---  ------      --------------  -----
 0   order_id    4 non-null      int64
 1   customer    3 non-null      str  
 2   order_date  3 non-null      str  
 3   amount      4 non-null      str  
dtypes: int64(1), str(3)
memory usage: 260.0 bytes
   order_id customer  order_date   amount
0       101     Alex  2026-01-01    12.50
1       102           2026-01-02       20
2       103      NaN    bad-date  unknown
3       104     Ravi         NaN        0

Compare the summary before and after conversion 🔝

Normalize blank customer strings to missing, parse dates with an explicit format and convert amounts to numbers. Coercion changes invalid values to missing; preserve the source table to identify what failed. Explore to_datetime() and astype() for further conversion examples.

raw = sales_data()
clean = raw.copy()
clean["customer"] = clean["customer"].astype("string").str.strip().replace("", pd.NA)
clean["order_date"] = pd.to_datetime(clean["order_date"], format="%Y-%m-%d", errors="coerce")
clean["amount"] = pd.to_numeric(clean["amount"], errors="coerce")
clean.info(verbose=True, show_counts=True)
print("Invalid non-missing amount inputs:")
print(raw.loc[raw["amount"].notna() & clean["amount"].isna(), ["order_id", "amount"]])
print("Missing after cleaning:")
print(clean.isna().sum())
assert clean["amount"].count() == 3
assert clean["customer"].isna().sum() == 2

Expected output

<class 'pandas.DataFrame'>
RangeIndex: 4 entries, 0 to 3
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   order_id    4 non-null      int64         
 1   customer    2 non-null      string        
 2   order_date  2 non-null      datetime64[us]
 3   amount      3 non-null      float64       
dtypes: datetime64[us](1), float64(1), int64(1), string(1)
memory usage: 260.0 bytes
Invalid non-missing amount inputs:
   order_id   amount
2       103  unknown
Missing after cleaning:
order_id      0
customer      2
order_date    2
amount        1
dtype: int64

Build a structured missing-value report 🔝

Use count(), isna() and dtypes to create a reusable audit table. This can be exported without parsing info() output. A high missing percentage is a prompt to investigate; it is not an automatic reason to delete a field or fill it with zero. See fillna() and dropna().

audit = pd.DataFrame({
    "dtype": clean.dtypes.astype(str),
    "non_missing": clean.count(),
    "missing": clean.isna().sum()
})
audit["missing_percent"] = clean.isna().mean().mul(100).round(1)
print(audit)
assert audit.loc["amount", "missing"] == 1
assert audit.loc["customer", "missing_percent"] == 50

Expected output

                     dtype  non_missing  missing  missing_percent
order_id             int64            4        0              0.0
customer            string            2        2             50.0
order_date  datetime64[us]            2        2             50.0
amount             float64            3        1             25.0

Choose info(), describe(), dtypes or shape 🔝

info() prints a structural overview. dtypes returns types by column, shape gives row and column counts, and describe() returns descriptive statistics. Use columns for header names and head() to view actual rows.

df = student_data()
print("Shape:", df.shape)
print("Dtypes:")
print(df.dtypes)
print("Numeric statistics:")
print(df[["MATH", "ENGLISH"]].describe())

Expected output

Shape: (3, 4)
Dtypes:
NAME         str
ID         int64
MATH       int64
ENGLISH    int64
dtype: object
Numeric statistics:
       MATH  ENGLISH
count   3.0      3.0
mean   40.0     30.0
std    10.0     10.0
min    30.0     20.0
25%    35.0     25.0
50%    40.0     30.0
75%    45.0     35.0
max    50.0     40.0

Inspect an empty table with an explicit schema 🔝

An empty DataFrame can still have columns and declared dtypes. A table with rows containing only missing values is not empty. Check shape and missing counts rather than interpreting empty as meaning all values are missing.

empty = pd.DataFrame({"order_id": pd.Series(dtype="int64"), "amount": pd.Series(dtype="float64")})
empty.info(show_counts=True)
all_missing = pd.DataFrame({"amount": [float("nan")]})
print("Zero-row table is empty:", empty.empty)
print("One missing row is empty:", all_missing.empty)
assert empty.empty and not all_missing.empty

Expected output

<class 'pandas.DataFrame'>
RangeIndex: 0 entries
Data columns (total 2 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   order_id  0 non-null      int64  
 1   amount    0 non-null      float64
dtypes: float64(1), int64(1)
memory usage: 132.0 bytes
Zero-row table is empty: True
One missing row is empty: False

Common questions and pitfalls 🔝

Why are some counts absent? Request verbose=True and show_counts=True. Why did conversion create more missing values? Inspect invalid inputs when using errors="coerce". Does info() fix the data? No; it reports structure. Does non-null mean valid? No; blank strings and invalid text may still count. Why do memory figures differ? Dtypes, string backends, index storage and platform details affect the values. Use the same environment for meaningful comparisons.

Exercise: inspect quantities before calculating 🔝

Create quantity values "2", "bad", None and "0", paired with order IDs 1 to 4. Convert quantity to numeric with invalid inputs coerced to missing. Print a full info report, calculate missing quantities and identify the order with invalid non-missing input. Expect two missing values; order 2 has the invalid text.

Exercise solution 🔝

Missing input and invalid input both become missing after conversion, but only the invalid non-missing input is a parsing failure. Zero remains a valid quantity.

raw_orders = pd.DataFrame({"order_id": [1, 2, 3, 4], "quantity": ["2", "bad", None, "0"]})
orders = raw_orders.copy()
orders["quantity"] = pd.to_numeric(orders["quantity"], errors="coerce")
orders.info(verbose=True, show_counts=True)
failed = raw_orders["quantity"].notna() & orders["quantity"].isna()
print("Missing quantities:", orders["quantity"].isna().sum())
print("Invalid input order IDs:", raw_orders.loc[failed, "order_id"].tolist())
assert orders["quantity"].isna().sum() == 2
assert raw_orders.loc[failed, "order_id"].tolist() == [2]
assert orders.loc[3, "quantity"] == 0

Expected output

<class 'pandas.DataFrame'>
RangeIndex: 4 entries, 0 to 3
Data columns (total 2 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   order_id  4 non-null      int64  
 1   quantity  2 non-null      float64
dtypes: float64(1), int64(1)
memory usage: 196.0 bytes
Missing quantities: 2
Invalid input order IDs: [2]

Practice in Google Colab 🔝

Open in Google Colab View on GitHub
Run the examples in order, change missing values and compare the summary before and after conversion. Save your own copy to keep edits. All sample tables are included in the notebook.

Continue with data exploration and analysis, data cleaning, dtypes and describe().

Reference: Pandas DataFrame.info documentation.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer