# How to Perform Conditional Pandas Replace Based on Multiple Conditions

> Learn how to perform conditional pandas replace using multiple conditions. Explore DataFrame where mask and boolean indexing for advanced data manipulation.

- Repository: [pandas/pandas](https://github.com/pandas-dev/pandas)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Use `DataFrame.where()`, `DataFrame.mask()`, or boolean-indexed assignment combined with logical operators (`&`, `|`, `~`) to replace values only when complex conditions are met, as [`pandas/core/generic.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py) implements condition evaluation separately from the scalar-mapping `replace()` method.**

The `pandas-dev/pandas` repository provides powerful indexing primitives for conditional data manipulation, but the high-level `replace()` method defined in [`pandas/core/generic.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py) (lines 7394‑7444) is architected strictly for value-to-value mapping—scalars, lists, dictionaries, or regex patterns—and cannot evaluate boolean predicates. To execute a conditional pandas replace based on multiple conditions, you must leverage the `where()` or `mask()` methods, which forward logic to the internal `_where` routine implemented in [`pandas/core/internals/managers.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/internals/managers.py) for efficient, block-level updates.

## Why `replace()` Cannot Handle Boolean Conditions

The `replace()` API is optimized for exact value substitution. It accepts a `to_replace` argument that can be a scalar, list, dict, or regular expression, but it has no parameter slot for a boolean mask. Attempting to pass a complex condition to `replace()` will raise a `ValueError` because the method simply iterates over the `to_replace` mapping rather than evaluating logical expressions. This design choice keeps the scalar-replacement pathway fast and memory-efficient, delegating conditional logic to the explicit indexing methods `where()` and `mask()`.

## The Core Implementation: `where()` and `mask()`

Two methods in [`pandas/core/generic.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py) provide the foundation for all conditional replacement:

- **`where()`** (lines 10161‑10178): Preserves the original value wherever the condition is **True** and inserts the `other` value wherever the condition is **False**.
- **`mask()`** (lines 10330‑10344): The inverse of `where()`—overwrites the original value wherever the condition is **True** and keeps it wherever the condition is **False**.

Both methods normalize their arguments and dispatch to the private `_where` routine inside [`pandas/core/internals/managers.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/internals/managers.py). This routine operates on the **BlockManager**, ensuring that replacements happen at the block level without unnecessary full-frame copying, delivering high performance even on large datasets.

## Building Boolean Masks for Multiple Conditions

Because `where()` and `mask()` accept any boolean `Series` or `DataFrame`, you can construct elaborate logic by combining individual predicates.

### Combining Conditions with Logical Operators

Use standard NumPy-style operators on boolean Series, always wrapping each condition in parentheses to respect Python operator precedence:

- `&` – Logical **AND** (both conditions must be True)
- `|` – Logical **OR** (either condition can be True)
- `~` – Logical **NOT** (invert the condition)

### Choosing Between `where()` and `mask()`

- Use **`df.where(cond, other)`** when you want to keep existing data that satisfies your criteria and replace everything else.
- Use **`df.mask(cond, other)`** when you want to target specific rows that violate business rules (e.g., outliers) and overwrite only those.

Both approaches allow you to reference the `cond` object multiple times or combine it with additional masks before passing it to the method.

## Practical Code Examples

The following examples assume this sample DataFrame:

```python
import pandas as pd
import numpy as np

df = pd.DataFrame({
    "city":   ["NY", "LA", "NY", "SF", "LA"],
    "sales":  [200, 150, 300, 120, 180],
    "profit": [30, 20, 50, 10, 25]
})

```

### Example 1: Replace Profit Where Sales Exceed 250 AND City is NY

This demonstrates **AND** logic with `mask()`:

```python

# Build combined condition

cond = (df["sales"] > 250) & (df["city"] == "NY")

# Replace profit with NaN where condition is True

df["profit"] = df["profit"].mask(cond, other=np.nan)

```

Result: Row 2 (NY, sales 300) has its profit replaced with `NaN`; all other rows remain unchanged. Internally, `mask()` inverts the boolean logic and forwards to `_where` in the BlockManager.

### Example 2: Inverse Logic with `where()`

Use `where()` to zero-out profits only where the condition is **False** (i.e., keep high-NY sales intact):

```python
df["profit"] = df["profit"].where(~cond, other=0)

```

Here, `~cond` inverts the mask, so only rows that do **not** meet both criteria are altered.

### Example 3: Column-Wise Assignment with `loc`

For direct scalar assignment without method chaining, use boolean indexing:

```python
df.loc[cond, "profit"] = -1

```

This updates the underlying blocks in-place via the same `_where` machinery used by `where()` and `mask()`.

### Example 4: OR Conditions Across Multiple Columns

Replace sales with `0` where `city` is **LA** OR `profit` is below 15:

```python
cond = (df["city"] == "LA") | (df["profit"] < 15)
df["sales"] = df["sales"].mask(cond, other=0)

```

Rows 1 (LA) and 3 (SF, profit 10) have their sales zeroed out, demonstrating how `|` aggregates independent boolean Series.

## Summary

- The `replace()` method in [`pandas/core/generic.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py) (lines 7394‑7444) handles scalar-to-scalar mapping but cannot evaluate boolean masks for multiple conditions.
- Use `DataFrame.where()` to retain values where conditions are **True** and substitute the `other` argument where they are **False**.
- Use `DataFrame.mask()` to overwrite values exactly where conditions evaluate to **True**, preserving data elsewhere.
- Combine individual boolean Series with `&`, `|`, and `~` (wrapped in parentheses) to express arbitrary multi-condition logic.
- All conditional replacement routes—whether via `where()`, `mask()`, or `loc`—ultimately invoke the `_where` implementation in [`pandas/core/internals/managers.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/internals/managers.py), ensuring consistent block-level performance across large datasets.

## Frequently Asked Questions

### Why does `df.replace()` raise an error when I pass a boolean mask?

According to the source in [`pandas/core/generic.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py) lines 7394‑7444, `replace()` is strictly a value-mapping utility. It accepts scalars, lists, dictionaries, or regex patterns for the `to_replace` parameter, but it has no internal logic to parse or evaluate boolean array conditions. Passing a mask triggers a validation error because the method expects a mapping, not a predicate.

### What is the difference between `where()` and `mask()` in pandas?

`where(cond, other)` keeps the original data where `cond` is **True** and inserts `other` where `cond` is **False**, whereas `mask(cond, other)` does the inverse—overwriting where `cond` is **True** and preserving where **False**. Both are implemented in [`pandas/core/generic.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py) (lines 10161‑10178 and 10330‑10344) and share the same backend `_where` routine for block-wise execution.

### How do I combine three or more conditions for a replacement?

Chain boolean Series with the `&` (AND) and `|` (OR) operators, ensuring each individual condition is wrapped in parentheses due to Python operator precedence. For example: `(df['A'] > 1) & (df['B'] < 5) & (df['C'].notna())`. Pass the resulting unified mask to `where()`, `mask()`, or use it inside `df.loc[mask, column] = value`.

### Is boolean indexing with `loc` faster than using `where()` or `mask()`?

All three patterns eventually call the same low-level `_where` implementation in [`pandas/core/internals/managers.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/internals/managers.py). Performance differences are negligible for most workflows; choose `loc` for direct in-place scalar assignment, `where()` when you want to keep values matching a condition, or `mask()` when you want to target violations for replacement.