Repository: pandas-dev/pandas

Issue:
Rolling standard deviation fails when used with win_type
#### Code Sample, a copy-pastable example if possible

```python
import pandas as pd
df = pd.DataFrame({'a': range(6)})
df['a'].rolling(3, win_type='blackman').agg(['mean', 'std'])
```
#### Problem description

When calculating rolling aggregations with a window function, sometimes there is an error which is quite hard to understand. I think it might be that the std() aggregation is not compatible with certain window types, or something like that- but it's not clear from the error messages or documentation what is going wrong.

Here is the stack trace that I see when I run the code sample above:
<details><pre><code>
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-48-41cc0256ede1> in <module>
      3 df = pd.DataFrame({'a': range(6)})
      4 
----> 5 print(df['a'].rolling(3, win_type='blackman').agg(['mean', 'std']))

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\pandas\core\window.py in aggregate(self, arg, *args, **kwargs)
    741     @Appender(_shared_docs['aggregate'])
    742     def aggregate(self, arg, *args, **kwargs):
--> 743         result, how = self._aggregate(arg, *args, **kwargs)
    744         if result is None:
    745 

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\pandas\core\base.py in _aggregate(self, arg, *args, **kwargs)
    557             return self._aggregate_multiple_funcs(arg,
    558                                                   _level=_level,
--> 559                                                   _axis=_axis), None
    560         else:
    561             result = None

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\pandas\core\base.py in _aggregate_multiple_funcs(self, arg, _level, _axis)
    587                 try:
    588                     colg = self._gotitem(obj.name, ndim=1, subset=obj)
--> 589                     results.append(colg.aggregate(a))
    590 
    591                     # make sure we find a good name

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\pandas\core\window.py in aggregate(self, arg, *args, **kwargs)
    741     @Appender(_shared_docs['aggregate'])
    742     def aggregate(self, arg, *args, **kwargs):
--> 743         result, how = self._aggregate(arg, *args, **kwargs)
    744         if result is None:
    745 

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\pandas\core\base.py in _aggregate(self, arg, *args, **kwargs)
    354         if isinstance(arg, compat.string_types):
    355             return self._try_aggregate_string_function(arg, *args,
--> 356                                                        **kwargs), None
    357 
    358         if isinstance(arg, dict):

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\pandas\core\base.py in _try_aggregate_string_function(self, arg, *args, **kwargs)
    321         f = getattr(np, arg, None)
    322         if f is not None:
--> 323             return f(self, *args, **kwargs)
    324 
    325         raise ValueError("{arg} is an unknown string function".format(arg=arg))

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\numpy\core\fromnumeric.py in std(a, axis, dtype, out, ddof, keepdims)
   3240 
   3241     return _methods._std(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
-> 3242                          **kwargs)
   3243 
   3244 

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\numpy\core\_methods.py in _std(a, axis, dtype, out, ddof, keepdims)
    138 def _std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
    139     ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
--> 140                keepdims=keepdims)
    141 
    142     if isinstance(ret, mu.ndarray):

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\numpy\core\_methods.py in _var(a, axis, dtype, out, ddof, keepdims)
    110                 arrmean, rcount, out=arrmean, casting='unsafe', subok=False)
    111     else:
--> 112         arrmean = arrmean.dtype.type(arrmean / rcount)
    113 
    114     # Compute sum of squared deviations from mean

~\AppData\Local\Continuum\anaconda3\envs\petropy\lib\site-packages\pandas\core\window.py in __getattr__(self, attr)
    148 
    149         raise AttributeError("%r object has no attribute %r" %
--> 150                              (type(self).__name__, attr))
    151 
    152     def _dir_additions(self):

AttributeError: 'Window' object has no attribute 'dtype'


</details></pre></code>


#### Expected Output

When I run the code above without the win_type paramater, everything works fine:

```python
import pandas as pd
df = pd.DataFrame({'a': range(6)})
df['a'].rolling(3, win_type=None).agg(['mean', 'std'])
```
Result:
<pre><code>
   mean  std
