Repository: modin-project/modin

Issue:
nsmallest function implementation



Discussion:
I've incidentally fixed 2 other tasks instead of this one. 
Feel free to grab this one, as I'm working on the other tasks right now. 

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
@@ -505,7 +505,6 @@ def is_monotonic_decreasing(self):
     idxmin = ReductionFunction.register(pandas.DataFrame.idxmin)
     median = ReductionFunction.register(pandas.DataFrame.median)
     nunique = ReductionFunction.register(pandas.DataFrame.nunique)
-    nsmallest = ReductionFunction.register(pandas.DataFrame.nsmallest)
     nlargest = ReductionFunction.register(pandas.DataFrame.nlargest)
     skew = ReductionFunction.register(pandas.DataFrame.skew)
     kurt = ReductionFunction.register(pandas.DataFrame.kurt)
@@ -820,6 +819,23 @@ def map_func(df, other=other):
         )
         return self.__constructor__(new_modin_frame)
 
+    def nsmallest(self, n, columns=0, keep="first"):
+        def map_func(df, n=n, keep=keep, columns=columns):
+            if isinstance(df.squeeze(), pandas.DataFrame):
+                return pandas.DataFrame.nsmallest(df, n=n, columns=columns, keep=keep)
+            else:
+                return pandas.Series.nsmallest(df.squeeze(), n=n, keep=keep)
+
+        if len(self.columns) != 1:
+            new_columns = self.columns
+        else:
+            new_columns = None
+
+        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 eval(self, expr, **kwargs):
         """Returns a new QueryCompiler with expr evaluated on columns.
 
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -1434,10 +1434,14 @@ def nsmallest(self, n, columns, keep="first"):
             - ``last`` : take the last occurrence.
             - ``all`` : do not drop any duplicates, even it means
               selecting more than `n` items.
-            .. versionadded:: 0.24.0
+        Returns
+        -------
+        DataFrame
         """
         return DataFrame(
-            query_compiler=self._query_compiler.nsmallest(n, columns, keep)
+            query_compiler=self._query_compiler.nsmallest(
+                n=n, columns=columns, keep=keep
+            )
         )
 
     def pivot(self, index=None, columns=None, values=None):
diff --git a/modin/pandas/series.py b/modin/pandas/series.py
--- a/modin/pandas/series.py
+++ b/modin/pandas/series.py
@@ -855,7 +855,27 @@ def nlargest(self, n=5, keep="first"):
         return self._default_to_pandas(pandas.Series.nlargest, n=n, keep=keep)
 
     def nsmallest(self, n=5, keep="first"):
-        return self._default_to_pandas(pandas.Series.nsmallest, n=n, keep=keep)
+        """
+        Return the smallest `n` elements.
+        Parameters
+        ----------
+        n : int, default 5
+            Return this many ascending sorted values.
+        keep : {'first', 'last', 'all'}, default 'first'
+            When there are duplicate values that cannot all fit in a
+            Series of `n` elements:
+            - ``first`` : return the first `n` occurrences in order
+                of appearance.
+            - ``last`` : return the last `n` occurrences in reverse
+                order of appearance.
+            - ``all`` : keep all occurrences. This can result in a Series of
+                size larger than `n`.
+        Returns
+        -------
+        Series
+            The `n` smallest values in the Series, sorted in increasing order.
+        """
+        return Series(query_compiler=self._query_compiler.nsmallest(n=n, keep=keep))
 
     @property
     def plot(


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
@@ -4048,7 +4048,12 @@ def test_nsmallest(self):
         modin_df = pd.DataFrame(data=data, index=index)
         pandas_df = pandas.DataFrame(data=data, index=index)
         df_equals(
-            modin_df.nsmallest(3, "population"), pandas_df.nsmallest(3, "population")
+            modin_df.nsmallest(n=3, columns="population"),
+            pandas_df.nsmallest(n=3, columns="population"),
+        )
+        df_equals(
+            modin_df.nsmallest(n=2, columns=["population", "GDP"], keep="all"),
+            pandas_df.nsmallest(n=2, columns=["population", "GDP"], keep="all"),
         )
 
     @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
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
@@ -1965,12 +1965,22 @@ def test_notnull(data):
     df_equals(modin_series.notnull(), pandas_series.notnull())
 
 
-@pytest.mark.skip(reason="Using pandas Series.")
-def test_nsmallest():
-    modin_series = create_test_series()
-
-    with pytest.raises(NotImplementedError):
-        modin_series.nsmallest(None)
+@pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
+def test_nsmallest(data):
+    modin_series, pandas_series = create_test_series(data)
+    df_equals(
+        modin_series.nsmallest(n=5, keep="first"),
+        pandas_series.nsmallest(n=5, keep="first"),
+    )
+    df_equals(
+        modin_series.nsmallest(n=10, keep="first"),
+        pandas_series.nsmallest(n=10, keep="first"),
+    )
+    df_equals(
+        modin_series.nsmallest(n=10, keep="last"),
+        pandas_series.nsmallest(n=10, keep="last"),
+    )
+    df_equals(modin_series.nsmallest(keep="all"), pandas_series.nsmallest(keep="all"))
 
 
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)