Repository: modin-project/modin

Issue:
pd.concat with Dictionary argument throws ValueError
Hi,
Just installed modin for the first time.. and I'm failing at the first hurdle.
Could anyone please take a look and tell me if they can spot something obvious I'm doing wrong?
I'm using a macbook with 10.15.4 of the OS.
python 3

Installed modin using:   

`pip install modin[ray]`

Now I'm trying to run code that worked with pandas:
```
import modin.pandas as pd
df_wave1 = pd.read_csv("Survey_1.csv")
df_wave2 = pd.read_csv("Survey_Cleaned_2.csv")
df_wave3 = pd.read_csv("Survey_Cleaned_3.csv")

surveys = {'1': df_wave1, '2': df_wave2, '3': df_wave3, }

df_all = pd.concat(surveys,  sort=False)
```
But I'm getting the error:
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-0ed75db51eaa> in <module>
      6 surveys = {'1': df_wave1, '2': df_wave2, '3': df_wave3, }
      7 
----> 8 df_all = pd.concat(surveys,  sort=False)

~/.virtualenvs/learningenv/lib/python3.7/site-packages/modin/pandas/concat.py in concat(objs, axis, join, ignore_index, keys, levels, names, verify_integrity, sort, copy)
     64             "and modin.pandas.DataFrame objs are "
     65             "valid",
---> 66             type(type_check),
     67         )
     68     all_series = all(isinstance(obj, Series) for obj in objs)

ValueError: ('cannot concatenate object of type "{0}"; only pandas.Series, pandas.DataFrame, and modin.pandas.DataFrame objs are valid', <class 'str'>)

```

I don't have pandas imported into the notebook.
I have restarted the kernel etc.

Any help would be greatly appreciated.
Thanks


Discussion:
Hi @vidapura, thanks for posting! This looks like a bug in Modin related to dictionary concatenation. We will get this fixed.

As a temporary workaround, you can slightly change your code to this:

```python
import modin.pandas as pd
df_wave1 = pd.read_csv("Survey_1.csv")
df_wave2 = pd.read_csv("Survey_Cleaned_2.csv")
df_wave3 = pd.read_csv("Survey_Cleaned_3.csv")

# surveys = {'1': df_wave1, '2': df_wave2, '3': df_wave3, }
df_wave1.index = pd.MultiIndex.from_tuples([("1", i) for i in df_wave1.index])
df_wave2.index = pd.MultiIndex.from_tuples([("2", i) for i in df_wave2.index])
df_wave3.index = pd.MultiIndex.from_tuples([("3", i) for i in df_wave3.index])
surveys = [df_wave1, df_wave2, df_wave3]

