Repository: pandas-dev/pandas

Issue:
Predictable order for columns in assign through sorting
The order of columns coming out of `.assign` is hard to predict

``` Python
In [1]: import pandas as pd

In [2]: df = pd.DataFrame({'a': [1], 'b': [2]})

In [3]: df
Out[3]: 
   a  b
0  1  2

In [4]: df.assign(d=df.a*10, a=df.b*10, e=df.a*20)
Out[4]: 
    a  b   e   d
0  20  2  20  10
```

Would it be reasonable to keep all existing keys in their current location and then add the remaining keys in sorted order at the end?



Discussion:
this does seem reasonable

ther args are passed as kwargs where ordering is not defined
but existing columns are known and new keys could be sorted

Agreed, this seems like a sane change.

@TomAugspurger want this issue?

I thought about doing this initially but then backed off when I saw PEP 468: https://www.python.org/dev/peps/pep-0468/ which is for preserving the order of **kwargs. Plus the eventual future where pandas runs on pypy, whose dicts are ordered.
That said, I'm not sure we should make design decisions around those, which could be very far off in the future or not happen at all. Having a predictable order now is probably a good thing.

It'll be a few days before I can get to it probably.

its a nice idea to have the ordering for `kwargs`, but practically that is long off (and `pypy` is even longer IMHO / never be adopted).


Resolving patch:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -2244,10 +2244,11 @@ def assign(self, **kwargs):
         Notes
         -----
         Since ``kwargs`` is a dictionary, the order of your
-        arguments may not be preserved, and so the order of the
-        new columns is not well defined. Assigning multiple
-        columns within the same ``assign`` is possible, but you cannot
-        reference other columns created within the same ``assign`` call.
+        arguments may not be preserved. The make things predicatable,
+        the columns are inserted in alphabetical order, at the end of
+        your DataFrame. Assigning multiple columns within the same
+        ``assign`` is possible, but you cannot reference other columns
+        created within the same ``assign`` call.
 
         Examples
         --------
@@ -2296,7 +2297,7 @@ def assign(self, **kwargs):
                 results[k] = v
 
         # ... and then assign
-        for k, v in results.items():
+        for k, v in sorted(results.items()):
             data[k] = v
 
         return data


Test patch:
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -14073,12 +14073,21 @@ def test_assign(self):
         assert_frame_equal(result, expected)
 
     def test_assign_multiple(self):
-        df = DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
+        df = DataFrame([[1, 4], [2, 5], [3, 6]], columns=['A', 'B'])
         result = df.assign(C=[7, 8, 9], D=df.A, E=lambda x: x.B)
-        expected = DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6], 'C': [7, 8, 9],
-                              'D': [1, 2, 3], 'E': [4, 5, 6]})
-        # column order isn't preserved
-        assert_frame_equal(result.reindex_like(expected), expected)
+        expected = DataFrame([[1, 4, 7, 1, 4], [2, 5, 8, 2, 5],
+                              [3, 6, 9, 3, 6]], columns=list('ABCDE'))
+        assert_frame_equal(result, expected)
+
+    def test_assign_alphabetical(self):
+        # GH 9818
+        df = DataFrame([[1, 2], [3, 4]], columns=['A', 'B'])
+        result = df.assign(D=df.A + df.B, C=df.A - df.B)
+        expected = DataFrame([[1, 2, -1, 3], [3, 4, -1, 7]],
+                             columns=list('ABCD'))
+        assert_frame_equal(result, expected)
+        result = df.assign(C=df.A - df.B, D=df.A + df.B)
+        assert_frame_equal(result, expected)
 
     def test_assign_bad(self):
         df = DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})