Python for Data course menu
pandas Data Types: astype, to_numeric, to_datetime and category
Fix column types in pandas: check dtypes, convert with astype, clean numbers stored as text with to_numeric, parse dates with to_datetime, use the category type to save memory, and nullable integer and string types.
On this page
Every column has a dtype. When a column has the wrong type, calculations fail or give nonsense: numbers stored as text sort alphabetically and cannot be summed, and dates stored as text cannot be compared by month. Fixing types is a core cleaning step.
Checking types#
import pandas as pd
raw = pd.DataFrame({
"order_id": ["001", "002", "003", "004"],
"price": ["1,200", "850", "2,499", "n/a"],
"quantity": ["2", "1", "3", "1"],
"ordered": ["05/01/2026", "09/01/2026", "11/02/2026", "20/02/2026"],
"status": ["paid", "paid", "pending", "paid"],
})
print(raw.dtypes)
print(raw["quantity"].sum()) # text joined together, not added!order_id object
price object
quantity object
ordered object
status object
dtype: object
2131Everything was read as text (object), so "summing" the quantities just joins the strings.
astype for clean values#
When every value converts cleanly, astype does the job:
import pandas as pd
quantity = pd.Series(["2", "1", "3", "1"])
q = quantity.astype(int)
print(q.dtype, q.sum())
print(pd.Series([1.0, 2.0]).astype(int).tolist(), pd.Series([1, 0, 1]).astype(bool).tolist())int64 7
[1, 2] [True, False, True]to_numeric for messy numbers#
astype(float) fails on values like "1,200" or "n/a". Clean the text first, then use pd.to_numeric with errors="coerce", which turns anything still invalid into NaN:
import pandas as pd
price = pd.Series(["1,200", "850", "2,499", "n/a"])
try:
price.astype(float)
except ValueError as e:
print("Error:", e)
clean = pd.to_numeric(price.str.replace(",", ""), errors="coerce")
print(clean)Error: could not convert string to float: '1,200'
0 1200.0
1 850.0
2 2499.0
3 NaN
dtype: float64After coercing, check how many values became NaN (clean.isna().sum()): each one is a value you could not read.
to_datetime for dates#
pd.to_datetime turns text into real timestamps. Give it the format so that day and month are never confused (05/01 could be 5 January or 1 May):
import pandas as pd
ordered = pd.Series(["05/01/2026", "09/01/2026", "11/02/2026", "20/02/2026"])
dates = pd.to_datetime(ordered, format="%d/%m/%Y")
print(dates)
print(dates.dt.month.tolist(), dates.dt.day_name().tolist())
print((dates.max() - dates.min()).days, "days between first and last")0 2026-01-05
1 2026-01-09
2 2026-02-11
3 2026-02-20
dtype: datetime64[ns]
[1, 1, 2, 2] ['Monday', 'Friday', 'Wednesday', 'Friday']
46 days between first and lastCommon format codes: %d day, %m month, %Y four-digit year, %y two-digit year, %H:%M hours and minutes, %b month name like Jan. More in Dates and Time Series.
The category type#
A text column with few distinct values (statuses, cities, sizes) can be stored as category: each value is stored once and rows hold small codes. It uses less memory and can have a meaningful order:
import pandas as pd
status = pd.Series(["paid", "pending", "paid", "refunded", "paid"] * 20000)
as_category = status.astype("category")
print(status.memory_usage(deep=True), as_category.memory_usage(deep=True))
print(as_category.cat.categories.tolist())
sizes = pd.Series(["M", "S", "L", "M"]).astype(pd.CategoricalDtype(["S", "M", "L"], ordered=True))
print(sizes.sort_values().tolist(), (sizes > "S").tolist())5440132 100406
['paid', 'pending', 'refunded']
['S', 'M', 'M', 'L'] [True, False, True, True]The category column uses far less memory. With an ordered category, sorting and comparisons follow S < M < L instead of alphabetical order.
Nullable types: Int64 and string#
A regular integer column cannot hold NaN (NaN is a float), so one missing value turns a whole column into floats. pandas' nullable types fix that, using <NA> for missing values:
import pandas as pd
ages = pd.Series([25, None, 31])
print(ages.dtype, ages.tolist())
ages_int = pd.Series([25, None, 31], dtype="Int64") # capital I
print(ages_int.dtype, ages_int.tolist())
names = pd.Series(["Asha", None], dtype="string")
print(names.dtype, names.str.upper().tolist())float64 [25.0, nan, 31.0]
Int64 [25, <NA>, 31]
string ['ASHA', <NA>]convert_dtypes() converts a whole DataFrame to these nullable types in one step.
Putting it together#
import pandas as pd
raw = pd.DataFrame({
"order_id": ["001", "002", "003", "004"],
"price": ["1,200", "850", "2,499", "n/a"],
"quantity": ["2", "1", "3", "1"],
"ordered": ["05/01/2026", "09/01/2026", "11/02/2026", "20/02/2026"],
"status": ["paid", "paid", "pending", "paid"],
})
clean = raw.assign(
price=pd.to_numeric(raw["price"].str.replace(",", ""), errors="coerce"),
quantity=raw["quantity"].astype(int),
ordered=pd.to_datetime(raw["ordered"], format="%d/%m/%Y"),
status=raw["status"].astype("category"),
)
print(clean.dtypes)
print((clean["price"] * clean["quantity"]).sum())order_id object
price float64
quantity int64
ordered datetime64[ns]
status category
dtype: object
10747.0order_id stays as text on purpose: it is a label, not a number, and its leading zeros matter.
Common mistakes#
Practice#
Practice exercise
Clean pd.Series(["₹1,250", "₹980", "free", "₹2,100"]) into numbers, treating "free" as 0. Print the cleaned values as integers and their total.
Show hint
Remove ₹ and , with str.replace, use to_numeric(errors="coerce"), then fillna(0) and astype(int).
Show solution
import pandas as pd
prices = pd.Series(["₹1,250", "₹980", "free", "₹2,100"])
numbers = pd.to_numeric(prices.str.replace("₹", "").str.replace(",", ""), errors="coerce")
numbers = numbers.fillna(0).astype(int)
print(numbers.tolist(), numbers.sum())[1250, 980, 0, 2100] 4330Key takeaway#
Next: Cleaning Text Columns.
Finished this lesson?