Data Types & astype
The main pandas dtypes (str, int64, float64, bool, category, datetime64), converting with astype, and safely parsing messy numbers with pd.to_numeric(errors='coerce').
Data loaded from CSV files, web pages or Excel often has the wrong types: runs stored as text, dates stored as text, "True"/"False" stored as text. With the wrong type, maths silently goes wrong — adding text joins it instead of summing. Checking and fixing dtypes is one of the first steps of every cleaning job.
Definition
A dtype (data type) is the kind of value a column holds — it decides how values are stored in memory and which operations are allowed.
astype(dtype)converts a Series or DataFrame to another dtype and returns a new object.pd.to_numericconverts values to numbers and, witherrors="coerce", turns anything it cannot parse intoNaNinstead of failing.
- Decides storage and operations —
int64can be summed and averaged;strsupports.strmethods;datetime64supports date maths and the.dtaccessor. - Returns a new object — assign it back:
df["runs"] = df["runs"].astype(int). - Coerce — "force": bad values become missing instead of raising an error.
The main pandas dtypes
| dtype | Holds | Example values | Notes |
|---|---|---|---|
int64 | whole numbers | 45, 101 | cannot hold NaN |
Int64 (capital I) | whole numbers with missing | 45, <NA> | nullable integer |
float64 | decimals (and NaN) | 8.9, NaN | |
bool | True / False | True | |
str | text | "MI" | default text dtype in pandas 3 |
object | any Python objects / mixed | "MI", 7 | text used this in pandas 2 |
category | a fixed set of repeated labels | "MI", "CSK" | saves memory, faster grouping |
datetime64[...] | dates and times | 2023-03-31 | via pd.to_datetime |
timedelta64[...] | durations | 3 days | difference of two dates |
Syntax
python
df.dtypes # check every column
s.astype(dtype, errors="raise") # convert one column
df.astype({"col1": "int64", "col2": "float64"}) # several columns at once
pd.to_numeric(arg, errors="raise", downcast=None)
pd.to_datetime(arg, errors="raise", format=None)
df.select_dtypes(include=None, exclude=None)| Function / parameter | Meaning | Default |
|---|---|---|
astype(dtype) | target type: int, "int64", float, "float64", str, bool, "category", "Int64"… | required |
astype(errors=...) | "raise" an error or "ignore" and return the original | "raise" |
to_numeric(errors="coerce") | unparseable values → NaN | "raise" |
to_numeric(downcast=...) | shrink to "integer", "signed", "unsigned" or "float" | None |
to_datetime(format=...) | explicit date format, e.g. "%d-%m-%Y" | inferred |
to_datetime(errors="coerce") | unparseable dates → NaT (Not a Time) | "raise" |
select_dtypes(include="number") | keep only columns of the given types | — |
Example — a table where everything is text
python
raw = pd.DataFrame({
"match_id": ["101", "102", "103", "104"],
"date": ["2023-03-31", "2023-04-01", "2023-04-02", "2023-04-03"],
"team": ["GT", "PBKS", "LSG", "RR"],
"runs": ["178", "191", "193", "203"],
"economy": ["8.9", "9.55", "9.65", "10.15"],
"won": ["True", "False", "True", "True"],
})
print(raw.dtypes)
print(raw["runs"].sum())text
match_id str
date str
team str
runs str
economy str
won str
dtype: object
178191193203How it works: every column is str, so sum() concatenated the text "178" + "191" + … instead of adding numbers. This is exactly the kind of silent bug that wrong dtypes cause.
Example — convert each column to the right type
python
df = raw.copy()
df["match_id"] = df["match_id"].astype("int64")
df["runs"] = df["runs"].astype(int)
df["economy"] = df["economy"].astype("float64")
df["date"] = pd.to_datetime(df["date"])
df["team"] = df["team"].astype("category")
df["won"] = df["won"].map({"True": True, "False": False})
print(df.dtypes)
print(df)
print(df["runs"].sum())text
match_id int64
date datetime64[us]
team category
runs int64
economy float64
won bool
dtype: object
match_id date team runs economy won
0 101 2023-03-31 GT 178 8.90 True
1 102 2023-04-01 PBKS 191 9.55 False
2 103 2023-04-02 LSG 193 9.65 True
3 104 2023-04-03 RR 203 10.15 True
765How it works:
astype("int64")/astype(int)— both giveint64;astype(float)givesfloat64.pd.to_datetimeparses date strings into real dates (datetime64) so you can sort by date, extract the weekday with.dt.day_name(), and so on.astype("category")stores each distinct team once and keeps small integer codes — great for columns with few distinct values repeated many times.wonuses.mapwith an explicit dict — see the bool trap below.- Now
sum()gives the real total765.
You can also convert several columns in one call with a dict: raw.astype({"match_id": "int64", "runs": "int64", "economy": "float64"}).
Note (pandas 3):
pd.to_datetimenow infers the time resolution, so you seedatetime64[us]here; old tutorials showdatetime64[ns]. The dates are the same.
The bool trap
python
print(pd.Series(["True", "False"]).astype(bool).tolist())text
[True, True]astype(bool) follows Python's rule: any non-empty string is True — including "False"! Convert text booleans with .map({"True": True, "False": False}) (or compare: s == "True").
Example — messy numbers: astype fails, to_numeric(errors="coerce") works
python
dirty = pd.Series(["45", "78", "DNB", "101", None]) # DNB = Did Not Bat
dirty.astype(int)text
ValueError: invalid literal for int() with base 10: 'DNB'python
print(pd.to_numeric(dirty, errors="coerce"))text
0 45.0
1 78.0
2 NaN
3 101.0
4 NaN
dtype: float64How it works: astype is strict — one bad value stops the whole conversion. pd.to_numeric(..., errors="coerce") converts what it can and replaces "DNB" (and the missing value) with NaN. The result is float64 because NaN needs a float. Without errors="coerce" you would get ValueError: Unable to parse string "DNB" at position 2 — which at least tells you where the bad value is.
To keep whole numbers and missing values, convert to the nullable Int64 dtype:
python
print(pd.to_numeric(dirty, errors="coerce").astype("Int64"))text
0 45
1 78
2 <NA>
3 101
4 <NA>
dtype: Int64Other conversions worth knowing
| Conversion | Code | Result / gotcha |
|---|---|---|
| float → int | pd.Series([45.9, 78.2]).astype(int) | [45, 78] — truncates, does not round |
| float with NaN → int | pd.Series([45.0, np.nan]).astype(int) | IntCastingNaNError → use "Int64" or fill NaN first |
| int → text | pd.Series([2008, 2009]).astype(str) | dtype: str |
| bad dates | pd.to_datetime(s, errors="coerce") | unparseable → NaT |
| shrink numbers | pd.to_numeric(s, downcast="integer") | smallest int type that fits, e.g. int8 |
| category memory | 6,000 team names: str ≈ 309,132 bytes vs category ≈ 6,287 bytes | big savings for repeated labels |
Comparison — astype vs to_numeric vs to_datetime
astype | pd.to_numeric | pd.to_datetime | |
|---|---|---|---|
| Converts to | any dtype | numbers only | dates/times only |
| Bad values | raises error | errors="coerce" → NaN | errors="coerce" → NaT |
| Best for | clean data | messy numeric text | date strings |
| Called as | method: s.astype(...) | function: pd.to_numeric(s) | function: pd.to_datetime(s) |
Common mistakes
- ❌
df["runs"].astype(int)without assigning → nothing changes. ✅df["runs"] = df["runs"].astype(int). - ❌
astype(bool)on"True"/"False"text → everythingTrue. ✅.map({"True": True, "False": False}). - ❌
astype(int)on a column withNaN→IntCastingNaNError. ✅astype("Int64"), orfillnafirst. - ❌
astype(int)on text like"DNB"or"1,200". ✅ Clean first (.str.replace(",", "")) or usepd.to_numeric(errors="coerce"). - ❌ Using
astype("datetime64[ns]")on messy date strings. ✅pd.to_datetime(s, format=..., errors="coerce").
Interview tip
Q: A "price" column was read as text because a few rows contain "N/A". How do you convert it to numbers?
A: df["price"] = pd.to_numeric(df["price"], errors="coerce") — valid values become numbers and "N/A" becomes NaN. Then decide how to handle the NaNs (drop or fill). astype(float) would fail on the first "N/A".
Key points to remember
- A dtype decides how a column is stored and which operations work; check with df.dtypes.
- Main dtypes: int64, float64, bool, str (text in pandas 3), object (mixed), category, datetime64.
- astype(dtype) returns a new object — assign it back to the column.
- Numbers stored as text concatenate instead of adding — always fix dtypes first.
- astype(bool) makes every non-empty string True; map 'True'/'False' text explicitly.
- pd.to_numeric(s, errors='coerce') turns unparseable values into NaN instead of failing.
- Use 'Int64' for integers with missing values and pd.to_datetime for date strings.
Try it yourself
Fix a table where everything is text
Messy numbers with pd.to_numeric
Gotchas: bool, float to int, category memory
Practice: clean a scraped batting table
Revision Flashcards
Tap a card to flip it and see the answer.
