Repository: modin-project/modin
Pull request: TEST-#5378: port stack, unstack, replace and groups benchmarks from pandas
<!--
Thank you for your contribution!
Please review the contributing docs: https://modin.readthedocs.io/en/latest/development/contributing.html
if you have questions about contributing.
-->

## What do these changes do?

Adding 3 benchmark classes from pandas benchmark TimeSimpleReshape , TimeReplace and TimeGroups.

Source PRs #5055 , #5056 , #5057 

<!-- Please give a short brief about these changes. -->

- [x] first commit message and PR title follow format outlined [here](https://modin.readthedocs.io/en/latest/development/contributing.html#commit-message-formatting)
  > **_NOTE:_**  If you edit the PR title to match this format, you need to add another commit (even if it's empty) or amend your last commit for the CI job that checks the PR title to pick up the new PR title.
- [x] passes `flake8 modin/ asv_bench/benchmarks scripts/doc_checker.py`
- [x] passes `black --check modin/ asv_bench/benchmarks scripts/doc_checker.py`
- [x] signed commit with `git commit -s` <!-- you can amend your commit with a signature via `git commit -amend -s` -->
- [x] Resolves #5378 <!-- issue must be created for each patch -->
- [x] tests added and passing
- [x] module layout described at `docs/development/architecture.rst` is up-to-date <!-- if you have added, renamed or removed files or directories please update the documentation accordingly -->


Problem statement:
[ASV] port stack, unstack, replace and  groups benchmarks from pandas github
Have to add ASV benchmarks related which does the following actions
1) perform stack and unstack on dataframe.
2) Replace function on a dataframe.
3) Get groups returned by groupby.

Hints from issue discussion:
@arunjose696 thank you for filing this issue. Could you please add priorities to all new Modin issues? You can ask someone from modin-core if you need help assigning a priority. I think these benchmarking issues can be P2.

Commit message:  fixing flake issue

Signed-off-by: arunjose696 <arunjose696@gmail.com>

Patch to review:
diff --git a/asv_bench/benchmarks/benchmarks.py b/asv_bench/benchmarks/benchmarks.py
index 19574b6be96..e1bf1a92392 100644
--- a/asv_bench/benchmarks/benchmarks.py
+++ b/asv_bench/benchmarks/benchmarks.py
@@ -1113,4 +1113,63 @@ def time_remove_categories(self, shape):
         execute(self.ts.cat.remove_categories(self.ts.cat.categories[::2]))
 
 