0   NaN  NaN
1   NaN  NaN
2   1.0  1.0
3   2.0  1.0
4   3.0  1.0
5   4.0  1.0
</pre></code>

Thanks in advance for any tips!

#### Output of ``pd.show_versions()``

<details>

[paste the output of ``pd.show_versions()`` here below this line]
INSTALLED VERSIONS
------------------
commit: None
python: 3.7.3.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 158 Stepping 9, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.24.2
pytest: 4.3.1
pip: 19.0.3
setuptools: 40.8.0
Cython: 0.29.6
numpy: 1.16.2
scipy: 1.2.1
pyarrow: None
xarray: None
IPython: 7.4.0
sphinx: 1.8.5
patsy: 0.5.1
dateutil: 2.8.0
pytz: 2018.9
blosc: None
bottleneck: 1.2.1
tables: None
numexpr: 2.6.9
feather: None
matplotlib: 3.0.3
openpyxl: 2.6.1
xlrd: 1.2.0
xlwt: 1.3.0
xlsxwriter: 1.1.5
lxml.etree: 4.3.2
bs4: 4.7.1
html5lib: 1.0.1
sqlalchemy: 1.3.1
pymysql: None
psycopg2: None
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: None
</details>



Discussion:
> think it might be that the std() aggregation is not compatible with certain window types


That seems to be the case.

```python
In [23]: df.rolling(3, win_type='blackman').agg('std')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-23-0df3ddf74018> in <module>
----> 1 df.rolling(3, win_type='blackman').agg('std')

~/sandbox/pandas/pandas/core/window.py in aggregate(self, arg, *args, **kwargs)
    748     @Appender(_shared_docs['aggregate'])
    749     def aggregate(self, arg, *args, **kwargs):
--> 750         result, how = self._aggregate(arg, *args, **kwargs)
    751         if result is None:
    752

~/sandbox/pandas/pandas/core/base.py in _aggregate(self, arg, *args, **kwargs)
    328         if isinstance(arg, str):
    329             return self._try_aggregate_string_function(arg, *args,
--> 330                                                        **kwargs), None
    331
    332         if isinstance(arg, dict):

~/sandbox/pandas/pandas/core/base.py in _try_aggregate_string_function(self, arg, *args, **kwargs)
    295         f = getattr(np, arg, None)
    296         if f is not None:
--> 297             return f(self, *args, **kwargs)
    298
    299         raise ValueError("{arg} is an unknown string function".format(arg=arg))

<__array_function__ internals> in std(*args, **kwargs)

~/sandbox/numpy/numpy/core/fromnumeric.py in std(a, axis, dtype, out, ddof, keepdims)
   3354
   3355     return _methods._std(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
-> 3356                          **kwargs)
   3357
   3358

~/sandbox/numpy/numpy/core/_methods.py in _std(a, axis, dtype, out, ddof, keepdims)
    214 def _std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
    215     ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
--> 216                keepdims=keepdims)
    217
    218     if isinstance(ret, mu.ndarray):

~/sandbox/numpy/numpy/core/_methods.py in _var(a, axis, dtype, out, ddof, keepdims)
    185                 arrmean, rcount, out=arrmean, casting='unsafe', subok=False)
    186     else:
--> 187         arrmean = arrmean.dtype.type(arrmean / rcount)
    188
    189     # Compute sum of squared deviations from mean

~/sandbox/pandas/pandas/core/window.py in __getattr__(self, attr)
    146
    147         raise AttributeError("%r object has no attribute %r" %
--> 148                              (type(self).__name__, attr))
    149
    150     def _dir_additions(self):

AttributeError: 'Window' object has no attribute 'dtype'

```

it'd be good to verify if that's intentional, or whether it's an implementation detail. If it's intentional, then we should be able to verify which aggfuncs are compatible with which window types, and raise an informative error message before attempting the aggregation.

@Connossor are you interested in doing that investigation?
Hi @TomAugspurger,

