Pandas DataFrame dtypes: Inspect and Validate Column Types

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.

Inspect the student table with dtypes 🔝

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: object

Get the dtype of one column 🔝

A 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: object

Recognize common dtype families 🔝

int64 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.

Why numbers stored as text behave differently 🔝

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]

Inspect mixed values hidden behind object dtype 🔝

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: float64

Keep missing integers with Int64 🔝

A 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() == 1

Expected output

Inferred: float64
Nullable: Int64
0       1
1    <NA>
2       3
dtype: Int64

Choose explicit nullable string storage 🔝

astype("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']

Parse Boolean labels rather than casting non-empty text 🔝

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] == False

Expected output

0     True
1    False
2     <NA>
3     <NA>
dtype: boolean
Unmapped non-missing: ['unknown']

Inspect datetime and timedelta types 🔝

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]

Use category for a defined set of labels 🔝

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 columns by dtype 🔝

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: float64

Compare astype(), to_numeric() and convert_dtypes() 🔝

Use 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() == 1

Expected output

After nullable inference:
quantity       Int64
price_text    string
dtype: object
   quantity  price
0         1   12.5
1      <NA>   <NA>
2         3    3.0

Practical workflow: clean a sales schema 🔝

Keep 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() == 2

Expected 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.0

Validate types and values before export 🔝

Use 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              Float64

Preserve identifiers while importing CSV data 🔝

An 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: object

Common questions and mistakes 🔝

Why 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.

Exercise: keep codes and clean quantities 🔝

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.

Exercise solution 🔝

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() == 2

Expected output

   code  quantity
0  0001         4
1  0002      <NA>
2  0003      <NA>
code        string
quantity     Int64
dtype: object
Invalid codes: ['0002']

Practice in Google Colab 🔝

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.




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