Data Types & astype

Reviewed & published by Brayan K

A dtype is the internal storage type pandas assigns to each column — int64, float64, object, bool, or datetime64 — and it decides how fast and how correctly your operations run.

Part of the free Pandas course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Learn to read the .dtypes attribute, convert columns with astype(), handle messy values with pd.to_numeric(errors=), and understand why object columns are the slow ones.

What You'll Learn in This Lesson

1 Reading the Types — df.dtypes

Every column has exactly one dtype, and df.dtypes lists them all at once. The names you will meet most often are int64 (whole numbers), float64 (decimals, and the home of NaN), bool (True/False), datetime64[ns] (timestamps), and object — the catch-all that usually means text, but is also where pandas dumps any column it could not interpret as something cleaner.

import pandas as pd

df = pd.DataFrame({
    "id":     [1, 2, 3],
    "price":  [1.5, 3.0, 0.75],
    "name":   ["Pen", "Notebook", "Eraser"],
    "active": [True, False, True],
})

df.dtypes
# id          int64
# price     float64
# name       object
# active       bool
import pandas as pd

df = pd.DataFrame({
    "id":     [1, 2, 3],
    "price":  [1.5, 3.0, 0.75],
    "name":   ["Pen", "Notebook", "Eraser"],
    "active": [True, False, True],
})

print(df.dtypes)

mem = df.memory_usage(deep=True)

# The fixed-width columns weigh the same on every machine, so print those...
print(mem.drop("name"))

# ...but "name" holds real Python str objects, and CPython 3.12 made every str
# 8 bytes smaller: this frame reports name=188 on Python 3.11 and name=164 on
# 3.12. Print mem["name"] to see yours. What is true on both is the point of
# the lesson — three short words outweigh three int64s, three float64s and the
# index put together:
print("name (object) is the heaviest column:", mem["name"] == mem.max())

# ✅ Expected output:
# id          int64
# price     float64
# name       object
# active       bool
# dtype: object
# Index     132
# id         24
# price      24
# active      3
# dtype: int64
# name (object) is the heaviest column: True

2 Converting Cleanly — astype()

astype() is the direct way to change a column's type. Call it on a Series and pass the target type — "int64", "float64", "str", "bool". It is strict: if any value cannot be converted, it raises an error rather than guessing. That strictness is a feature, because it stops bad data from silently slipping through.

df["price"] = df["price"].astype("float64")   # already float, no-op
df["id"]    = df["id"].astype("str")          # 1 -> "1"

# Convert several columns at once with a dict:
df = df.astype({"id": "int64", "active": "bool"})
import pandas as pd

df = pd.DataFrame({"id": [1, 2, 3], "score": [88.7, 91.2, 75.9]})

# astype is strict and direct
df["id"] = df["id"].astype("str")
print(df["id"].tolist())
# ['1', '2', '3']

# float -> int TRUNCATES (does not round)
df["score_int"] = df["score"].astype("int")
print(df["score_int"].tolist())

# ✅ Expected output:
# ['1', '2', '3']
# [88, 91, 75]

3 Taming Messy Data — pd.to_numeric(errors=)

Real spreadsheets are dirty: a price column might hold "1.50", "3.00", and a rogue "N/A". Calling astype("float") on that explodes. pd.to_numeric() is built for this — its errors= argument lets you decide what happens to values that will not parse. errors="coerce" turns each bad value into NaN so the rest of the column converts successfully.

raw = pd.Series(["1.50", "3.00", "N/A", "0.75"])

pd.to_numeric(raw)                    # raises ValueError on "N/A"
pd.to_numeric(raw, errors="coerce")   # "N/A" -> NaN, rest convert
pd.to_datetime(date_strings)          # the same idea for dates
import pandas as pd

raw = pd.Series(["1.50", "3.00", "N/A", "0.75"])

# astype would crash; to_numeric with coerce survives
clean = pd.to_numeric(raw, errors="coerce")
print(clean)

print("bad values:", clean.isna().sum())
# bad values: 1

# ✅ Expected output:
# 0    1.50
# 1    3.00
# 2     NaN
# 3    0.75
# dtype: float64
# bad values: 1
import pandas as pd

s = pd.Series(["10", "20", "oops", "40"])

# 🎯 YOUR TURN:
# Convert this messy text Series to numbers, turning "oops" into NaN
# instead of crashing. Fill in the errors= value.

nums = pd.to_numeric(s, errors="___")
print(nums)

# Expected output:
# 0    10.0
# 1    20.0
# 2     NaN
# 3    40.0
# dtype: float64

! Common Errors (And How to Fix Them)

❌ "could not convert string to float"

A stray non-numeric value blows up a strict astype:

df["price"].astype("float")   # ❌ ValueError on "N/A"

✅ Fix: coerce the bad values to NaN:

pd.to_numeric(df["price"], errors="coerce")   # ✅

❌ "Cannot convert non-finite values" to int

astype("int") refuses a column that contains NaN:

df["count"].astype("int")   # ❌ fails because of NaN

