Repository: KIT-IAI/pyWATTS

Issue:
EWMA option for rolling_mean
**What is your feature request related to?**
* Module request
**Please describe the module/problem**

Enable a EWMA Profile. Thus, add an alpha value to the rolling_mean.


**What type of Feature Request**
* Module request,


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e43c40..b5faf26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
 # Changelog
+## 0.3.1 Unreleased
+
+### Added
+* Add alpha to rolling mean ([#225](https://github.com/KIT-IAI/pyWATTS/issues/225))
+
 
 ## 0.3.0 - 2022-31-03
 
diff --git a/pywatts/modules/feature_extraction/rolling_mean.py b/pywatts/modules/feature_extraction/rolling_mean.py
index e7f76f8..99bb27b 100644
--- a/pywatts/modules/feature_extraction/rolling_mean.py
+++ b/pywatts/modules/feature_extraction/rolling_mean.py
@@ -1,4 +1,8 @@
-from pywatts.modules.feature_extraction.rolling_base import RollingBase
+from typing import Optional, Dict
+
+import pandas as pd
+
+from pywatts.modules.feature_extraction.rolling_base import RollingBase, RollingGroupBy
 
 
 class RollingMean(RollingBase):
@@ -23,7 +27,54 @@ class RollingMean(RollingBase):
      :type country: str
      :param closed: If there array is closed left or right
      :type closed: str
+     :param alpha: alpha value for weighting the most recent value if exponential weighting should be applied. If alpha
+                   is not set, then the mean of the sliding window is calculated.
+    :type alpha: float
     """
 
+    def __init__(self, name: str = "RollingMean", window_size=24 * 7, window_size_unit="d",
+                 group_by: RollingGroupBy = RollingGroupBy.No, continent: str = "Europe",
+                 country: str = "Germany", closed="left", alpha=None):
+        super().__init__(name=name, window_size=window_size, window_size_unit=window_size_unit, group_by=group_by,
+                         continent=continent, country=country, closed=closed)
+        self.alpha = alpha
+
     def _get_rolling(self, df):
-        return df.rolling(f"{self.window_size}{self.window_size_unit}", closed=self.closed).mean()
+        if self.alpha:
+            ewm = lambda x: pd.Series(x).ewm(alpha=self.alpha).mean().iloc[-1]
+            return df.rolling(f"{self.window_size}{self.window_size_unit}",closed=self.closed).apply(ewm)
+        else:
+            return df.rolling(f"{self.window_size}{self.window_size_unit}", closed=self.closed).mean()
+
+
+    def get_params(self) -> Dict[str, object]:
+        """
+        Get the parameters of the rolling mean module as dict
+        """
+        params = super(RollingMean, self).get_params()
+        params["alpha"] = self.alpha
+        return params
+
+    def set_params(self, alpha: Optional[float] = None, **kwargs):
+        """
+        Set parameters of the rolling mean
+        :param window_size: Window size for which to calculate the mean
+        :type window_size: int
+        :param window_size_unit: Unit of the window size (default: "d" [day])
+        :type window_size_unit: str
+        :param groupy_by: how the entries of the time series should be grouped
+        :type group_by. RollingGroupBy
+        :param continent: If group_by is WorkdayAndHoliday: Continent where the country or region is located
+                          (important for importing calendar module).
+        :type continent: str
+        :param country: If group_by is WorkdayAndHoliday: Country or region to use for holiday calendar (default 'Germany')
+        :type country: str
+        :param closed: If there array is closed left or right
+        :type closed: str
+        :param alpha: alpha value for weighting the most recent value if exponential weighting should be applied. If alpha
+                   is not set, then the mean of the sliding window is calculated.
+        :type alpha: float
+        """
+        if alpha:
+            self.alpha = alpha
+        super(RollingMean, self).set_params(**kwargs)


Test patch:
diff --git a/tests/unit/modules/test_rolling_mean.py b/tests/unit/modules/test_rolling_mean.py
index 36ebc1c..5468dc7 100644
--- a/tests/unit/modules/test_rolling_mean.py
+++ b/tests/unit/modules/test_rolling_mean.py
@@ -18,6 +18,7 @@ class TestRollingMean(unittest.TestCase):
             self.rolling_mean.get_params(),
             {
                 "window_size": 3,
+                "alpha":None,
                 "window_size_unit": "d",
                 "group_by": RollingGroupBy.No,
                 "country": "Germany",
@@ -33,10 +34,11 @@ class TestRollingMean(unittest.TestCase):
                 "window_size_unit": "d",
                 "group_by": RollingGroupBy.No,
                 "country": "Germany",
-                "continent": "Europe"
+                "continent": "Europe",
+                "alpha": None
             }
         )
-        self.rolling_mean.set_params(window_size=5)
+        self.rolling_mean.set_params(window_size=5, alpha=0.4)
         self.assertEqual(
             self.rolling_mean.get_params(),
             {
@@ -44,7 +46,8 @@ class TestRollingMean(unittest.TestCase):
                 "window_size_unit": "d",
                 "group_by": RollingGroupBy.No,
                 "country": "Germany",
-                "continent": "Europe"
+                "continent": "Europe",
+                "alpha":0.4
             }
         )
 
@@ -59,6 +62,18 @@ class TestRollingMean(unittest.TestCase):
 
         xr.testing.assert_equal(result, expected_result)
 
+    def test_transform_groupbyNo_alpha(self):
+        time = pd.date_range('2002-01-01', freq='24H', periods=7)
+        self.rolling_mean.set_params(alpha=0.5)
+
+        ds = xr.DataArray([1, 2, 4, 8, 16, 32, 64], dims=["time"], coords={'time': time})
+        result = self.rolling_mean.transform(ds)
+
+        # Note we use an adjustment and not the standard recursive variant for calculating the EWMA
+        expected_result = xr.DataArray([0., 1, 2.5/1.5, 5.25/1.75, 6., 12., 24.], dims=["time"], coords={'time': time})
+
+        xr.testing.assert_equal(result, expected_result)
+
     def test_transform_groupbyWeekendWeekday(self):
         self.rolling_mean.set_params(group_by=RollingGroupBy.WorkdayWeekend)
         time = pd.date_range('2002-01-01', freq='12H', periods=14)