Use astype() to cast a Pandas Series or DataFrame to a chosen data type. Inspect the source values first, choose how to handle invalid or missing input, and assign the converted result. A successful cast does not by itself validate business rules.
Start with numeric marks and inspect dtypes before conversion. Keep the original table available to compare values and types.
import pandas as pd
import numpy as np
def student_data():
return pd.DataFrame({
"NAME": ["Ravi", "Raju", "Alex", "Ron", "King", "Jack"],
"ID": [1, 2, 3, 4, 5, 6],
"MATH": [80, 40, 70, 70, 70, 30],
"ENGLISH": [80, 70, 40, 50, 60, 30]
})
df = student_data()
print(df.dtypes)
print("MATH:", df["MATH"].dtype)
print("NAME:", df["NAME"].dtype)Expected output
NAME str
ID int64
MATH int64
ENGLISH int64
dtype: object
MATH: int64
NAME: strConverting marks to text changes how + behaves. The original str conversion appends "2" to each score; numeric addition increases each score by 2. Prefer nullable string when missing values should remain missing.
df = student_data()
print("Text concatenation:")
print(df["MATH"].astype(str) + "2")
print("Numeric addition:")
print(df["MATH"] + 2)
assert (df["MATH"] + 2).tolist() == [82, 42, 72, 72, 72, 32]Expected output
Text concatenation:
0 802
1 402
2 702
3 702
4 702
5 302
Name: MATH, dtype: str
Numeric addition:
0 82
1 42
2 72
3 72
4 72
5 32
Name: MATH, dtype: int64Call astype() on the selected Series and assign the result back to update the table. The method returns a converted object; a call alone does not update the column.
df = student_data()
converted = df["MATH"].astype("int32")
print(converted)
print("Source type:", df["MATH"].dtype)
df["MATH"] = converted
print("Updated type:", df["MATH"].dtype)
assert str(df["MATH"].dtype) == "int32"Expected output
0 80
1 40
2 70
3 70
4 70
5 30
Name: MATH, dtype: int32
Source type: int64
Updated type: int32A dictionary maps column names to their intended dtypes. Columns not included in the mapping retain their types. Assign the returned DataFrame rather than assuming the original changed.
df = student_data()
converted = df.astype({"NAME": "string", "ID": "int32", "MATH": "float64"})
print(converted.dtypes)
print("Source MATH:", df["MATH"].dtype)
assert str(converted["ID"].dtype) == "int32"
assert str(converted["MATH"].dtype) == "float64"Expected output
NAME string
ID int32
MATH float64
ENGLISH int64
dtype: object
Source MATH: int64Use a single dtype for an entire table only when every selected column is compatible. Casting a table containing names to float will fail. Select relevant columns explicitly.
df = student_data()
scores = df[["MATH", "ENGLISH"]].astype("float64")
print(scores.head(2))
print(scores.dtypes)
assert scores.dtypes.eq(np.dtype("float64")).all()Expected output
MATH ENGLISH
0 80.0 80.0
1 40.0 70.0
MATH float64
ENGLISH float64
dtype: objectThe default errors="raise" reports a failed conversion. errors="ignore" suppresses conversion errors but may leave data unconverted; check the resulting dtype. astype() does not accept errors="coerce". Use to_numeric() when invalid numeric text should become missing.
values = pd.Series(["10", "bad", "30"], dtype="string")
try:
values.astype("int64")
except ValueError:
print("Conversion rejected invalid numeric text.")
ignored = values.astype("int64", errors="ignore")
print("After ignore:", ignored.tolist())
print("Dtype:", ignored.dtype)
assert ignored.equals(values)Expected output
Conversion rejected invalid numeric text.
After ignore: ['10', 'bad', '30']
Dtype: stringPreserve the source values so you can identify parsing failures. Coercion marks invalid input as missing, but it does not explain why input was invalid. Here the values are whole numbers, so nullable Int64 is appropriate.
raw = pd.Series(["10", "bad", None, "30"], dtype="string")
numeric = pd.to_numeric(raw, errors="coerce")
failed = raw.notna() & numeric.isna()
result = numeric.astype("Int64")
print(result)
print("Invalid inputs:", raw.loc[failed].tolist())
assert result.isna().sum() == 2Expected output
0 10
1 <NA>
2 <NA>
3 30
dtype: Int64
Invalid inputs: ['bad']Plain int64 cannot represent missing values. Nullable Int64 can hold integer values and pd.NA. Filling an unknown quantity with zero changes its meaning, so use fillna() only when that replacement is justified.
values = pd.Series([1.0, float("nan"), 3.0])
try:
values.astype("int64")
except (ValueError, TypeError):
print("Plain int64 cannot store a missing value.")
nullable = values.astype("Int64")
print(nullable)
assert nullable.isna().sum() == 1Expected output
Plain int64 cannot store a missing value.
0 1
1 <NA>
2 3
dtype: Int64Casting finite floating-point values to a NumPy integer dtype truncates toward zero; it does not round. Nullable integer conversion rejects non-integral values. If the domain requires whole units, validate first rather than silently losing fractions.
values = pd.Series([2.9, -2.9, 4.0])
print("Truncated:", values.astype("int64").tolist())
fractional = values.mod(1).ne(0)
print("Values needing a policy:", values.loc[fractional].tolist())
assert fractional.tolist() == [True, True, False]
# Choose and document rounding or rejection before casting.Expected output
Truncated: [2, -2, 4]
Values needing a policy: [2.9, -2.9]Use string storage for identifiers that contain leading zeros. Casting an already parsed integer to string cannot recover lost zeros. Declare the type during import when the original formatting matters; see read_csv().
codes = pd.Series(["0012", None, "0045"], dtype="object")
text = codes.astype("string")
print(text)
print("Missing:", text.isna().sum())
assert text.iloc[0] == "0012"
assert pd.isna(text.iloc[1])Expected output
0 0012
1 <NA>
2 0045
dtype: string
Missing: 1A non-empty string such as "False" is truthy when cast to bool. Define the allowed text labels, normalize them, map to logical values and then use nullable boolean. Review unknown labels.
raw = pd.Series(["Yes", "No", None, "unknown"], dtype="string")
flags = raw.str.strip().str.lower().map({"yes": True, "no": False}).astype("boolean")
print(flags)
print("Unrecognized:", raw.loc[raw.notna() & flags.isna()].tolist())
assert flags.iloc[0] == True and flags.iloc[1] == FalseExpected output
0 True
1 False
2 <NA>
3 <NA>
dtype: boolean
Unrecognized: ['unknown']Use category for a defined vocabulary, and explicitly define order when comparisons should follow a scale. Validate labels before casting to a restricted category set: unknown labels become missing. Explore categorical data.
labels = pd.Series(["low", "high", "medium", "other"], dtype="string")
category_type = pd.CategoricalDtype(["low", "medium", "high"], ordered=True)
unknown = labels.notna() & ~labels.isin(category_type.categories)
print("Unknown labels:", labels.loc[unknown].tolist())
known = labels.loc[~unknown].astype(category_type)
print(known)
print("Above low:", known.loc[known > "low"].tolist())Expected output
Unknown labels: ['other']
0 low
1 high
2 medium
dtype: category
Categories (3, str): ['low' < 'medium' < 'high']
Above low: ['high', 'medium']For text dates, to_datetime() provides formats and a parsing-error policy. astype() is a casting tool, not a replacement for an explicit date-parsing workflow. Timezone localization and conversion should use the relevant datetime methods.
raw = pd.Series(["31/01/2026", "bad-date", None], dtype="string")
dates = pd.to_datetime(raw, format="%d/%m/%Y", errors="coerce")
print(dates)
print("Invalid date inputs:", raw.loc[raw.notna() & dates.isna()].tolist())
assert dates.isna().sum() == 2Expected output
0 2026-01-31
1 NaT
2 NaT
dtype: datetime64[us]
Invalid date inputs: ['bad-date']A smaller integer dtype has a limited range. Do not rely on casting to detect overflow; validate against the target dtype limits before converting. A smaller dtype is useful only when it can represent every required value.
values = pd.Series([0, 120, 300], dtype="int64")
limits = np.iinfo(np.int8)
fits = values.between(limits.min, limits.max)
print("int8 range:", limits.min, limits.max)
print("Out-of-range values:", values.loc[~fits].tolist())
safe = values.loc[fits].astype("int8")
print("Validated conversion:", safe.tolist())
assert not fits.all()Expected output
int8 range: -128 127
Out-of-range values: [300]
Validated conversion: [0, 120]Keep raw data for audit, normalize text and parse numeric inputs before applying a schema. Quantities must be non-negative whole numbers here. Unknown quantities stay missing. This synthetic sample uses floats for demonstration; define suitable precision rules for production financial calculations.
raw_sales = pd.DataFrame({
"code": ["0012", "0045", "0070", "0081"],
"quantity": ["2", "bad", None, "0"],
"price": ["12.50", "3", "5", "8"],
"paid": ["yes", "no", "unknown", "yes"]
})
sales = raw_sales.copy()
quantity = pd.to_numeric(sales["quantity"], errors="coerce")
known = quantity.dropna()
assert known.ge(0).all() and known.mod(1).eq(0).all()
sales["quantity"] = quantity
sales["price"] = pd.to_numeric(sales["price"], errors="coerce")
sales["paid"] = sales["paid"].astype("string").str.lower().map({"yes": True, "no": False})
sales = sales.astype({"code": "string", "quantity": "Int64", "price": "Float64", "paid": "boolean"})
sales["revenue"] = sales["quantity"] * sales["price"]
print(sales)
print(sales.dtypes)
assert sales.loc[0, "code"] == "0012"Expected output
code quantity price paid revenue
0 0012 2 12.5 True 25.0
1 0045 <NA> 3.0 False <NA>
2 0070 <NA> 5.0 <NA> <NA>
3 0081 0 8.0 True 0.0
code string
quantity Int64
price Float64
paid boolean
revenue Float64
dtype: objectInvalid non-missing input and originally missing input are different problems. Report them separately. Sum with min_count=1 to avoid treating an entirely unknown subtotal as zero. See sum() and info().
failed_quantity = raw_sales["quantity"].notna() & sales["quantity"].isna()
print("Invalid quantity codes:", raw_sales.loc[failed_quantity, "code"].tolist())
print("Originally missing quantities:", raw_sales["quantity"].isna().sum())
print("Unknown revenue rows:", sales["revenue"].isna().sum())
print("Known revenue subtotal:", sales["revenue"].sum(min_count=1))
assert sales["revenue"].sum(min_count=1) == 25Expected output
Invalid quantity codes: ['0045']
Originally missing quantities: 1
Unknown revenue rows: 2
Known revenue subtotal: 25.0astype() returns a result rather than modifying the source in place. In Pandas 3.0, the copy argument is ignored and deprecated; avoid using it to control conversion semantics. Work with the returned object and assign it explicitly. In older versions, copy behavior can differ.
source = pd.DataFrame({"quantity": [1, 2]})
converted = source.astype({"quantity": "float64"})
converted.loc[0, "quantity"] = 9.5
print("Source values:", source["quantity"].tolist())
print("Converted values:", converted["quantity"].tolist())
assert source["quantity"].tolist() == [1, 2]Expected output
Source values: [1, 2]
Converted values: [9.5, 2.0]Why did the source dtype stay unchanged? Assign the converted result. Can astype coerce invalid text? No; parse with to_numeric() or to_datetime() first. Why does int64 reject missing values? Choose nullable Int64 when missing integers are valid. Why did a decimal disappear? NumPy integer casting truncates; validate fractions first. Why did errors="ignore" leave text? It suppresses failures without guaranteeing conversion. Does convert_dtypes() parse numeric strings? It chooses suitable nullable types from existing values; explicit parsing is still needed. Continue with data cleaning.
Create codes "0001", "0002" and "0003" with quantity text "4", "bad" and None. Keep codes as string, parse quantities and store them as nullable Int64. Identify invalid non-missing input and verify that two quantities remain missing.
Parsing determines which values are valid numbers; casting establishes the intended storage type.
raw = pd.DataFrame({"code": ["0001", "0002", "0003"], "quantity": ["4", "bad", None]})
clean = raw.copy()
clean["quantity"] = pd.to_numeric(clean["quantity"], errors="coerce")
failed = raw["quantity"].notna() & clean["quantity"].isna()
clean = clean.astype({"code": "string", "quantity": "Int64"})
print(clean)
print("Invalid codes:", raw.loc[failed, "code"].tolist())
assert clean.loc[0, "code"] == "0001"
assert clean["quantity"].isna().sum() == 2Expected output
code quantity
0 0001 4
1 0002 <NA>
2 0003 <NA>
Invalid codes: ['0002']Open in Google Colab View on GitHub
Run the examples in order, change the source values and inspect conversion results and validation checks. Save your own copy to keep edits. All sample tables are included in the notebook.
Continue with dtypes, select_dtypes(), to_timedelta() and date differences.
Reference: Pandas DataFrame.astype 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.