Repository: modin-project/modin

Issue:
resample function implementation



Discussion:

Resolving patch:
diff --git a/modin/backends/pandas/query_compiler.py b/modin/backends/pandas/query_compiler.py
--- a/modin/backends/pandas/query_compiler.py
+++ b/modin/backends/pandas/query_compiler.py
@@ -630,6 +630,197 @@ def sort_index_for_equal_values(result, ascending):
 
     # END Reduction operations
 
+    def _resample_func(
+        self, resample_args, func_name, new_columns=None, df_op=None, *args, **kwargs
+    ):
+        def map_func(df, resample_args=resample_args):
+            if df_op is not None:
+                df = df_op(df)
+            resampled_val = df.resample(*resample_args)
+            op = getattr(pandas.core.resample.Resampler, func_name)
+            if callable(op):
+                try:
+                    # This will happen with Arrow buffer read-only errors. We don't want to copy
+                    # all the time, so this will try to fast-path the code first.
+                    val = op(resampled_val, *args, **kwargs)
+                except (ValueError):
+                    resampled_val = df.copy().resample(*resample_args)
+                    val = op(resampled_val, *args, **kwargs)
+            else:
+                val = getattr(resampled_val, func_name)
+
+            if isinstance(val, pandas.Series):
+                return val.to_frame()
+            else:
+                return val
+
+        new_modin_frame = self._modin_frame._apply_full_axis(
+            axis=0, func=map_func, new_columns=new_columns
+        )
+        return self.__constructor__(new_modin_frame)
+
+    def resample_get_group(self, resample_args, name, obj):
+        return self._resample_func(resample_args, "get_group", name=name, obj=obj)
+
+    def resample_app_ser(self, resample_args, func, *args, **kwargs):
+        return self._resample_func(
+            resample_args,
+            "apply",
+            df_op=lambda df: df.squeeze(axis=1),
+            func=func,
+            *args,
+            **kwargs
+        )
+
+    def resample_app_df(self, resample_args, func, *args, **kwargs):
+        return self._resample_func(resample_args, "apply", func=func, *args, **kwargs)
+
+    def resample_agg_ser(self, resample_args, func, *args, **kwargs):
+        return self._resample_func(
+            resample_args,
+            "aggregate",
+            df_op=lambda df: df.squeeze(axis=1),
+            func=func,
+            *args,
+            **kwargs
+        )
+
+    def resample_agg_df(self, resample_args, func, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "aggregate", func=func, *args, **kwargs
+        )
+
+    def resample_transform(self, resample_args, arg, *args, **kwargs):
+        return self._resample_func(resample_args, "transform", arg=arg, *args, **kwargs)
+
+    def resample_pipe(self, resample_args, func, *args, **kwargs):
+        return self._resample_func(resample_args, "pipe", func=func, *args, **kwargs)
+
+    def resample_ffill(self, resample_args, limit):
+        return self._resample_func(resample_args, "ffill", limit=limit)
+
+    def resample_backfill(self, resample_args, limit):
+        return self._resample_func(resample_args, "backfill", limit=limit)
+
+    def resample_bfill(self, resample_args, limit):
+        return self._resample_func(resample_args, "bfill", limit=limit)
+
+    def resample_pad(self, resample_args, limit):
+        return self._resample_func(resample_args, "pad", limit=limit)
+
+    def resample_nearest(self, resample_args, limit):
+        return self._resample_func(resample_args, "nearest", limit=limit)
+
+    def resample_fillna(self, resample_args, method, limit):
+        return self._resample_func(resample_args, "fillna", method=method, limit=limit)
+
+    def resample_asfreq(self, resample_args, fill_value):
+        return self._resample_func(resample_args, "asfreq", fill_value=fill_value)
+
+    def resample_interpolate(
+        self,
+        resample_args,
+        method,
+        axis,
+        limit,
+        inplace,
+        limit_direction,
+        limit_area,
+        downcast,
+        **kwargs
+    ):
+        return self._resample_func(
+            resample_args,
+            "interpolate",
+            axis=axis,
+            limit=limit,
+            inplace=inplace,
+            limit_direction=limit_direction,
+            limit_area=limit_area,
+            downcast=downcast,
+            **kwargs
+        )
+
+    def resample_count(self, resample_args):
+        return self._resample_func(resample_args, "count")
+
+    def resample_nunique(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "nunique", _method=_method, *args, **kwargs
+        )
+
+    def resample_first(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "first", _method=_method, *args, **kwargs
+        )
+
+    def resample_last(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "last", _method=_method, *args, **kwargs
+        )
+
+    def resample_max(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "max", _method=_method, *args, **kwargs
+        )
+
+    def resample_mean(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "median", _method=_method, *args, **kwargs
+        )
+
+    def resample_median(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "median", _method=_method, *args, **kwargs
+        )
+
+    def resample_min(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "min", _method=_method, *args, **kwargs
+        )
+
+    def resample_ohlc_ser(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args,
+            "ohlc",
+            df_op=lambda df: df.squeeze(axis=1),
+            _method=_method,
+            *args,
+            **kwargs
+        )
+
+    def resample_ohlc_df(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "ohlc", _method=_method, *args, **kwargs
+        )
+
+    def resample_prod(self, resample_args, _method, min_count, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "prod", _method=_method, min_count=min_count, *args, **kwargs
+        )
+
+    def resample_size(self, resample_args):
+        return self._resample_func(resample_args, "size", new_columns=["__reduced__"])
+
+    def resample_sem(self, resample_args, _method, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "sem", _method=_method, *args, **kwargs
+        )
+
+    def resample_std(self, resample_args, ddof, *args, **kwargs):
+        return self._resample_func(resample_args, "std", ddof=ddof, *args, **kwargs)
+
+    def resample_sum(self, resample_args, _method, min_count, *args, **kwargs):
+        return self._resample_func(
+            resample_args, "sum", _method=_method, min_count=min_count, *args, **kwargs
+        )
+
+    def resample_var(self, resample_args, ddof, *args, **kwargs):
+        return self._resample_func(resample_args, "var", ddof=ddof, *args, **kwargs)
+
+    def resample_quantile(self, resample_args, q, **kwargs):
+        return self._resample_func(resample_args, "quantile", q=q, **kwargs)
+
     # Map partitions operations
     # These operations are operations that apply a function to every partition.
     abs = MapFunction.register(pandas.DataFrame.abs, dtypes="copy")
