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.
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 bytesThe 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: NoneNon-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() == 3Expected 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: int64verbose=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 bytesWhen 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 bytesUse 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 bytesDefault 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 == beforeExpected 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 bytesmemory_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 bytesUse 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: 354Pass 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 bytesA 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() == 4Expected 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 0Normalize 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() == 2Expected 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: int64Use 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"] == 50Expected 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.0info() 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.0An 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.emptyExpected 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: FalseWhy 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.
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.
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"] == 0Expected 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]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.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.