Repository: pandas-dev/pandas
Pull request: Fix concat not respecting order of OrderedDict
- [x] closes #21510
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`


Problem statement:
concat does not respect order of an OrderedDict
#### Code Sample, a copy-pastable example if possible

```python
In [2]: from collections import OrderedDict

In [3]: pd.concat(OrderedDict([('First', pd.Series(range(3))),
   ...:                        ('Another', pd.Series(range(4)))]))
   ...:                        
Out[3]: 
Another  0    0
         1    1
         2    2
         3    3
First    0    0
         1    1
         2    2
dtype: int64
```
#### Problem description

Since the data I am passing has an order - as when I pass a list - ``pd.concat`` should respect it.


#### Expected Output

``` python
In [4]: pd.concat([pd.Series(range(3)), pd.Series(range(4))], keys=['First', 'Another'])
Out[4]: 
First    0    0
         1    1
         2    2
Another  0    0
         1    1
         2    2
         3    3
dtype: int64

```

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.3.final.0
python-bits: 64
OS: Linux
OS-release: 4.9.0-6-amd64
machine: x86_64
processor: 
byteorder: little
LC_ALL: None
LANG: it_IT.UTF-8
LOCALE: it_IT.UTF-8

pandas: 0.24.0.dev0+119.gc08a39fae
pytest: 3.5.0
pip: 9.0.1
setuptools: 39.2.0
Cython: 0.25.2
numpy: 1.14.3
scipy: 0.19.0
pyarrow: None
xarray: None
IPython: 6.2.1
sphinx: 1.5.6
patsy: 0.5.0
dateutil: 2.7.3
pytz: 2018.4
blosc: None
bottleneck: 1.2.0dev
tables: 3.3.0
numexpr: 2.6.1
feather: 0.3.1
matplotlib: 2.2.2.post1153+gff6786446
openpyxl: 2.3.0
xlrd: 1.0.0
xlwt: 1.3.0
xlsxwriter: 0.9.6
lxml: 4.1.1
bs4: 4.5.3
html5lib: 0.999999999
sqlalchemy: 1.0.15
pymysql: None
psycopg2: None
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: 0.2.1


</details>

Hints from issue discussion:
@toobaz can you see if we have any similar issues outstanding? 
> @toobaz can you see if we have any similar issues outstanding?

Couldn't find any (the only vaguely related is #20909 )

What about an "order" or "sort" tagging label?

Commit message: Fix import order

Patch to review:
diff --git a/pandas/core/common.py b/pandas/core/common.py
index 5b83cb344b1e7..2f677e92e5904 100644
--- a/pandas/core/common.py
+++ b/pandas/core/common.py
@@ -14,7 +14,7 @@
 
 from pandas._libs import lib, tslibs
 import pandas.compat as compat
-from pandas.compat import PY36, iteritems
+from pandas.compat import PY37, iteritems
 
 from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
 from pandas.core.dtypes.common import (
@@ -226,7 +226,7 @@ def try_sort(iterable):
 def dict_keys_to_ordered_list(mapping):
     # when pandas drops support for Python < 3.6, this function
     # can be replaced by a simple list(mapping.keys())
-    if PY36 or isinstance(mapping, OrderedDict):
+    if PY37 or isinstance(mapping, OrderedDict):
         keys = list(mapping.keys())
     else:
         keys = try_sort(mapping)
diff --git a/pandas/core/reshape/concat.py b/pandas/core/reshape/concat.py
index 53671e00e88b4..c77bc0b71c952 100644
--- a/pandas/core/reshape/concat.py
+++ b/pandas/core/reshape/concat.py
@@ -253,7 +253,7 @@ def __init__(self, objs, axis=0, join='outer', join_axes=None,
 
         if isinstance(objs, dict):
             if keys is None:
-                keys = sorted(objs)
+                keys = com.dict_keys_to_ordered_list(objs)
             objs = [objs[k] for k in keys]
         else:
             objs = list(objs)
diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py
index a8a78b26e317c..c4f62ab40d553 100644
--- a/pandas/tests/frame/test_constructors.py
+++ b/pandas/tests/frame/test_constructors.py
@@ -12,7 +12,8 @@
 import pytest
 
 from pandas.compat import (
-    PY3, PY36, is_platform_little_endian, lmap, long, lrange, lzip, range, zip)
+    PY3, PY36, PY37, is_platform_little_endian, lmap, long, lrange, lzip,
+    range, zip)
 
 from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
 from pandas.core.dtypes.common import is_integer_dtype
@@ -340,7 +341,7 @@ def test_constructor_dict_nan_tuple_key(self, value):
         result = DataFrame(data, index=idx, columns=cols)
         tm.assert_frame_equal(result, expected)
 
-    @pytest.mark.skipif(not PY36, reason='Insertion order for Python>=3.6')
+    @pytest.mark.skipif(not PY37, reason='Insertion order for Python>=3.6')
     def test_constructor_dict_order_insertion(self):
         # GH19018
         # initialization ordering: by insertion order if python>= 3.6
diff --git a/pandas/tests/reshape/test_concat.py b/pandas/tests/reshape/test_concat.py
index a186d32ed8800..ac4c4aaa22c3f 100644
--- a/pandas/tests/reshape/test_concat.py
+++ b/pandas/tests/reshape/test_concat.py
@@ -1,4 +1,4 @@
-from collections import deque
+from collections import OrderedDict, deque
 import datetime as dt
 from datetime import datetime
 from decimal import Decimal
@@ -2399,6 +2399,14 @@ def test_concat_different_extension_dtypes_upcasts(self):
         ], dtype=object)
         tm.assert_series_equal(result, expected)
 
+    def test_concat_odered_dict(self):
+        # GH 21510
+        expected = pd.concat([pd.Series(range(3)), pd.Series(range(4))],
+                             keys=['First', 'Another'])
+        result = pd.concat(OrderedDict([('First', pd.Series(range(3))),
+                                        ('Another', pd.Series(range(4)))]))
+        tm.assert_series_equal(result, expected)
+
 
 @pytest.mark.parametrize('pdt', [pd.Series, pd.DataFrame, pd.Panel])
 @pytest.mark.parametrize('dt', np.sctypes['float'])
diff --git a/pandas/tests/sparse/frame/test_frame.py b/pandas/tests/sparse/frame/test_frame.py
index bfb5103c97adc..5ce5d4eb1ff42 100644
--- a/pandas/tests/sparse/frame/test_frame.py
+++ b/pandas/tests/sparse/frame/test_frame.py
@@ -129,7 +129,7 @@ def test_constructor_dict_order(self):
         # order by value
         d = {'b': [2, 3], 'a': [0, 1]}
         frame = SparseDataFrame(data=d)
-        if compat.PY36:
+        if compat.PY37:
             expected = SparseDataFrame(data=d, columns=list('ba'))
         else:
             expected = SparseDataFrame(data=d, columns=list('ab'))


Code review comments:
diff_hunk: @@ -226,7 +226,7 @@ def try_sort(iterable):
 def dict_keys_to_ordered_list(mapping):
     # when pandas drops support for Python < 3.6, this function
     # can be replaced by a simple list(mapping.keys())
-    if PY36 or isinstance(mapping, OrderedDict):
+    if PY37 or isinstance(mapping, OrderedDict):
path: pandas/core/common.py
text: @user1:
pls change these back to PY36 which is fine.