Repository: modin-project/modin

Issue:
Expand parameters support of `modin.distributed.dataframe.pandas.from_partitions`
Currently, function `modin.distributed.dataframe.pandas.from_partitions` supports only `partitions` and `axis` parameters. We can avoid internal blocking calls by adding additional optional `index`, `columns`, `row_lengths` and `column_widths` parameters, which can be useful for user in case he has this data. 


Discussion:

Resolving patch:
diff --git a/modin/distributed/dataframe/pandas/partitions.py b/modin/distributed/dataframe/pandas/partitions.py
--- a/modin/distributed/dataframe/pandas/partitions.py
+++ b/modin/distributed/dataframe/pandas/partitions.py
@@ -89,7 +89,9 @@ def _unwrap_partitions(oid):
         ]
 
 
-def from_partitions(partitions, axis):
+def from_partitions(
+    partitions, axis, index=None, columns=None, row_lengths=None, column_widths=None
+):
     """
     Create DataFrame from remote partitions.
 
@@ -106,11 +108,26 @@ def from_partitions(partitions, axis):
         * ``axis=0`` if you want to create DataFrame from row partitions
         * ``axis=1`` if you want to create DataFrame from column partitions
         * ``axis=None`` if you want to create DataFrame from 2D list of partitions
+    index : sequence, optional
+        The index for the DataFrame. Is computed if not provided.
+    columns : sequence, optional
+        The columns for the DataFrame. Is computed if not provided.
+    row_lengths : list, optional
+        The length of each partition in the rows. The "height" of
+        each of the block partitions. Is computed if not provided.
+    column_widths : list, optional
+        The width of each partition in the columns. The "width" of
+        each of the block partitions. Is computed if not provided.
 
     Returns
     -------
     modin.pandas.DataFrame
         DataFrame instance created from remote partitions.
+
+    Notes
+    -----
+    Pass `index`, `columns`, `row_lengths` and `column_widths` to avoid triggering
+    extra computations of the metadata when creating a DataFrame.
     """
     from modin.data_management.factories.dispatcher import FactoryDispatcher
 
@@ -159,8 +176,24 @@ def from_partitions(partitions, axis):
             f"Got unacceptable value of axis {axis}. Possible values are {0}, {1} or {None}."
         )
 
-    index = partition_mgr_class.get_indices(0, parts, lambda df: df.axes[0])
-    columns = partition_mgr_class.get_indices(1, parts, lambda df: df.axes[1])
-    return DataFrame(
-        query_compiler=PandasQueryCompiler(partition_frame_class(parts, index, columns))
+    labels_axis_to_sync = None
+    if index is None:
+        labels_axis_to_sync = 1
+        index = partition_mgr_class.get_indices(0, parts, lambda df: df.axes[0])
+
+    if columns is None:
+        labels_axis_to_sync = 0 if labels_axis_to_sync is None else -1
+        columns = partition_mgr_class.get_indices(1, parts, lambda df: df.axes[1])
+
+    frame = partition_frame_class(
+        parts,
+        index,
+        columns,
+        row_lengths=row_lengths,
+        column_widths=column_widths,
     )
+
+    if labels_axis_to_sync != -1:
+        frame.synchronize_labels(axis=labels_axis_to_sync)
+
+    return DataFrame(query_compiler=PandasQueryCompiler(frame))


Test patch:
diff --git a/modin/test/test_partition_api.py b/modin/test/test_partition_api.py
--- a/modin/test/test_partition_api.py
+++ b/modin/test/test_partition_api.py
@@ -102,11 +102,35 @@ def test_unwrap_partitions(axis):
                 )
 
 
+@pytest.mark.parametrize("column_widths", [None, "column_widths"])
+@pytest.mark.parametrize("row_lengths", [None, "row_lengths"])
+@pytest.mark.parametrize("columns", [None, "columns"])
+@pytest.mark.parametrize("index", [None, "index"])
 @pytest.mark.parametrize("axis", [None, 0, 1])
-def test_from_partitions(axis):
-    data = np.random.randint(0, 100, size=(2 ** 16, 2 ** 8))
+def test_from_partitions(axis, index, columns, row_lengths, column_widths):
+    num_rows = 2 ** 16
+    num_cols = 2 ** 8
+    data = np.random.randint(0, 100, size=(num_rows, num_cols))
     df1, df2 = pandas.DataFrame(data), pandas.DataFrame(data)
     expected_df = pandas.concat([df1, df2], axis=1 if axis is None else axis)
+
+    index = expected_df.index if index == "index" else None
+    columns = expected_df.columns if columns == "columns" else None
+    row_lengths = (
+        None
+        if row_lengths is None
+        else [num_rows, num_rows]
+        if axis == 0
+        else [num_rows]
+    )
+    column_widths = (
+        None
+        if column_widths is None
+        else [num_cols]
+        if axis == 0
+        else [num_cols, num_cols]
+    )
+
     if Engine.get() == "Ray":
         if axis is None:
             futures = [[ray.put(df1), ray.put(df2)]]
@@ -118,7 +142,40 @@ def test_from_partitions(axis):
             futures = [client.scatter([df1, df2], hash=False)]
         else:
             futures = client.scatter([df1, df2], hash=False)
-    actual_df = from_partitions(futures, axis)
+    actual_df = from_partitions(
+        futures,
+        axis,
+        index=index,
+        columns=columns,
+        row_lengths=row_lengths,
+        column_widths=column_widths,
+    )
+    df_equals(expected_df, actual_df)
+
+
+@pytest.mark.parametrize("columns", ["original_col", "new_col"])
+@pytest.mark.parametrize("index", ["original_idx", "new_idx"])
+@pytest.mark.parametrize("axis", [None, 0, 1])
+def test_from_partitions_mismatched_labels(axis, index, columns):
+    num_rows = 2 ** 16
+    num_cols = 2 ** 8
+    expected_df = pd.DataFrame(np.random.randint(0, 100, size=(num_rows, num_cols)))
+    partitions = unwrap_partitions(expected_df, axis=axis)
+
+    index = (
+        expected_df.index
+        if index == "original_idx"
+        else [f"row{i}" for i in expected_df.index]
+    )
+    columns = (
+        expected_df.columns
+        if columns == "original_col"
+        else [f"col{i}" for i in expected_df.columns]
+    )
+
+    expected_df.index = index
+    expected_df.columns = columns
+    actual_df = from_partitions(partitions, axis=axis, index=index, columns=columns)
     df_equals(expected_df, actual_df)