Repository: pandas-dev/pandas
Pull request: DEPR: `Styler.where` possible deprecation
closes #40124 

Proposal to deprecate the method: `Styler.where`, since `Styler.applymap` and `Styler.where` are so similar in function, and return signatures, that it is almost confusing to have 2 methods for the same thing.

Note that `Styler.where` offers no advantage over `applymap` since internally it just restructures the callable and uses `applymap` itself:

```
def where(..):
    return self.applymap(lambda val: value if cond(val, **kwargs) else other, subset=subset)
```

Problem statement:
DEPR: `Styler.where` in favour of using `applymap` and `apply`
`Styler` by definition is a conditional renderer. The functions it provides, `apply` and `applymap`, serve as conditional functions to render a style.

The function `Styler.where` therefore, in the first case, seems unnecessary and indirectly confusing since it is a conditional rendering function implying perhaps the other methods are not.  Not only is it just a 'convenience' wrapper of `applymap`:

```
def where(cond, value, other, subset, **kwargs):
    return self.applymap(lambda v: value if cond(v) else other, subset=subset, **kwargs)
```
But I also think, in the second case, it is quite inconvenient for a couple reasons:
1)  The docs are ambiguous:
```
cond: callable: cond should take a scalar and return a boolean.
**kwargs: dict: pass along to cond.
```
and in fact `kwargs` appear in the code but do nothing.

2) It feels that there is more benefit to be had by a user learning how you use `apply` and `applymap` to the same effect through good examples.

For example: 
`styler.applymap(lambda v: props if cond(v) else None, subset=subset)` or
`styler.apply(lambda d: np.where(d > 10, props, ""), axis=None, subset=subset)`

seem pretty straightforward replacements???

Perhaps documenting in the first instance that `apply` and `applymap` are preferred over `where` is non-controversial.

Hints from issue discussion:

Commit message: Merge remote-tracking branch 'upstream/master' into where_fix

Patch to review:
diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py
index ba17e44fc66e0..3372b89c0f4b1 100644
--- a/pandas/io/formats/style.py
+++ b/pandas/io/formats/style.py
@@ -987,7 +987,6 @@ def apply(
 
         See Also
         --------
-        Styler.where: Apply CSS-styles based on a conditional function elementwise.
         Styler.applymap: Apply a CSS-styling function elementwise.
 
         Notes
@@ -1045,7 +1044,6 @@ def applymap(self, func: Callable, subset=None, **kwargs) -> Styler:
 
         See Also
         --------
-        Styler.where: Apply CSS-styles based on a conditional function elementwise.
         Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise.
 
         Notes
@@ -1080,10 +1078,12 @@ def where(
         Updates the HTML representation with a style which is
         selected in accordance with the return value of a function.
 
+        .. deprecated:: 1.3.0
+
         Parameters
         ----------
         cond : callable
-            ``cond`` should take a scalar and return a boolean.
+            ``cond`` should take a scalar, and optional args, and return a boolean.
         value : str
             Applied when ``cond`` returns true.
         other : str
@@ -1103,18 +1103,36 @@ def where(
         Styler.applymap: Apply a CSS-styling function elementwise.
         Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise.
 
-        Examples
-        --------
-        >>> def cond(v):
-        ...     return v > 1 and v != 4
+        Notes
+        -----
+        This method is deprecated.
+
+        This method is a convenience wrapper for :meth:`Styler.applymap`, which we
+        recommend using instead.
+
+        The example:
         >>> df = pd.DataFrame([[1, 2], [3, 4]])
-        >>> df.style.where(cond, value='color:red;', other='font-size:2em;')
+        >>> def cond(v, limit=4):
+        ...     return v > 1 and v != limit
+        >>> df.style.where(cond, value='color:green;', other='color:red;')
+
+        should be refactored to:
+        >>> def style_func(v, value, other, limit=4):
+        ...     cond = v > 1 and v != limit
+        ...     return value if cond else other
+        >>> df.style.applymap(style_func, value='color:green;', other='color:red;')
         """
+        warnings.warn(
+            "this method is deprecated in favour of `Styler.applymap()`",
+            FutureWarning,
+            stacklevel=2,
+        )
+
         if other is None:
             other = ""
 
         return self.applymap(
-            lambda val: value if cond(val) else other, subset=subset, **kwargs
+            lambda val: value if cond(val, **kwargs) else other, subset=subset
         )
 
     def set_precision(self, precision: int) -> Styler:
diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py
index 3422eb9dc64b7..6c6a31c6bb6df 100644
--- a/pandas/tests/io/formats/style/test_style.py
+++ b/pandas/tests/io/formats/style/test_style.py
@@ -495,7 +495,8 @@ def f(x):
 
         style1 = "foo: bar"
 
-        result = self.df.style.where(f, style1)._compute().ctx
+        with tm.assert_produces_warning(FutureWarning):
+            result = self.df.style.where(f, style1)._compute().ctx
         expected = {
             (r, c): [("foo", "bar")]
             for r, row in enumerate(self.df.index)
@@ -522,14 +523,15 @@ def f(x):
         style1 = "foo: bar"
         style2 = "baz: foo"
 
-        result = self.df.style.where(f, style1, style2, subset=slice_)._compute().ctx
+        with tm.assert_produces_warning(FutureWarning):
+            res = self.df.style.where(f, style1, style2, subset=slice_)._compute().ctx
         expected = {
             (r, c): [("foo", "bar") if f(self.df.loc[row, col]) else ("baz", "foo")]
             for r, row in enumerate(self.df.index)
             for c, col in enumerate(self.df.columns)
             if row in self.df.loc[slice_].index and col in self.df.loc[slice_].columns
         }
-        assert result == expected
+        assert res == expected
 
     def test_where_subset_compare_with_applymap(self):
         # GH 17474
@@ -551,12 +553,29 @@ def g(x):
         ]
 
         for slice_ in slices:
-            result = (
-                self.df.style.where(f, style1, style2, subset=slice_)._compute().ctx
-            )
+            with tm.assert_produces_warning(FutureWarning):
+                result = (
+                    self.df.style.where(f, style1, style2, subset=slice_)._compute().ctx
+                )
             expected = self.df.style.applymap(g, subset=slice_)._compute().ctx
             assert result == expected
 
+    def test_where_kwargs(self):
+        df = DataFrame([[1, 2], [3, 4]])
+
+        def f(x, val):
+            return x > val
+
+        with tm.assert_produces_warning(FutureWarning):
+            res = df.style.where(f, "color:green;", "color:red;", val=2)._compute().ctx
+        expected = {
+            (0, 0): [("color", "red")],
+            (0, 1): [("color", "red")],
+            (1, 0): [("color", "green")],
+            (1, 1): [("color", "green")],
+        }
+        assert res == expected
+
     def test_empty(self):
         df = DataFrame({"A": [1, 0]})
         s = df.style


Code review comments:
diff_hunk: @@ -1080,10 +1078,12 @@ def where(
         Updates the HTML representation with a style which is
         selected in accordance with the return value of a function.
 
+        .. deprecated:: 1.3.0
+
         Parameters
         ----------
         cond : callable
-            ``cond`` should take a scalar and return a boolean.
+            ``cond`` should take a scalar, and optional args, and return a boolean.
path: pandas/io/formats/style.py
text: @author:
this was conflicting with line 1095 in the docstring:
```
**kwargs : dict
            Pass along to ``cond``.
```