diff --git a/modin/pandas/base.py b/modin/pandas/base.py
--- a/modin/pandas/base.py
+++ b/modin/pandas/base.py
@@ -2194,9 +2194,54 @@ def resample(
         on=None,
         level=None,
     ):
-        return self._default_to_pandas(
-            "resample",
-            rule,
+        """
+        Resample time-series data.
+        Convenience method for frequency conversion and resampling of time
+        series. Object must have a datetime-like index (`DatetimeIndex`,
+        `PeriodIndex`, or `TimedeltaIndex`), or pass datetime-like values
+        to the `on` or `level` keyword.
+        Parameters
+        ----------
+        rule : DateOffset, Timedelta or str
+            The offset string or object representing target conversion.
+        axis : {0 or 'index', 1 or 'columns'}, default 0
+            Which axis to use for up- or down-sampling. For `Series` this
+            will default to 0, i.e. along the rows. Must be
+            `DatetimeIndex`, `TimedeltaIndex` or `PeriodIndex`.
+        closed : {'right', 'left'}, default None
+            Which side of bin interval is closed. The default is 'left'
+            for all frequency offsets except for 'M', 'A', 'Q', 'BM',
+            'BA', 'BQ', and 'W' which all have a default of 'right'.
+        label : {'right', 'left'}, default None
+            Which bin edge label to label bucket with. The default is 'left'
+            for all frequency offsets except for 'M', 'A', 'Q', 'BM',
+            'BA', 'BQ', and 'W' which all have a default of 'right'.
+        convention : {'start', 'end', 's', 'e'}, default 'start'
+            For `PeriodIndex` only, controls whether to use the start or
+            end of `rule`.
+        kind : {'timestamp', 'period'}, optional, default None
+            Pass 'timestamp' to convert the resulting index to a
+            `DateTimeIndex` or 'period' to convert it to a `PeriodIndex`.
+            By default the input representation is retained.
+        loffset : timedelta, default None
+            Adjust the resampled time labels.
+        base : int, default 0
+            For frequencies that evenly subdivide 1 day, the "origin" of the
+            aggregated intervals. For example, for '5min' frequency, base could
+            range from 0 through 4. Defaults to 0.
+        on : str, optional
+            For a DataFrame, column to use instead of index for resampling.
+            Column must be datetime-like.
+        level : str or int, optional
+            For a MultiIndex, level (name or number) to use for
+            resampling. `level` must be datetime-like.
+        Returns
+        -------
+        Resampler object
+        """
+        return Resampler(
+            self,
+            rule=rule,
             axis=axis,
             closed=closed,
             label=label,
@@ -3529,3 +3574,319 @@ def default_handler(*args, **kwargs):
 
                 return default_handler
         return object.__getattribute__(self, item)
+
+
+class Resampler(object):
+    def __init__(
+        self,
+        dataframe,
+        rule,
+        axis=0,
+        closed=None,
+        label=None,
+        convention="start",
+        kind=None,
+        loffset=None,
+        base=0,
+        on=None,
+        level=None,
+    ):
+        self._dataframe = dataframe
+        self._query_compiler = dataframe._query_compiler
+        self.resample_args = [
+            rule,
+            axis,
+            closed,
+            label,
+            convention,
+            kind,
+            loffset,
+            base,
+            on,
+            level,
+        ]
+
+        self.__groups = self.__get_groups(*self.resample_args)
+
+    def __get_groups(
+        self, rule, axis, closed, label, convention, kind, loffset, base, on, level
+    ):
+        if axis == 0 or axis == "index":
+            df = self._dataframe
+        else:
+            df = self._dataframe.T
+        groups = df.groupby(
+            pandas.Grouper(
+                key=on,
+                freq=rule,
+                closed=closed,
+                label=label,
+                convention=convention,
+                loffset=loffset,
+                base=base,
+                level=level,
+            )
+        )
+        return groups
+
+    @property
+    def groups(self):
+        return self._query_compiler.default_to_pandas(
+            lambda df: pandas.DataFrame.resample(df, *self.resample_args).groups
+        )
+
+    @property
+    def indices(self):
+        return self._query_compiler.default_to_pandas(
+            lambda df: pandas.DataFrame.resample(df, *self.resample_args).indices
+        )
+
+    def get_group(self, name, obj=None):
+        if self.resample_args[1] == 0 or self.resample_args[1] == "index":
+            result = self.__groups.get_group(name)
+        else:
+            result = self.__groups.get_group(name).T
+        return result
+
+    def apply(self, func, *args, **kwargs):
+        from .dataframe import DataFrame
+
+        if isinstance(self._dataframe, DataFrame):
+            query_comp_op = self._query_compiler.resample_app_df
+        else:
+            query_comp_op = self._query_compiler.resample_app_ser
+
+        dataframe = DataFrame(
+            query_compiler=query_comp_op(self.resample_args, func, *args, **kwargs,)
+        )
+        if is_list_like(func) or isinstance(self._dataframe, DataFrame):
+            return dataframe
+        else:
+            if len(dataframe.index) == 1:
+                return dataframe.iloc[0]
+            else:
+                return dataframe.squeeze()
+
+    def aggregate(self, func, *args, **kwargs):
+        from .dataframe import DataFrame
+
+        if isinstance(self._dataframe, DataFrame):
+            query_comp_op = self._query_compiler.resample_agg_df
+        else:
+            query_comp_op = self._query_compiler.resample_agg_ser
+
+        dataframe = DataFrame(
+            query_compiler=query_comp_op(self.resample_args, func, *args, **kwargs,)
+        )
+        if is_list_like(func) or isinstance(self._dataframe, DataFrame):
+            return dataframe
+        else:
+            if len(dataframe.index) == 1:
+                return dataframe.iloc[0]
+            else:
+                return dataframe.squeeze()
+
+    def transform(self, arg, *args, **kwargs):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_transform(
+                self.resample_args, arg, *args, **kwargs
+            )
+        )
+
+    def pipe(self, func, *args, **kwargs):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_pipe(
+                self.resample_args, func, *args, **kwargs
+            )
+        )
+
+    def ffill(self, limit=None):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_ffill(
+                self.resample_args, limit
+            )
+        )
+
+    def backfill(self, limit=None):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_backfill(
+                self.resample_args, limit
+            )
+        )
+
+    def bfill(self, limit=None):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_bfill(
+                self.resample_args, limit
+            )
+        )
+
+    def pad(self, limit=None):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_pad(self.resample_args, limit)
+        )
+
+    def nearest(self, limit=None):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_nearest(
+                self.resample_args, limit
+            )
+        )
+
+    def fillna(self, method, limit=None):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_fillna(
+                self.resample_args, method, limit
+            )
+        )
+
+    def asfreq(self, fill_value=None):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_asfreq(
+                self.resample_args, fill_value
+            )
+        )
+
+    def interpolate(
+        self,
+        method="linear",
+        axis=0,
+        limit=None,
+        inplace=False,
+        limit_direction="forward",
+        limit_area=None,
+        downcast=None,
+        **kwargs,
+    ):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_interpolate(
+                self.resample_args,
+                method,
+                axis,
+                limit,
+                inplace,
+                limit_direction,
+                limit_area,
+                downcast,
+                **kwargs,
+            )
+        )
+
+    def count(self):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_count(self.resample_args)
+        )
+
+    def nunique(self, _method="nunique", *args, **kwargs):
+        if self.resample_args[1] == 0 or self.resample_args[1] == "index":
+            result = self.__groups.nunique(*args, **kwargs)
+        else:
+            result = self.__groups.nunique(*args, **kwargs).T
+        return result
+
+    def first(self, _method="first", *args, **kwargs):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_first(
+                self.resample_args, _method, *args, **kwargs,
+            )
+        )
+
+    def last(self, _method="last", *args, **kwargs):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_last(
+                self.resample_args, _method, *args, **kwargs,
+            )
+        )
+
+    def max(self, _method="max", *args, **kwargs):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_max(
+                self.resample_args, _method, *args, **kwargs,
+            )
+        )
+
+    def mean(self, _method="mean", *args, **kwargs):
+        if self.resample_args[1] == 0 or self.resample_args[1] == "index":
+            result = self.__groups.mean(*args, **kwargs)
+        else:
+            result = self.__groups.mean(*args, **kwargs).T
+        return result
+
+    def median(self, _method="median", *args, **kwargs):
+        if self.resample_args[1] == 0 or self.resample_args[1] == "index":
+            result = self.__groups.median(*args, **kwargs)
+        else:
+            result = self.__groups.median(*args, **kwargs).T
+        return result
+
+    def min(self, _method="min", *args, **kwargs):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_min(
+                self.resample_args, _method, *args, **kwargs,
+            )
+        )
+
+    def ohlc(self, _method="ohlc", *args, **kwargs):
+        from .dataframe import DataFrame
+
+        if isinstance(self._dataframe, DataFrame):
+            return DataFrame(
+                query_compiler=self._query_compiler.resample_ohlc_df(
+                    self.resample_args, _method, *args, **kwargs,
+                )
+            )
+        else:
+            return DataFrame(
+                query_compiler=self._query_compiler.resample_ohlc_ser(
+                    self.resample_args, _method, *args, **kwargs,
+                )
+            )
+
+    def prod(self, _method="prod", min_count=0, *args, **kwargs):
+        if self.resample_args[1] == 0 or self.resample_args[1] == "index":
+            result = self.__groups.prod(min_count=min_count, *args, **kwargs)
+        else:
+            result = self.__groups.prod(min_count=min_count, *args, **kwargs).T
+        return result
+
+    def size(self):
+        from .series import Series
+
+        return Series(
+            query_compiler=self._query_compiler.resample_size(self.resample_args)
+        )
+
+    def sem(self, _method="sem", *args, **kwargs):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_sem(
+                self.resample_args, _method, *args, **kwargs,
+            )
+        )
+
+    def std(self, ddof=1, *args, **kwargs):
+        if self.resample_args[1] == 0 or self.resample_args[1] == "index":
+            result = self.__groups.std(ddof=ddof, *args, **kwargs)
+        else:
+            result = self.__groups.std(ddof=ddof, *args, **kwargs).T
+        return result
+
+    def sum(self, _method="sum", min_count=0, *args, **kwargs):
+        if self.resample_args[1] == 0 or self.resample_args[1] == "index":
+            result = self.__groups.sum(min_count=min_count, *args, **kwargs)
+        else:
+            result = self.__groups.sum(min_count=min_count, *args, **kwargs).T
+        return result
+
+    def var(self, ddof=1, *args, **kwargs):
+        if self.resample_args[1] == 0 or self.resample_args[1] == "index":
+            result = self.__groups.var(ddof=ddof, *args, **kwargs)
+        else:
+            result = self.__groups.var(ddof=ddof, *args, **kwargs).T
+        return result
+
+    def quantile(self, q=0.5, **kwargs):
+        return self._dataframe.__constructor__(
+            query_compiler=self._query_compiler.resample_quantile(
+                self.resample_args, q, **kwargs
+            )
+        )


