Map, apply, and vectorize

Prefer column arithmetic. Use map, np.where, and apply when you must.

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.

Goal

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))
Pitfall

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.