+class TimeSimpleReshape:
+
+    params = [get_benchmark_shapes("TimeSimpleReshape")]
+    param_names = ["shape"]
+
+    def setup(self, shape):
+        rows, cols = shape
+        k = 10
+        arrays = [
+            np.arange(rows // k).repeat(k),
+            np.roll(np.tile(np.arange(rows // k), k), 25),
+        ]
+        index = IMPL.MultiIndex.from_arrays(arrays)
+        self.df = IMPL.DataFrame(np.random.randn(rows, cols), index=index)
+        self.udf = self.df.unstack(1)
+        execute(self.df), execute(self.udf)
+
+    def time_stack(self, shape):
+        execute(self.udf.stack())
+
+    def time_unstack(self, shape):
+        execute(self.df.unstack(1))
+
+
+class TimeReplace:
+
+    params = [get_benchmark_shapes("TimeReplace")]
+    param_names = ["shape"]
+
+    def setup(self, shape):
+        rows, cols = shape
+        self.to_replace = {i: getattr(IMPL, "Timestamp")(i) for i in range(rows)}
+        self.df = IMPL.DataFrame()
+        for col in range(cols):
+            self.df["col" + str(col + 1)] = np.random.randint(rows, size=rows)
+        execute(self.df)
+
+    def time_replace(self, shape):
+        execute(self.df.replace(self.to_replace))
+
+
+class TimeGroups:
+
+    params = [get_benchmark_shapes("TimeGroups")]
+    param_names = ["shape"]
+
+    def setup(self, shape):
+        self.series = IMPL.Series(np.random.randint(0, 100, size=shape[0]))
+        execute(self.series)
+
+    # returns a pretty dict thus not calling execute
+    def time_series_groups(self, shape):
+        self.series.groupby(self.series).groups
+
+    # returns a  dict thus not calling execute
+    def time_series_indices(self, shape):
+        self.series.groupby(self.series).indices
+
+
 from .utils import setup  # noqa: E402, F401
diff --git a/asv_bench/benchmarks/utils/data_shapes.py b/asv_bench/benchmarks/utils/data_shapes.py
index fe7c612ba08..e3bc9fdb1d6 100644
--- a/asv_bench/benchmarks/utils/data_shapes.py
+++ b/asv_bench/benchmarks/utils/data_shapes.py
@@ -101,6 +101,8 @@
             "TimeReindexMethod",
             "TimeFillnaMethodDataframe",
             "TimeDropDuplicatesDataframe",
+            "TimeSimpleReshape",
+            "TimeReplace",
             # IO benchmarks
             "TimeReadCsvSkiprows",
             "TimeReadCsvTrueFalseValues",
@@ -128,6 +130,7 @@
         [
             # Pandas storage format benchmarks
             "TimeFillnaSeries",
+            "TimeGroups",
             "TimeIndexingNumericSeries",
             "TimeFillnaMethodSeries",
             "TimeDatetimeAccessor",


Code review comments:
diff_hunk: @@ -1113,4 +1113,63 @@ def time_remove_categories(self, shape):
         execute(self.ts.cat.remove_categories(self.ts.cat.categories[::2]))
 
 
+class TimeSimpleReshape:
+
+    params = [get_benchmark_shapes("TimeSimpleReshape")]
+    param_names = ["shape"]
+
+    def setup(self, shape):
+        rows, cols = shape
+        k = 10
+        arrays = [
+            np.arange(rows // k).repeat(k),
+            np.roll(np.tile(np.arange(rows // k), k), 25),
+        ]
+        index = IMPL.MultiIndex.from_arrays(arrays)
+        self.df = IMPL.DataFrame(np.random.randn(rows, cols), index=index)
+        self.udf = self.df.unstack(1)
+        execute(self.df), execute(self.udf)
+
+    def time_stack(self, shape):
+        execute(self.udf.stack())
+
+    def time_unstack(self, shape):
+        execute(self.df.unstack(1))
+
+
+class TimeReplace:
+
+    params = [get_benchmark_shapes("TimeReplace")]
+    param_names = ["shape"]
+
+    def setup(self, shape):
+        rows, cols = shape
+        self.to_replace = {i: getattr(IMPL, "Timestamp")(i) for i in range(rows)}
+        self.df = IMPL.DataFrame()
+        for col in range(cols):
+            self.df["col" + str(col + 1)] = np.random.randint(rows, size=rows)
path: asv_bench/benchmarks/benchmarks.py
text: @user1:
```suggestion
        columns = [f"col{col + 1}" for col in range(cols)]
        self.df = IMPL.DataFrame(np.random.randint(rows, size=(rows, cols), columns=columns)
```

@user1:
Creating a dataframe in Modin by assigning columns in a loop results in inefficient internal partitioning, which in turn leads to poor performance.

@author:
changed this to normal initialization of dataframe.

diff_hunk: @@ -1113,4 +1113,63 @@ def time_remove_categories(self, shape):
         execute(self.ts.cat.remove_categories(self.ts.cat.categories[::2]))
 
 
+class TimeSimpleReshape:
+
+    params = [get_benchmark_shapes("TimeSimpleReshape")]
+    param_names = ["shape"]
+
+    def setup(self, shape):
+        rows, cols = shape
+        k = 10
+        arrays = [
+            np.arange(rows // k).repeat(k),
+            np.roll(np.tile(np.arange(rows // k), k), 25),
+        ]
+        index = IMPL.MultiIndex.from_arrays(arrays)
+        self.df = IMPL.DataFrame(np.random.randn(rows, cols), index=index)
+        self.udf = self.df.unstack(1)
+        execute(self.df), execute(self.udf)
+
+    def time_stack(self, shape):
+        execute(self.udf.stack())
+
+    def time_unstack(self, shape):
+        execute(self.df.unstack(1))
+
+
+class TimeReplace:
+
+    params = [get_benchmark_shapes("TimeReplace")]
+    param_names = ["shape"]
+
+    def setup(self, shape):
+        rows, cols = shape
+        self.to_replace = {i: getattr(IMPL, "Timestamp")(i) for i in range(rows)}
+        self.df = IMPL.DataFrame()
+        for col in range(cols):
+            self.df["col" + str(col + 1)] = np.random.randint(rows, size=rows)
+        execute(self.df)
+
+    def time_replace(self, shape):
+        execute(self.df.replace(self.to_replace))
+
+
+class TimeGroups:
+
+    params = [get_benchmark_shapes("TimeGroups")]
+    param_names = ["shape"]
+
+    def setup(self, shape):
+        self.series = IMPL.Series(np.random.randint(0, 100, size=shape[0]))
+        execute(self.series)
+
+    # returns a pretty dict thus not calling execute
+    def time_series_groups(self, shape):
+        self.series.groupby(self.series).groups
+
+    # returns a  dict thus not calling execute
path: asv_bench/benchmarks/benchmarks.py
text: @user1:
```suggestion
    # returns a dict thus not calling execute
```

diff_hunk: @@ -101,6 +101,8 @@
             "TimeReindexMethod",
             "TimeFillnaMethodDataframe",
             "TimeDropDuplicatesDataframe",
+            "TimeSimpleReshape",
path: asv_bench/benchmarks/utils/data_shapes.py
text: @user1:
```suggestion
            "TimeStack",
            "TimeUnstack",
```