Test patch:
diff --git a/modin/pandas/test/test_dataframe.py b/modin/pandas/test/test_dataframe.py
--- a/modin/pandas/test/test_dataframe.py
+++ b/modin/pandas/test/test_dataframe.py
@@ -2446,17 +2446,108 @@ def test_replace(self):
         with pytest.warns(UserWarning):
             pd.DataFrame(data).replace()
 
-    def test_resample(self):
-        d = dict(
-            {
-                "price": [10, 11, 9, 13, 14, 18, 17, 19],
-                "volume": [50, 60, 40, 100, 50, 100, 40, 50],
-            }
+    @pytest.mark.parametrize("rule", ["5T", pandas.offsets.Hour()])
+    @pytest.mark.parametrize("axis", [0, "columns"])
+    @pytest.mark.parametrize("closed", ["left", "right"])
+    @pytest.mark.parametrize("label", ["right", "left"])
+    @pytest.mark.parametrize("on", [None, "DateColumn"])
+    @pytest.mark.parametrize("level", [None, 1])
+    def test_resample(self, rule, axis, closed, label, on, level):
+        freq = "H"
+        base = 2
+        index = pandas.date_range("31/12/2000", periods=12, freq=freq)
+        data = {"A": range(12), "B": range(12)}
+
+        pandas_df = pandas.DataFrame(data, index=index)
+        modin_df = pd.DataFrame(data, index=index)
+
+        if on is not None and axis == 0:
+            pandas_df[on] = pandas.date_range("22/06/1941", periods=12, freq="T")
+            modin_df[on] = pandas.date_range("22/06/1941", periods=12, freq="T")
+        else:
+            on = None
+
+        if axis == "columns":
+            pandas_df = pandas_df.T
+            modin_df = modin_df.T
+
+        if level is not None and axis == 0 and on is None:
+            index = pandas.MultiIndex.from_product(
+                [["a", "b", "c"], pandas.date_range("31/12/2000", periods=4, freq=freq)]
+            )
+            pandas_df.index = index
+            modin_df.index = index
+        else:
+            level = None
+
+        pandas_resampler = pandas_df.resample(
+            rule, axis=axis, closed=closed, label=label, base=base, on=on, level=level
+        )
+        modin_resampler = modin_df.resample(
+            rule, axis=axis, closed=closed, label=label, base=base, on=on, level=level
+        )
+
+        df_equals(modin_resampler.count(), pandas_resampler.count())
+        df_equals(modin_resampler.var(0), pandas_resampler.var(0))
+        df_equals(modin_resampler.sum(), pandas_resampler.sum())
+        df_equals(modin_resampler.std(), pandas_resampler.std())
+        df_equals(modin_resampler.sem(), pandas_resampler.sem())
+        df_equals(modin_resampler.size(), pandas_resampler.size())
+        df_equals(modin_resampler.prod(), pandas_resampler.prod())
+        if on is None:
+            df_equals(modin_resampler.ohlc(), pandas_resampler.ohlc())
+        df_equals(modin_resampler.min(), pandas_resampler.min())
+        df_equals(modin_resampler.median(), pandas_resampler.median())
+        df_equals(modin_resampler.mean(), pandas_resampler.mean())
+        df_equals(modin_resampler.max(), pandas_resampler.max())
+        df_equals(modin_resampler.last(), pandas_resampler.last())
+        df_equals(modin_resampler.first(), pandas_resampler.first())
+        df_equals(modin_resampler.nunique(), pandas_resampler.nunique())
+        df_equals(
+            modin_resampler.pipe(lambda x: x.max() - x.min()),
+            pandas_resampler.pipe(lambda x: x.max() - x.min()),
         )
-        df = pd.DataFrame(d)
-        df["week_starting"] = pd.date_range("01/01/2018", periods=8, freq="W")
-        with pytest.warns(UserWarning):
-            df.resample("M", on="week_starting")
+        df_equals(
+            modin_resampler.transform(lambda x: (x - x.mean()) / x.std()),
+            pandas_resampler.transform(lambda x: (x - x.mean()) / x.std()),
+        )
+        df_equals(
+            pandas_resampler.aggregate("max"), modin_resampler.aggregate("max"),
+        )
+        df_equals(
+            modin_resampler.apply("sum"), pandas_resampler.apply("sum"),
+        )
+        df_equals(
+            modin_resampler.get_group(name=list(modin_resampler.groups)[0]),
+            pandas_resampler.get_group(name=list(pandas_resampler.groups)[0]),
+        )
+        assert pandas_resampler.indices == modin_resampler.indices
+        assert pandas_resampler.groups == modin_resampler.groups
+        df_equals(modin_resampler.quantile(), pandas_resampler.quantile())
+        if axis == 0:
+            # Upsampling from level= or on= selection is not supported
+            if on is None and level is None:
+                df_equals(
+                    modin_resampler.interpolate(), pandas_resampler.interpolate(),
+                )
+                df_equals(modin_resampler.asfreq(), pandas_resampler.asfreq())
+                df_equals(
+                    modin_resampler.fillna(method="nearest"),
+                    pandas_resampler.fillna(method="nearest"),
+                )
+                df_equals(modin_resampler.pad(), pandas_resampler.pad())
+                df_equals(modin_resampler.nearest(), pandas_resampler.nearest())
+                df_equals(modin_resampler.bfill(), pandas_resampler.bfill())
+                df_equals(modin_resampler.backfill(), pandas_resampler.backfill())
+                df_equals(modin_resampler.ffill(), pandas_resampler.ffill())
+            df_equals(
+                pandas_resampler.apply(["sum", "mean", "max"]),
+                modin_resampler.apply(["sum", "mean", "max"]),
+            )
+            df_equals(
+                modin_resampler.aggregate(["sum", "mean", "max"]),
+                pandas_resampler.aggregate(["sum", "mean", "max"]),
+            )
 
     def test_rolling(self):
         df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})