df_all = pd.concat(surveys,  sort=False)
```
Ahh brilliant!
Thanks @devin-petersohn !
I'll continue on with the experiment now and see if the rest of the code works with modin.
Thanks again.



Resolving patch:
diff --git a/modin/pandas/concat.py b/modin/pandas/concat.py
--- a/modin/pandas/concat.py
+++ b/modin/pandas/concat.py
@@ -44,18 +44,21 @@ def concat(
             '"{name}"'.format(name=type(objs).__name__)
         )
     axis = pandas.DataFrame()._get_axis_number(axis)
-    objs = list(objs)
-    if len(objs) == 0:
+    if isinstance(objs, dict):
+        list_of_objs = list(objs.values())
+    else:
+        list_of_objs = list(objs)
+    if len(list_of_objs) == 0:
         raise ValueError("No objects to concatenate")
 
-    objs = [obj for obj in objs if obj is not None]
+    list_of_objs = [obj for obj in list_of_objs if obj is not None]
 
-    if len(objs) == 0:
+    if len(list_of_objs) == 0:
         raise ValueError("All objects passed were None")
     try:
         type_check = next(
             obj
-            for obj in objs
+            for obj in list_of_objs
             if not isinstance(obj, (pandas.Series, Series, pandas.DataFrame, DataFrame))
         )
     except StopIteration:
@@ -63,17 +66,17 @@ def concat(
     if type_check is not None:
         raise ValueError(
             'cannot concatenate object of type "{0}"; only '
-            "pandas.Series, pandas.DataFrame, "
+            "modin.pandas.Series "
             "and modin.pandas.DataFrame objs are "
             "valid",
             type(type_check),
         )
-    all_series = all(isinstance(obj, Series) for obj in objs)
+    all_series = all(isinstance(obj, Series) for obj in list_of_objs)
     if all_series and axis == 0:
         return Series(
-            query_compiler=objs[0]._query_compiler.concat(
+            query_compiler=list_of_objs[0]._query_compiler.concat(
                 axis,
-                [o._query_compiler for o in objs[1:]],
+                [o._query_compiler for o in list_of_objs[1:]],
                 join=join,
                 join_axes=None,
                 ignore_index=ignore_index,
@@ -85,8 +88,6 @@ def concat(
                 sort=sort,
             )
         )
-    if isinstance(objs, dict):
-        raise NotImplementedError("Obj as dicts not implemented.")
     if join not in ["inner", "outer"]:
         raise ValueError(
             "Only can inner (intersect) or outer (union) join the other axis"
@@ -94,22 +95,29 @@ def concat(
     # We have the weird Series and axis check because, when concatenating a
     # dataframe to a series on axis=0, pandas ignores the name of the series,
     # and this check aims to mirror that (possibly buggy) functionality
-    objs = [
+    list_of_objs = [
         obj
         if isinstance(obj, DataFrame)
         else DataFrame(obj.rename())
         if isinstance(obj, (pandas.Series, Series)) and axis == 0
         else DataFrame(obj)
-        for obj in objs
+        for obj in list_of_objs
+    ]
+    list_of_objs = [
+        obj._query_compiler
+        for obj in list_of_objs
+        if len(obj.index) or len(obj.columns)
     ]
-    objs = [obj._query_compiler for obj in objs if len(obj.index) or len(obj.columns)]
     if keys is not None:
         if all_series:
             new_idx = keys
         else:
-            objs = [objs[i] for i in range(min(len(objs), len(keys)))]
+            list_of_objs = [
+                list_of_objs[i] for i in range(min(len(list_of_objs), len(keys)))
+            ]
             new_idx_labels = {
-                k: v.index if axis == 0 else v.columns for k, v in zip(keys, objs)
+                k: v.index if axis == 0 else v.columns
+                for k, v in zip(keys, list_of_objs)
             }
             tuples = [
                 (k, *o) if isinstance(o, tuple) else (k, o)
@@ -120,14 +128,18 @@ def concat(
             if names is not None:
                 new_idx.names = names
             else:
-                old_name = _determine_name(objs, axis)
+                old_name = _determine_name(list_of_objs, axis)
                 if old_name is not None:
                     new_idx.names = [None] + old_name
+    elif isinstance(objs, dict):
+        new_idx = pandas.concat(
+            {k: pandas.Series(index=obj.axes[axis]) for k, obj in objs.items()}
+        ).index
     else:
         new_idx = None
-    new_query_compiler = objs[0].concat(
+    new_query_compiler = list_of_objs[0].concat(
         axis,
-        objs[1:],
+        list_of_objs[1:],
         join=join,
         join_axes=None,
         ignore_index=ignore_index,


Test patch:
diff --git a/modin/pandas/test/test_concat.py b/modin/pandas/test/test_concat.py
--- a/modin/pandas/test/test_concat.py
+++ b/modin/pandas/test/test_concat.py
@@ -179,3 +179,14 @@ def test_concat_multiindex(axis, names):
         pd.concat([md_df1, md_df2], keys=keys, axis=axis, names=names),
         pandas.concat([pd_df1, pd_df2], keys=keys, axis=axis, names=names),
     )
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+def test_concat_dictionary(axis):
+    pandas_df, pandas_df2 = generate_dfs()
+    modin_df, modin_df2 = from_pandas(pandas_df), from_pandas(pandas_df2)
+
+    df_equals(
+        pd.concat({"A": modin_df, "B": modin_df2}, axis=axis),
+        pandas.concat({"A": pandas_df, "B": pandas_df2}, axis=axis),
+    )
diff --git a/stress_tests/kaggle/kaggle12.py b/stress_tests/kaggle/kaggle12.py
--- a/stress_tests/kaggle/kaggle12.py
+++ b/stress_tests/kaggle/kaggle12.py
@@ -52,7 +52,7 @@ def detect_outliers(df, n, features):
 train.loc[Outliers_to_drop]  # Show the outliers rows
 train = train.drop(Outliers_to_drop, axis=0).reset_index(drop=True)
 train_len = len(train)
-dataset = pd.concat(objs=[train, test], axis=0).reset_index(drop=True)
+dataset = pd.concat(list_of_objs=[train, test], axis=0).reset_index(drop=True)
 dataset = dataset.fillna(np.nan)
 dataset.isnull().sum()
 train.info()