Thanks for the response, and yep agree with your plan.

I did my best to look through the codebase, and it's not clear to me which aggfuncs are supposed to be compatible with which window types. It seems from here that if a string aggregation such as "std", then if it exists as a function in numpy it gets used:

https://github.com/pandas-dev/pandas/blob/cb00deb94500205fcb27a33cc1d0df79a9727f8b/pandas/core/base.py#L299-L323

From some research I think a std() ought to be useable with any window type. A formula for a weighted standard deviation is here: 
https://www.itl.nist.gov/div898/software/dataplot/refman2/ch2/weightsd.pdf . 
This issue may be related to issue #26462, where the mean() aggregation seems to have different behaviour for a  rolling window versus a grouby-rolling window.
@TomAugspurger I've investigated as you suggested, here is what I think is happening:

This function seems to govern what class is actually used: we get a `pandas.core.window.Window` object if the win_type parameter is set, otherwise a  `pandas.core.window.Rolling` object which seems to a be effectively a Window with uniform weights.

https://github.com/pandas-dev/pandas/blob/addc5fcd95064b765d4ee4260304d44822fdee3b/pandas/core/window.py#L2626-L2633


Then, when the `"std"` aggregation is called the `_try_aggregate_string_function()` method linked above looks to see of the object has a `.std()` method, and if none is present falls back to a numpy function.

The Window class has no `.std()` method. It falls back to the numpy implementation of std() which fails, as per the example above.

On the other hand, the `Rolling` class has a std() method which works just fine.

On a related note: the `pandas.core.window.RollingGroupby` class seems to inherit the mean() method from the `Rolling` class, and hence completely ignores the win_type paramater. 

Overall, it looks like we need two fixes:

1.  Raise an informative error if the "std" aggregation is used on a Window object, or alternatively implement a weighted standard deviation as described above.
2.  Ensure consistent behaviour with groupby. `Rolling` is to `Window` as `RollingGroupby` is to `WindowGroupby`, so perhaps we need a `WindowGroupby` class?

What do you think?

Your number 1. sounds reasonable.