diff --git a/modin/pandas/test/test_series.py b/modin/pandas/test/test_series.py
--- a/modin/pandas/test/test_series.py
+++ b/modin/pandas/test/test_series.py
@@ -2306,13 +2306,90 @@ def test_replace(data):
         modin_series.replace(0, 5)
 
 
-def test_resample():
-    modin_series = pd.Series(
-        [10, 11, 9, 13, 14, 18, 17, 19],
-        index=pd.date_range("01/01/2018", periods=8, freq="W"),
+@pytest.mark.parametrize("closed", ["left", "right"])
+@pytest.mark.parametrize("label", ["right", "left"])
+@pytest.mark.parametrize("level", [None, 1])
+def test_resample(closed, label, level):
+    rule = "5T"
+    freq = "H"
+    base = 2
+
+    index = pandas.date_range("1/1/2000", periods=12, freq=freq)
+    pandas_series = pandas.Series(range(12), index=index)
+    modin_series = pd.Series(range(12), index=index)
+
+    if level is not None:
+        index = pandas.MultiIndex.from_product(
+            [["a", "b", "c"], pandas.date_range("31/12/2000", periods=4, freq=freq)]
+        )
+        pandas_series.index = index
+        modin_series.index = index
+    pandas_resampler = pandas_series.resample(
+        rule, closed=closed, label=label, base=base, level=level
+    )
+    modin_resampler = modin_series.resample(
+        rule, closed=closed, label=label, base=base, level=level
+    )
+
+    df_equals(modin_resampler.count(), pandas_resampler.count())
+    df_equals(modin_resampler.var(0), pandas_resampler.var(0))
+    df_equals(modin_resampler.sum(), pandas_resampler.sum())
+    df_equals(modin_resampler.std(), pandas_resampler.std())
+    df_equals(modin_resampler.sem(), pandas_resampler.sem())
+    df_equals(modin_resampler.size(), pandas_resampler.size())
+    df_equals(modin_resampler.prod(), pandas_resampler.prod())
+    df_equals(modin_resampler.ohlc(), pandas_resampler.ohlc())
+    df_equals(modin_resampler.min(), pandas_resampler.min())
+    df_equals(modin_resampler.median(), pandas_resampler.median())
+    df_equals(modin_resampler.mean(), pandas_resampler.mean())
+    df_equals(modin_resampler.max(), pandas_resampler.max())
+    df_equals(modin_resampler.last(), pandas_resampler.last())
+    df_equals(modin_resampler.first(), pandas_resampler.first())
+    df_equals(modin_resampler.nunique(), pandas_resampler.nunique())
+    df_equals(
+        modin_resampler.pipe(lambda x: x.max() - x.min()),
+        pandas_resampler.pipe(lambda x: x.max() - x.min()),
+    )
+    df_equals(
+        modin_resampler.transform(lambda x: (x - x.mean()) / x.std()),
+        pandas_resampler.transform(lambda x: (x - x.mean()) / x.std()),
+    )
+    df_equals(
+        pandas_resampler.aggregate("max"), modin_resampler.aggregate("max"),
+    )
+    df_equals(
+        modin_resampler.apply("sum"), pandas_resampler.apply("sum"),
+    )
+    df_equals(
+        modin_resampler.get_group(name=list(modin_resampler.groups)[0]),
+        pandas_resampler.get_group(name=list(pandas_resampler.groups)[0]),
+    )
+    assert pandas_resampler.indices == modin_resampler.indices
+    assert pandas_resampler.groups == modin_resampler.groups
+    df_equals(modin_resampler.quantile(), pandas_resampler.quantile())
+    # Upsampling from level= or on= selection is not supported
+    if level is None:
+        df_equals(
+            modin_resampler.interpolate(), pandas_resampler.interpolate(),
+        )
+        df_equals(modin_resampler.asfreq(), pandas_resampler.asfreq())
+        df_equals(
+            modin_resampler.fillna(method="nearest"),
+            pandas_resampler.fillna(method="nearest"),
+        )
+        df_equals(modin_resampler.pad(), pandas_resampler.pad())
+        df_equals(modin_resampler.nearest(), pandas_resampler.nearest())
+        df_equals(modin_resampler.bfill(), pandas_resampler.bfill())
+        df_equals(modin_resampler.backfill(), pandas_resampler.backfill())
+        df_equals(modin_resampler.ffill(), pandas_resampler.ffill())
+    df_equals(
+        pandas_resampler.apply(["sum", "mean", "max"]),
+        modin_resampler.apply(["sum", "mean", "max"]),
+    )
+    df_equals(
+        modin_resampler.aggregate(["sum", "mean", "max"]),
+        pandas_resampler.aggregate(["sum", "mean", "max"]),
     )
-    with pytest.warns(UserWarning):
-        modin_series.resample("M")
 
 
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)