Use df.dtypes to inspect the data type of each Pandas column. It is an attribute, not a function. Check types before arithmetic, date operations or export, then validate values before converting them.
Use df.dtypes, without parentheses. This attribute returns a Series indexed by column names. The sample includes seven kinds of column data; these are examples, not an exhaustive list of Pandas types. Explicit date formatting prevents day/month ambiguity.
import pandas as pd
from pandas.api.types import is_numeric_dtype, is_integer_dtype, is_datetime64_any_dtype
def student_data():
df = pd.DataFrame({
"NAME": ["Ravi", "Raju", "Alex", "Ron", "King", "Jack"],
"ID": [1, 2, 3, 4, 5, 6],
"MATH": [80, 40, 70, 70, 70, 30],
"Avg_mark": [45.5, 48.09, 50.12, 55.1, 50.6, 55.6],
"dt_start": ["1/1/2020", "2/1/2020", "5/1/2020", "11/7/2020", "15/8/2020", "31/12/2020"],
"Exam": [True, False, True, True, False, False],
"dt": pd.Series([pd.Timedelta(days=i) for i in range(6)]),
"grade": ["a", "c", "b", "b", "b", "c"]
})
df["grade"] = df["grade"].astype("category")
df["dt_start"] = pd.to_datetime(df["dt_start"], format="%d/%m/%Y")
return df
df = student_data()
print(df.dtypes)
assert is_datetime64_any_dtype(df["dt_start"].dtype)Expected output
NAME str
ID int64
MATH int64
Avg_mark float64
dt_start datetime64[us]
Exam bool
dt timedelta64[us]
grade category
dtype: objectA DataFrame has dtypes for multiple columns; a Series has a single dtype. Prefer df["Avg_mark"].dtype for one column. The trailing dtype: object printed by df.dtypes describes the returned summary Series, not every source column.
df = student_data()
print("Average marks:", df["Avg_mark"].dtype)
print("Series dtypes alias:", df["Avg_mark"].dtypes)
print("Summary Series dtype:", df.dtypes.dtype)
assert str(df["Avg_mark"].dtype) == "float64"Expected output
Average marks: float64
Series dtypes alias: float64
Summary Series dtype: objectint64 stores integers without missing values; nullable Int64 supports missing integers. float64 stores floating-point numbers; Float64 is its nullable extension counterpart. bool and nullable boolean store logical values. Text may use object, str or string depending on construction and version. Datetime and timedelta dtypes encode units; category stores a set of labels. A dtype controls storage and operations, not whether a value is correct for your business rule.
Numeric 2 plus numeric 2 gives 4. Text "2" plus text "2" concatenates to "22". Object dtype can contain strings, numbers or a mixture, so it does not guarantee concatenation. Inspect values as well as the dtype before choosing a conversion.
numbers = pd.Series([2, 2], dtype="int64")
text = pd.Series(["2", "2"], dtype="object")
objects_with_numbers = pd.Series([2, 2], dtype="object")
print("Numeric:", (numbers + numbers).tolist())
print("Text:", (text + text).tolist())
print("Object containing integers:", (objects_with_numbers + objects_with_numbers).tolist())
assert (text + text).tolist() == ["22", "22"]Expected output
Numeric: [4, 4]
Text: ['22', '22']
Object containing integers: [4, 4]A column of mixed Python values may remain object. Counting the Python type names reveals what it contains, but does not define a cleaning policy. Decide whether numeric-looking text should be parsed and whether invalid values should be rejected or marked missing.
mixed = pd.Series([10, "20", None, "bad"], dtype="object")
print("Storage dtype:", mixed.dtype)
print("Python value types:")
print(mixed.map(lambda value: type(value).__name__).value_counts().sort_index())
parsed = pd.to_numeric(mixed, errors="coerce")
print("Parsed values:", parsed.tolist())
print("Parsed dtype:", parsed.dtype)Expected output
Storage dtype: object
Python value types:
NoneType 1
int 1
str 2
Name: count, dtype: int64
Parsed values: [10.0, 20.0, nan, nan]
Parsed dtype: float64A conventional numeric Series containing an integer and NaN is commonly inferred as float64. Explicit nullable Int64 stores integer values with pd.NA. The capital I matters. See missing-value handling before filling unknown quantities with zero.
inferred = pd.Series([1, None, 3])
nullable = pd.Series([1, None, 3], dtype="Int64")
print("Inferred:", inferred.dtype)
print("Nullable:", nullable.dtype)
print(nullable)
assert is_integer_dtype(nullable.dtype)
assert nullable.isna().sum() == 1Expected output
Inferred: float64
Nullable: Int64
0 1
1 <NA>
2 3
dtype: Int64astype("string") expresses a text column with missing values. Avoid converting missing inputs into literal "None" or "nan" when those strings have no meaning in the dataset. With inferred text, Pandas versions may show object or str; an explicit dtype communicates your intent.
labels = pd.Series(["Notebook", None, "Pen"], dtype="string")
print(labels)
print("Dtype:", labels.dtype)
print("Missing:", labels.isna().sum())
mask = labels.str.contains("note", case=False, regex=False, na=False)
print("Matching labels:", labels.loc[mask].tolist())Expected output
0 Notebook
1 <NA>
2 Pen
dtype: string
Dtype: string
Missing: 1
Matching labels: ['Notebook']Non-empty strings are truthy in Python, so casting the text "False" to bool can produce True. Map a defined vocabulary and use nullable boolean for unknown values. Review unmapped values before accepting the result.
raw = pd.Series(["True", "False", None, "unknown"], dtype="string")
mapped = raw.str.lower().map({"true": True, "false": False}).astype("boolean")
print(mapped)
print("Unmapped non-missing:", raw.loc[raw.notna() & mapped.isna()].tolist())
assert mapped.iloc[0] == True and mapped.iloc[1] == FalseExpected output
0 True
1 False
2 <NA>
3 <NA>
dtype: boolean
Unmapped non-missing: ['unknown']Dates stored as text need parsing before date arithmetic. An explicit format makes interpretation reproducible. Datetime resolution can vary; test the dtype family rather than assuming every environment uses nanoseconds. Explore to_datetime() and to_timedelta().
df = student_data()
print(df[["dt_start", "dt"]].dtypes)
print(df["dt_start"].dt.strftime("%Y-%m-%d").tolist())
duration = pd.to_timedelta(["1 day", "2 days", None])
print("Duration dtype:", duration.dtype)
assert is_datetime64_any_dtype(df["dt_start"].dtype)Expected output
dt_start datetime64[us]
dt timedelta64[us]
dtype: object
['2020-01-01', '2020-01-02', '2020-01-05', '2020-07-11', '2020-08-15', '2020-12-31']
Duration dtype: timedelta64[us]Category is useful for repeated labels and can represent a meaningful ordering. It is not a universal memory improvement for high-cardinality data. Specify category order explicitly when comparisons should reflect an ordered scale. See categorical data.
df = student_data()
print("Grade dtype:", df["grade"].dtype)
print("Categories:", df["grade"].cat.categories.tolist())
ordered_type = pd.CategoricalDtype(categories=["c", "b", "a"], ordered=True)
grade = df["grade"].astype(ordered_type)
print("Above b:", grade.loc[grade > "b"].tolist())Expected output
Grade dtype: category
Categories: ['a', 'b', 'c']
Above b: ['a']select_dtypes() selects dtype families. Numeric columns include ID here, although IDs are not marks. Select the intended measurement columns before aggregating rather than assuming every numeric column belongs in the total.
df = student_data()
print("Numeric:", df.select_dtypes(include="number").columns.tolist())
print("Boolean:", df.select_dtypes(include="bool").columns.tolist())
print("Categorical:", df.select_dtypes(include="category").columns.tolist())
print("Score totals:")
print(df[["MATH", "Avg_mark"]].sum())Expected output
Numeric: ['ID', 'MATH', 'Avg_mark', 'dt']
Boolean: ['Exam']
Categorical: ['grade']
Score totals:
MATH 360.00
Avg_mark 305.01
dtype: float64Use astype() when values are compatible with a chosen dtype; use to_numeric() to parse numeric text with an explicit error policy. convert_dtypes() chooses suitable nullable dtypes from existing values, but does not replace a deliberate parsing step for numeric text.
raw = pd.DataFrame({"quantity": [1, None, 3], "price_text": ["12.50", "bad", "3.00"]})
inferred_nullable = raw.convert_dtypes()
print("After nullable inference:")
print(inferred_nullable.dtypes)
clean = raw.copy()
clean["quantity"] = clean["quantity"].astype("Int64")
clean["price"] = pd.to_numeric(clean["price_text"], errors="coerce").astype("Float64")
print(clean[["quantity", "price"]])
assert clean["price"].isna().sum() == 1Expected output
After nullable inference:
quantity Int64
price_text string
dtype: object
quantity price
0 1 12.5
1 <NA> <NA>
2 3 3.0Keep product codes as strings to preserve leading zeros. Parse quantities and dates, then inspect failures before calculating revenue. Fractional quantities should be handled according to the domain; this sample allows only whole units. The synthetic source remains available for auditing.
raw_sales = pd.DataFrame({
"product_code": ["0012", "0045", "0070", "0081"],
"quantity": ["2", "bad", None, "0"],
"unit_price": ["12.50", "3.00", "5.00", "8.00"],
"order_date": ["2026-01-01", "2026-01-02", "invalid", "2026-01-04"]
})
sales = raw_sales.copy()
sales["product_code"] = sales["product_code"].astype("string")
quantity = pd.to_numeric(sales["quantity"], errors="coerce")
assert quantity.dropna().mod(1).eq(0).all()
sales["quantity"] = quantity.astype("Int64")
sales["unit_price"] = pd.to_numeric(sales["unit_price"], errors="coerce").astype("Float64")
sales["order_date"] = pd.to_datetime(sales["order_date"], format="%Y-%m-%d", errors="coerce")
sales["revenue"] = sales["quantity"] * sales["unit_price"]
print(sales.dtypes)
print(sales[["product_code", "quantity", "revenue"]])
print("Known revenue subtotal:", sales["revenue"].sum(min_count=1))
assert sales.loc[0, "product_code"] == "0012"
assert sales["revenue"].isna().sum() == 2Expected output
product_code string
quantity Int64
unit_price Float64
order_date datetime64[us]
revenue Float64
dtype: object
product_code quantity revenue
0 0012 2 25.0
1 0045 <NA> <NA>
2 0070 <NA> <NA>
3 0081 0 0.0
Known revenue subtotal: 25.0Use dtype-family checks instead of brittle string comparisons for inferred types. Also validate required fields, missing quantities and value ranges: the dtype alone cannot establish completeness or accuracy. info() provides a useful companion overview.
assert is_integer_dtype(sales["quantity"].dtype)
assert is_numeric_dtype(sales["unit_price"].dtype)
assert is_datetime64_any_dtype(sales["order_date"].dtype)
assert sales["quantity"].dropna().ge(0).all()
invalid_quantity = raw_sales["quantity"].notna() & sales["quantity"].isna()
print("Invalid quantity codes:", raw_sales.loc[invalid_quantity, "product_code"].tolist())
print("Rows with unknown revenue:", sales["revenue"].isna().sum())
schema = sales.dtypes.astype(str).rename("dtype").to_frame()
print(schema)Expected output
Invalid quantity codes: ['0045']
Rows with unknown revenue: 2
dtype
product_code string
quantity Int64
unit_price Float64
order_date datetime64[us]
revenue Float64An inferred integer identifier can lose leading zeros. Declare identifier columns as string while importing. The embedded CSV uses the same approach as a file; see read_csv(). CSV stores text and does not retain a Pandas dtype schema for future reads.
import io
csv_text = "product_code,quantity\n0012,2\n0045,3\n"
loaded = pd.read_csv(io.StringIO(csv_text), dtype={"product_code": "string", "quantity": "Int64"})
print(loaded)
print(loaded.dtypes)
assert loaded.loc[0, "product_code"] == "0012"Expected output
product_code quantity
0 0012 2
1 0045 3
product_code string
quantity Int64
dtype: objectWhy does dtypes() fail? dtypes is an attribute; remove parentheses. Why do text columns differ from an older example? String inference and storage backends vary across Pandas versions. Can dtype tell whether a date is valid? It identifies storage; check parsing failures and domain rules separately. Why does integer conversion fail? Look for missing values, invalid text or fractional numbers. Does object always mean text? No; inspect actual values. For more inspection tools, visit data exploration and analysis and data cleaning.
Create codes "0001", "0002" and "0003" with quantity text "4", "oops" and None. Preserve codes as nullable strings, parse quantities as nullable Int64 and report invalid non-missing quantity inputs. Expect one invalid input and two missing quantities.
Retain the raw quantity column to distinguish invalid text from an originally missing value.
raw = pd.DataFrame({"code": ["0001", "0002", "0003"], "quantity": ["4", "oops", None]})
result = raw.copy()
result["code"] = result["code"].astype("string")
result["quantity"] = pd.to_numeric(result["quantity"], errors="coerce").astype("Int64")
failed = raw["quantity"].notna() & result["quantity"].isna()
print(result)
print(result.dtypes)
print("Invalid codes:", raw.loc[failed, "code"].tolist())
assert raw.loc[failed, "code"].tolist() == ["0002"]
assert result["quantity"].isna().sum() == 2Expected output
code quantity
0 0001 4
1 0002 <NA>
2 0003 <NA>
code string
quantity Int64
dtype: object
Invalid codes: ['0002']Open in Google Colab View on GitHub
Run the examples in order, change the source values and inspect dtypes before and after conversion. Save your own copy to keep edits. All sample tables are included in the notebook.
Continue with astype(), info(), column labels and date differences.
Reference: Pandas DataFrame.dtypes 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.