pandas is fastest when you stay in column operations: df["a"] + df["b"], np.where, .str, .dt. map and apply exist for the rest. Reach for them after a vectorized version does not fit.
Rewrite a Python loop as column math, use map for dictionaries, and know what apply is doing to each row or column.
Vectorize first
df = pd.DataFrame(
{
"product": ["A", "B", "A", "C"],
"units": [12, 7, 9, 2],
"price": [10.5, 22.0, 10.5, 31.0],
}
)
df["revenue"] = df["units"] * df["price"]
df["tier"] = np.where(df["revenue"] >= 100, "large", "small")
print(df)That is the default style for this course.
map a Series
Dictionary lookup, element-wise function, or another Series.
df = pd.DataFrame(
{
"product": ["A", "B", "A", "C"],
"units": [12, 7, 9, 2],
}
)
labels = {"A": "Hardware", "B": "Software", "C": "Hardware"}
print(df["product"].map(labels))
print()
print(df["units"].map(lambda n: "bulk" if n >= 9 else "each"))Unmapped keys become NaN.
replace vs map
df = pd.DataFrame({"product": ["A", "B", "A", "C"]})
print("replace keeps unknowns:")
print(df["product"].replace({"A": "Alpha", "B": "Beta"}))
print()
print("map turns unknowns into NA:")
print(df["product"].map({"A": "Alpha", "B": "Beta"}))apply on a Series
df = pd.DataFrame({"price": [10.5, 22.0, 10.5, 31.0]})
print(df["price"].apply(lambda p: round(p * 1.16, 2)))
print()
print("same, vectorized:")
print((df["price"] * 1.16).round(2))Use Series apply when the function is not vectorized.
apply on a DataFrame (rows)
axis=1 calls your function with each row as a Series. This is slow. Fine on 15 rows; painful on 15 million.
df = pd.DataFrame(
{
"units": [12, 7, 9, 2],
"price": [10.5, 22.0, 10.5, 31.0],
}
)
def label(row):
if row["units"] >= 9 and row["price"] < 15:
return "promo"
return "std"
print(df.apply(label, axis=1))
print()
print("same, vectorized:")
print(np.where((df["units"] >= 9) & (df["price"] < 15), "promo", "std"))apply on columns (axis=0)
df = pd.DataFrame(
{
"units": [12, 7, 9, 2],
"price": [10.5, 22.0, 10.5, 31.0],
"revenue": [126.0, 154.0, 94.5, 62.0],
}
)
print(df.apply(np.mean))
print()
print(df.mean(numeric_only=True))for i, row in df.iterrows(): is the slowest common pattern. If you catch yourself writing it, stop and try columns, np.where, or map first.