✅ Fix: fill the gaps first, or use the nullable Int64:

df["count"].fillna(0).astype("int")   # ✅
df["count"].astype("Int64")           # ✅ nullable integer

🎯 Mini Challenge: Fix the Types

A small import came in with everything as text. Repair the dtypes.

import pandas as pd

df = pd.DataFrame({
    "qty":     ["3", "5", "2"],
    "price":   ["1.50", "?", "0.75"],
    "sold_on": ["2026-01-04", "2026-01-05", "2026-01-06"],
})

df["qty"]     = df["qty"].astype("int")
df["price"]   = pd.to_numeric(df["price"], errors="coerce")
df["sold_on"] = pd.to_datetime(df["sold_on"])

print(df.dtypes)
# qty                 int64
# price             float64
# sold_on    datetime64[ns]
# dtype: object

print(df["price"].tolist())
# [1.5, nan, 0.75]

# ✅ Expected output:
# qty                 int64
# price             float64
# sold_on    datetime64[ns]
# dtype: object
# [1.5, nan, 0.75]

❓ Frequently Asked Questions

Lesson complete — your columns have the right types!

You can read df.dtypes, convert cleanly with astype, rescue messy data with pd.to_numeric(errors="coerce") and pd.to_datetime, and you know why object columns drag performance down.

Practice quiz

What does df.dtypes show you?

  • The data type of every column
  • The number of rows
  • The column names only
  • The missing values

Answer: The data type of every column. df.dtypes lists each column's storage type at once.

Which dtype is the home of NaN and decimal numbers?

  • int64
  • bool
  • float64
  • datetime64

Answer: float64. float64 holds decimals and is where NaN lives.

What happens to an int column the moment a single NaN appears?

  • It stays int64
  • It becomes bool
  • It is deleted
  • It becomes float64

Answer: It becomes float64. Classic int64 cannot hold NaN, so the column is promoted to float64.

What does pd.Series([88.7, 91.2, 75.9]).astype('int') produce?

  • [89, 91, 76]
  • [88, 91, 75]
  • An error
  • [88.0, 91.0, 75.0]

Answer: [88, 91, 75]. astype('int') truncates toward zero rather than rounding.

How is astype() described compared with to_numeric()?

  • Strict, raising an error on bad values
  • It silently drops bad rows
  • It only works on dates
  • It always returns strings

Answer: Strict, raising an error on bad values. astype is strict and raises if any value cannot be converted.

What does errors='coerce' do in pd.to_numeric()?

  • Raises a ValueError
  • Skips the whole column
  • Rounds the values
  • Turns unparseable values into NaN

Answer: Turns unparseable values into NaN. errors='coerce' replaces values that cannot be parsed with NaN.

After pd.to_numeric(pd.Series(['1.50','3.00','N/A','0.75']), errors='coerce'), what does .isna().sum() return?

  • 1
  • 0
  • 2
  • 3

Answer: 1. Only 'N/A' fails to parse, so exactly one NaN appears.

Which function parses a column of date strings into real datetimes?

  • pd.to_int
  • pd.parse
  • pd.to_datetime
  • pd.astype_date

Answer: pd.to_datetime. pd.to_datetime parses common formats into a datetime64 column.

Why are object columns slow?

  • They are always sorted
  • They store pointers to scattered Python objects instead of a contiguous block
  • They are stored on disk
  • They are encrypted

Answer: They store pointers to scattered Python objects instead of a contiguous block. Object columns hold pointers to separate Python objects, so operations loop in slow Python.

astype('int') fails on a column containing NaN. A safe fix is:

  • Delete the column
  • Convert to bool first
  • Use a for loop
  • fillna(0) first, or use the nullable 'Int64' dtype

Answer: fillna(0) first, or use the nullable 'Int64' dtype. Clean the NaNs with fillna, or use the nullable Int64 dtype that allows missing values.

Continue this course

Frequently asked questions

Why does a column of numbers show up as 'object'?

If even one value cannot be parsed as a number — a stray '$', a 'N/A' string, a blank — pandas falls back to the object dtype and stores the whole column as Python strings. The fix is to clean the bad values, then convert with astype(int)/astype(float) or pd.to_numeric().

What is the difference between astype and to_numeric?

astype() is a strict, general converter: it tries to coerce the whole column and raises an error if anything cannot be converted. pd.to_numeric() is numeric-specific and adds an errors= argument, so you can turn unparseable values into NaN with errors='coerce' instead of crashing. Use to_numeric for messy real-world data.

Why are object columns slow?

An object column is really an array of pointers to separate Python string objects scattered in memory. Operations loop in slow Python rather than fast vectorised C. Numeric and datetime columns store their values in a compact, contiguous block, so math and comparisons run orders of magnitude faster.

How do I convert a column of date strings to real dates?

Use pd.to_datetime(df['col']). It parses common formats automatically and gives you a datetime64 column, which unlocks .dt accessors, date arithmetic, and proper sorting. Like to_numeric, it accepts errors='coerce' to turn unparseable dates into NaT.