Not sure about 2. I'm not that familiar with this code.
I want to implement weighted variance function (using proposed algo [here](https://people.xiph.org/~tterribe/tmp/homs/West79-_Updating_Mean_and_Variance_Estimates-_An_Improved_Method.pdf)) but `Window` uses single [function](https://github.com/pandas-dev/pandas/blob/master/pandas/_libs/window.pyx#L1678-L1740) with an argument `avg` (if it is True mean else sum) for calculating both `mean` and `sum`. I think the function should be splitted into two at first. What do you think? @jreback @WillAyd
sure this could be changed

Resolving patch:
diff --git a/pandas/core/window/expanding.py b/pandas/core/window/expanding.py
--- a/pandas/core/window/expanding.py
+++ b/pandas/core/window/expanding.py
@@ -181,13 +181,13 @@ def mean(self, *args, **kwargs):
     def median(self, **kwargs):
         return super().median(**kwargs)
 
-    @Substitution(name="expanding")
+    @Substitution(name="expanding", versionadded="")
     @Appender(_shared_docs["std"])
     def std(self, ddof=1, *args, **kwargs):
         nv.validate_expanding_func("std", args, kwargs)
         return super().std(ddof=ddof, **kwargs)
 
-    @Substitution(name="expanding")
+    @Substitution(name="expanding", versionadded="")
     @Appender(_shared_docs["var"])
     def var(self, ddof=1, *args, **kwargs):
         nv.validate_expanding_func("var", args, kwargs)
diff --git a/pandas/core/window/rolling.py b/pandas/core/window/rolling.py
--- a/pandas/core/window/rolling.py
+++ b/pandas/core/window/rolling.py
@@ -4,7 +4,7 @@
 """
 from datetime import timedelta
 from textwrap import dedent
-from typing import Callable, List, Optional, Set, Union
+from typing import Callable, Dict, List, Optional, Set, Tuple, Union
 import warnings
 
 import numpy as np
@@ -169,13 +169,30 @@ def __getattr__(self, attr):
     def _dir_additions(self):
         return self.obj._dir_additions()
 
-    def _get_window(self, other=None, **kwargs) -> int:
+    def _get_win_type(self, kwargs: Dict):
         """
-        Returns window length
+        Exists for compatibility, overriden by subclass Window.
 
         Parameters
         ----------
-        other:
+        kwargs : dict
+            ignored, exists for compatibility
+
+        Returns
+        -------
+        None
+        """
+        return None
+
+    def _get_window(self, other=None, win_type: Optional[str] = None) -> int:
+        """
+        Return window length.
+
+        Parameters
+        ----------
+        other :
+            ignored, exists for compatibility
+        win_type :
             ignored, exists for compatibility
 
         Returns
@@ -405,6 +422,7 @@ def _apply(
         -------
         y : type of input
         """
+
         if center is None:
             center = self.center
 
@@ -412,7 +430,8 @@ def _apply(
             check_minp = _use_window
 
         if window is None:
-            window = self._get_window(**kwargs)
+            win_type = self._get_win_type(kwargs)
+            window = self._get_window(win_type=win_type)
 
         blocks, obj = self._create_blocks()
         block_list = list(blocks)
@@ -612,6 +631,126 @@ def aggregate(self, func, *args, **kwargs):
     """
     )
 
+    _shared_docs["var"] = dedent(
+        """
+    Calculate unbiased %(name)s variance.
+    %(versionadded)s
+    Normalized by N-1 by default. This can be changed using the `ddof`
+    argument.
+
+    Parameters
+    ----------
+    ddof : int, default 1
+        Delta Degrees of Freedom.  The divisor used in calculations
+        is ``N - ddof``, where ``N`` represents the number of elements.
+    *args, **kwargs
+        For NumPy compatibility. No additional arguments are used.
+
+    Returns
+    -------
+    Series or DataFrame
+        Returns the same object type as the caller of the %(name)s calculation.
+
+    See Also
+    --------
+    Series.%(name)s : Calling object with Series data.
+    DataFrame.%(name)s : Calling object with DataFrames.
+    Series.var : Equivalent method for Series.
+    DataFrame.var : Equivalent method for DataFrame.
+    numpy.var : Equivalent method for Numpy array.
+
+    Notes
+    -----
+    The default `ddof` of 1 used in :meth:`Series.var` is different than the
+    default `ddof` of 0 in :func:`numpy.var`.
+
+    A minimum of 1 period is required for the rolling calculation.
+
+    Examples
+    --------
+    >>> s = pd.Series([5, 5, 6, 7, 5, 5, 5])
+    >>> s.rolling(3).var()
+    0         NaN
+    1         NaN
+    2    0.333333
+    3    1.000000
+    4    1.000000
+    5    1.333333
+    6    0.000000
+    dtype: float64
+
+    >>> s.expanding(3).var()
+    0         NaN
+    1         NaN
+    2    0.333333
+    3    0.916667
+    4    0.800000
+    5    0.700000
+    6    0.619048
+    dtype: float64
+    """
+    )
+
+    _shared_docs["std"] = dedent(
+        """
+    Calculate %(name)s standard deviation.
+    %(versionadded)s
+    Normalized by N-1 by default. This can be changed using the `ddof`
+    argument.
+
+    Parameters
+    ----------
+    ddof : int, default 1
+        Delta Degrees of Freedom.  The divisor used in calculations
+        is ``N - ddof``, where ``N`` represents the number of elements.
+    *args, **kwargs
+        For NumPy compatibility. No additional arguments are used.
+
+    Returns
+    -------
+    Series or DataFrame
+        Returns the same object type as the caller of the %(name)s calculation.
+
+    See Also
+    --------
+    Series.%(name)s : Calling object with Series data.
+    DataFrame.%(name)s : Calling object with DataFrames.
+    Series.std : Equivalent method for Series.
+    DataFrame.std : Equivalent method for DataFrame.
+    numpy.std : Equivalent method for Numpy array.
+
+    Notes
+    -----
+    The default `ddof` of 1 used in Series.std is different than the default
+    `ddof` of 0 in numpy.std.
+
+    A minimum of one period is required for the rolling calculation.
+
+    Examples
+    --------
+    >>> s = pd.Series([5, 5, 6, 7, 5, 5, 5])
+    >>> s.rolling(3).std()
+    0         NaN
+    1         NaN
+    2    0.577350
+    3    1.000000
+    4    1.000000
+    5    1.154701
+    6    0.000000
+    dtype: float64
+
+    >>> s.expanding(3).std()
+    0         NaN
+    1         NaN
+    2    0.577350
+    3    0.957427
+    4    0.894427
+    5    0.836660
+    6    0.786796
+    dtype: float64
+    """
+    )
+
 
 class Window(_Window):
     """
@@ -783,15 +922,63 @@ def validate(self):
         else:
             raise ValueError("Invalid window {0}".format(window))
 
-    def _get_window(self, other=None, **kwargs) -> np.ndarray:
+    def _get_win_type(self, kwargs: Dict) -> Union[str, Tuple]:
         """
-        Provide validation for the window type, return the window
-        which has already been validated.
+        Extract arguments for the window type, provide validation for it
+        and return the validated window type.
 
         Parameters
         ----------
-        other:
+        kwargs : dict
+
+        Returns
+        -------
+        win_type : str, or tuple
+        """
+        # the below may pop from kwargs
+        def _validate_win_type(win_type, kwargs):
+            arg_map = {
+                "kaiser": ["beta"],
+                "gaussian": ["std"],
+                "general_gaussian": ["power", "width"],
+                "slepian": ["width"],
+                "exponential": ["tau"],
+            }
+
+            if win_type in arg_map:
+                win_args = _pop_args(win_type, arg_map[win_type], kwargs)
+                if win_type == "exponential":
+                    # exponential window requires the first arg (center)
+                    # to be set to None (necessary for symmetric window)
+                    win_args.insert(0, None)
+
+                return tuple([win_type] + win_args)
+
+            return win_type
+
+        def _pop_args(win_type, arg_names, kwargs):
+            msg = "%s window requires %%s" % win_type
+            all_args = []
+            for n in arg_names:
+                if n not in kwargs:
+                    raise ValueError(msg % n)
+                all_args.append(kwargs.pop(n))
+            return all_args
+
+        return _validate_win_type(self.win_type, kwargs)
+
+    def _get_window(
+        self, other=None, win_type: Optional[Union[str, Tuple]] = None
+    ) -> np.ndarray:
+        """
+        Get the window, weights.
+
+        Parameters
+        ----------
+        other :
             ignored, exists for compatibility
+        win_type : str, or tuple
+            type of window to create
 
         Returns
         -------
@@ -805,37 +992,6 @@ def _get_window(self, other=None, **kwargs) -> np.ndarray:
         elif is_integer(window):
             import scipy.signal as sig
 
-            # the below may pop from kwargs
-            def _validate_win_type(win_type, kwargs):
-                arg_map = {
-                    "kaiser": ["beta"],
-                    "gaussian": ["std"],
-                    "general_gaussian": ["power", "width"],
-                    "slepian": ["width"],
-                    "exponential": ["tau"],
-                }
-
-                if win_type in arg_map:
-                    win_args = _pop_args(win_type, arg_map[win_type], kwargs)
-                    if win_type == "exponential":
-                        # exponential window requires the first arg (center)
-                        # to be set to None (necessary for symmetric window)
-                        win_args.insert(0, None)
-
-                    return tuple([win_type] + win_args)
-
-                return win_type
-
-            def _pop_args(win_type, arg_names, kwargs):
-                msg = "%s window requires %%s" % win_type
-                all_args = []
-                for n in arg_names:
-                    if n not in kwargs:
-                        raise ValueError(msg % n)
-                    all_args.append(kwargs.pop(n))
-                return all_args
-
-            win_type = _validate_win_type(self.win_type, kwargs)
             # GH #15662. `False` makes symmetric window, rather than periodic.
             return sig.get_window(win_type, window, False).astype(float)
 
@@ -844,7 +1000,7 @@ def _get_roll_func(
     ) -> Callable:
         def func(arg, window, min_periods=None, closed=None):
             minp = check_minp(min_periods, len(window))
-            return cfunc(arg, window, minp)
+            return cfunc(arg, window, minp, **kwargs)
 
         return func
 
@@ -922,6 +1078,18 @@ def mean(self, *args, **kwargs):
         nv.validate_window_func("mean", args, kwargs)
         return self._apply("roll_weighted_mean", **kwargs)
 
+    @Substitution(name="window", versionadded="\n.. versionadded:: 1.0.0\n")
+    @Appender(_shared_docs["var"])
+    def var(self, ddof=1, *args, **kwargs):
+        nv.validate_window_func("var", args, kwargs)
+        return self._apply("roll_weighted_var", ddof=ddof, **kwargs)
+
+    @Substitution(name="window", versionadded="\n.. versionadded:: 1.0.0\n")
+    @Appender(_shared_docs["std"])
+    def std(self, ddof=1, *args, **kwargs):
+        nv.validate_window_func("std", args, kwargs)
+        return _zsqrt(self.var(ddof=ddof, **kwargs))
+
 
 class _Rolling(_Window):
     @property
@@ -1176,66 +1344,6 @@ def mean(self, *args, **kwargs):
     def median(self, **kwargs):
         return self._apply("roll_median_c", "median", **kwargs)
 
-    _shared_docs["std"] = dedent(
-        """
-    Calculate %(name)s standard deviation.
-
-    Normalized by N-1 by default. This can be changed using the `ddof`
-    argument.
-
-    Parameters
-    ----------
-    ddof : int, default 1
-        Delta Degrees of Freedom.  The divisor used in calculations
-        is ``N - ddof``, where ``N`` represents the number of elements.
-    *args, **kwargs
-        For NumPy compatibility. No additional arguments are used.
-
-    Returns
-    -------
-    Series or DataFrame
-        Returns the same object type as the caller of the %(name)s calculation.
-
-    See Also
-    --------
-    Series.%(name)s : Calling object with Series data.
-    DataFrame.%(name)s : Calling object with DataFrames.
-    Series.std : Equivalent method for Series.
-    DataFrame.std : Equivalent method for DataFrame.
-    numpy.std : Equivalent method for Numpy array.
-
-    Notes
-    -----
-    The default `ddof` of 1 used in Series.std is different than the default
-    `ddof` of 0 in numpy.std.
-
-    A minimum of one period is required for the rolling calculation.
-
-    Examples
-    --------
-    >>> s = pd.Series([5, 5, 6, 7, 5, 5, 5])
-    >>> s.rolling(3).std()
-    0         NaN
-    1         NaN
-    2    0.577350
-    3    1.000000
-    4    1.000000
-    5    1.154701
-    6    0.000000
-    dtype: float64
-
-    >>> s.expanding(3).std()
-    0         NaN
-    1         NaN
-    2    0.577350
-    3    0.957427
-    4    0.894427
-    5    0.836660
-    6    0.786796
-    dtype: float64
-    """
-    )
-
     def std(self, ddof=1, *args, **kwargs):
         nv.validate_window_func("std", args, kwargs)
         window = self._get_window()
@@ -1251,66 +1359,6 @@ def f(arg, *args, **kwargs):
             f, "std", check_minp=_require_min_periods(1), ddof=ddof, **kwargs
         )
 
-    _shared_docs["var"] = dedent(
-        """
-    Calculate unbiased %(name)s variance.
-
-    Normalized by N-1 by default. This can be changed using the `ddof`
-    argument.
-
-    Parameters
-    ----------
-    ddof : int, default 1
-        Delta Degrees of Freedom.  The divisor used in calculations
-        is ``N - ddof``, where ``N`` represents the number of elements.
-    *args, **kwargs
-        For NumPy compatibility. No additional arguments are used.
-
-    Returns
-    -------
-    Series or DataFrame
-        Returns the same object type as the caller of the %(name)s calculation.
-
-    See Also
-    --------
-    Series.%(name)s : Calling object with Series data.
-    DataFrame.%(name)s : Calling object with DataFrames.
-    Series.var : Equivalent method for Series.
-    DataFrame.var : Equivalent method for DataFrame.
-    numpy.var : Equivalent method for Numpy array.
-
-    Notes
-    -----
-    The default `ddof` of 1 used in :meth:`Series.var` is different than the
-    default `ddof` of 0 in :func:`numpy.var`.
-
-    A minimum of 1 period is required for the rolling calculation.
-
-    Examples
-    --------
-    >>> s = pd.Series([5, 5, 6, 7, 5, 5, 5])
-    >>> s.rolling(3).var()
-    0         NaN
-    1         NaN
-    2    0.333333
-    3    1.000000
-    4    1.000000
-    5    1.333333
-    6    0.000000
-    dtype: float64
-
-    >>> s.expanding(3).var()
-    0         NaN
-    1         NaN
-    2    0.333333
-    3    0.916667
-    4    0.800000
-    5    0.700000
-    6    0.619048
-    dtype: float64
-    """
-    )
-
     def var(self, ddof=1, *args, **kwargs):
         nv.validate_window_func("var", args, kwargs)
         return self._apply(
@@ -1845,13 +1893,13 @@ def mean(self, *args, **kwargs):
     def median(self, **kwargs):
         return super().median(**kwargs)
 
-    @Substitution(name="rolling")
+    @Substitution(name="rolling", versionadded="")
     @Appender(_shared_docs["std"])
     def std(self, ddof=1, *args, **kwargs):
         nv.validate_rolling_func("std", args, kwargs)
         return super().std(ddof=ddof, **kwargs)
 
-    @Substitution(name="rolling")
+    @Substitution(name="rolling", versionadded="")
     @Appender(_shared_docs["var"])
     def var(self, ddof=1, *args, **kwargs):
         nv.validate_rolling_func("var", args, kwargs)


Test patch:
diff --git a/pandas/tests/window/test_moments.py b/pandas/tests/window/test_moments.py
--- a/pandas/tests/window/test_moments.py
+++ b/pandas/tests/window/test_moments.py
@@ -119,64 +119,95 @@ def test_cmov_window_corner(self):
         assert len(result) == 5
 
     @td.skip_if_no_scipy
-    def test_cmov_window_frame(self):
+    @pytest.mark.parametrize(
+        "f,xp",
+        [
+            (
+                "mean",
+                [
+                    [np.nan, np.nan],
+                    [np.nan, np.nan],
+                    [9.252, 9.392],
+                    [8.644, 9.906],
+                    [8.87, 10.208],
+                    [6.81, 8.588],
+                    [7.792, 8.644],
+                    [9.05, 7.824],
+                    [np.nan, np.nan],
+                    [np.nan, np.nan],
+                ],
+            ),
+            (
+                "std",
+                [
+                    [np.nan, np.nan],
+                    [np.nan, np.nan],
+                    [3.789706, 4.068313],
+                    [3.429232, 3.237411],
+                    [3.589269, 3.220810],
+                    [3.405195, 2.380655],
+                    [3.281839, 2.369869],
+                    [3.676846, 1.801799],
+                    [np.nan, np.nan],
+                    [np.nan, np.nan],
+                ],
+            ),
+            (
+                "var",
+                [
+                    [np.nan, np.nan],
+                    [np.nan, np.nan],
+                    [14.36187, 16.55117],
+                    [11.75963, 10.48083],
+                    [12.88285, 10.37362],
+                    [11.59535, 5.66752],
+                    [10.77047, 5.61628],
+                    [13.51920, 3.24648],
+                    [np.nan, np.nan],
+                    [np.nan, np.nan],
+                ],
+            ),
+            (
+                "sum",
+                [
+                    [np.nan, np.nan],
+                    [np.nan, np.nan],
+                    [46.26, 46.96],
+                    [43.22, 49.53],
+                    [44.35, 51.04],
+                    [34.05, 42.94],
+                    [38.96, 43.22],
+                    [45.25, 39.12],
+                    [np.nan, np.nan],
+                    [np.nan, np.nan],
+                ],
+            ),
+        ],
+    )
+    def test_cmov_window_frame(self, f, xp):
         # Gh 8238
-        vals = np.array(
-            [
-                [12.18, 3.64],
-                [10.18, 9.16],
-                [13.24, 14.61],
-                [4.51, 8.11],
-                [6.15, 11.44],
-                [9.14, 6.21],
-                [11.31, 10.67],
-                [2.94, 6.51],
-                [9.42, 8.39],
-                [12.44, 7.34],
-            ]
-        )
-
-        xp = np.array(
-            [
-                [np.nan, np.nan],
-                [np.nan, np.nan],
-                [9.252, 9.392],
-                [8.644, 9.906],
-                [8.87, 10.208],
-                [6.81, 8.588],
-                [7.792, 8.644],
-                [9.05, 7.824],
-                [np.nan, np.nan],
-                [np.nan, np.nan],
-            ]
+        df = DataFrame(
+            np.array(
+                [
+                    [12.18, 3.64],
+                    [10.18, 9.16],
+                    [13.24, 14.61],
+                    [4.51, 8.11],
+                    [6.15, 11.44],
+                    [9.14, 6.21],
+                    [11.31, 10.67],
+                    [2.94, 6.51],
+                    [9.42, 8.39],
+                    [12.44, 7.34],
+                ]
+            )
         )
+        xp = DataFrame(np.array(xp))
 
-        # DataFrame
-        rs = DataFrame(vals).rolling(5, win_type="boxcar", center=True).mean()
-        tm.assert_frame_equal(DataFrame(xp), rs)
-
-        # invalid method
-        with pytest.raises(AttributeError):
-            (DataFrame(vals).rolling(5, win_type="boxcar", center=True).std())
-
-        # sum
-        xp = np.array(
-            [
-                [np.nan, np.nan],
-                [np.nan, np.nan],
-                [46.26, 46.96],
-                [43.22, 49.53],
-                [44.35, 51.04],
-                [34.05, 42.94],
-                [38.96, 43.22],
-                [45.25, 39.12],
-                [np.nan, np.nan],
-                [np.nan, np.nan],
-            ]
-        )
+        roll = df.rolling(5, win_type="boxcar", center=True)
+        rs = getattr(roll, f)()
 
-        rs = DataFrame(vals).rolling(5, win_type="boxcar", center=True).sum()
-        tm.assert_frame_equal(DataFrame(xp), rs)
+        tm.assert_frame_equal(xp, rs)
 
     @td.skip_if_no_scipy
     def test_cmov_window_na_min_periods(self):
diff --git a/pandas/tests/window/test_window.py b/pandas/tests/window/test_window.py
--- a/pandas/tests/window/test_window.py
+++ b/pandas/tests/window/test_window.py
@@ -60,7 +60,7 @@ def test_numpy_compat(self, method):
             getattr(w, method)(dtype=np.float64)
 
     @td.skip_if_no_scipy
-    @pytest.mark.parametrize("arg", ["median", "var", "std", "kurt", "skew"])
+    @pytest.mark.parametrize("arg", ["median", "kurt", "skew"])
     def test_agg_function_support(self, arg):
         df = pd.DataFrame({"A": np.arange(5)})
         roll = df.rolling(2, win_type="triang")