Repository: networkx/networkx

Issue:
When using 'from_pandas_adjacency', edges are added focusing on column names rather than the order
When using the function `from_pandas_adjacency` (networkx 2.1) to create a graph, edges are added focusing on the ***order*** of columns and rows in the pandas DataFrame.
Personally, I think that it is more useful by adding edges focusing on the ***names*** of columns and rows (indices) rather than the ***order***, because

1. Personally, when column names and row names are given in the pandas DataFrame, I think it is natural to interpret them as node names.
2. In the current behavior, it is necessary for the code calling `from_pandas_adjacency` to ensure that the columns and rows of the pandas DataFrame are in the same order. For example, by writing the following code every time
　　`df = df[df.index]  # Sort columns by row (index) order`
Without this code, there is a possibility that an unintended graph structure may be created.

The examples are as follows.

### Current behavior:
In the following case 1 and case 2, the pandas DataFrames have the same structure except column order. (The columns order of case 1 is ["A", "B", "C"]],  and the columns order of case 2 is ["A "," C "," B "])

![dataframe](https://user-images.githubusercontent.com/31801148/43364706-6604f2ac-935a-11e8-8199-c379f19068ad.png)

However, the graph structure created using `from_pandas_adjacency` (networkx 2.1) differs between case 1 and case 2.

![graph](https://user-images.githubusercontent.com/31801148/43364871-208cc30e-935e-11e8-815f-717593c034b1.png)

**Case 1:**  This case is no problem.

```
# A->B, B->C
data = {
        "A": {"A": 0, "B" : 0, "C": 0},
        "B": {"A": 1, "B" : 0, "C": 0},
        "C": {"A": 0, "B" : 1, "C": 0}
        }
case1_df = pd.DataFrame(data)

G  = nx.from_pandas_adjacency(case1_df, create_using=nx.DiGraph())
pos = nx.spring_layout(G)
nx.draw_networkx_nodes(G, pos=pos, node_color="#ffa1a1")
nx.draw_networkx_labels(G, pos=pos)
nx.draw_networkx_edges(G, pos=pos)
plt.title('case 1')
plt.show()
```
![case1_graph](https://user-images.githubusercontent.com/31801148/43364873-2a2bb0e6-935e-11e8-9a73-a9cc87924415.png)

**Case 2:**  
I think that behavior in this case is a little confusing.
Focusing on column names and row names, it is expected that the same graph structure as case 1 will be created. However, in fact, a different graph structure is created. It is because `from_pandas_adjacency` is currently based on the ***order***, not the ***names***.

```
# A->B, B->C (Same as case 1)
data = {
        "A": {"A": 0, "B" : 0, "C": 0},
        "B": {"A": 1, "B" : 0, "C": 0},
        "C": {"A": 0, "B" : 1, "C": 0}
        }
case2_df = pd.DataFrame(data)

# !!! Change the column order from ["A", "B", "C"] to ["A", "C", "B"] !!!
case2_df = case2_df[["A", "C", "B"]]  

G  = nx.from_pandas_adjacency(case2_df, create_using=nx.DiGraph())
pos = nx.spring_layout(G)
nx.draw_networkx_nodes(G, pos=pos, node_color="#ffa1a1")
nx.draw_networkx_labels(G, pos=pos)
nx.draw_networkx_edges(G, pos=pos)
plt.title('case 2')
plt.show()

```
![case2_graph](https://user-images.githubusercontent.com/31801148/43364874-34ca0f34-935e-11e8-899b-1ae30066de40.png)

### Expected result: 
I want the result of case 2 to be the same as case 1. The reasons are mentioned at the beginning of this issue. 
If the code is changed, I'm thinking of sending a PR like the following.

**Before:**
https://github.com/networkx/networkx/blob/ab3a6cdece25e359f28526fdc5f9f8ae443503f3/networkx/convert_matrix.py#L180-L190

**After:**
My desire is to avoid the situation that always needs to be careful of columns order when using `from_pandas_adjacency`. I think that there are two approaches.

**[Approach 1]**
In the original code, the columns are sorted after creating the graph. Change this to sort before creating the graph.

```
def from_pandas_adjacency(df, create_using=None):
    # …

    try:
        df = df[df.index]
    except:
        msg = "%s not in columns"
        missing = list(set(df.index).difference(set(df.columns)))
        raise nx.NetworkXError("Columns must match Indices.", msg % missing)

    A = df.values
    G = from_numpy_matrix(A, create_using=create_using)

    nx.relabel.relabel_nodes(G, dict(enumerate(df.columns)), copy=False)
    return G
```

**[Approach 2]**
If the columns and rows are in the different order, throw an exception. Then, before using `from_pandas_adjacency`, I will be able to remember that the columns and rows are in the same order. And, it is possible to avoid trouble that an unintended graph structure is created.


Discussion:
Thanks for pointing this out! I agree that we need a fix here -- either raise an exception when order doesn't match or reorder the columns to match the rows.  I would lean toward your **Approach 1**. Do you see advantages of the second approach?  I guess my question is also getting at: are there backward compatibility issues we need to worry about here?

Thanks!
Thank you for the comments.

> Do you see advantages of the second approach? 

I think there is no big advantage for the second approach. I myself want to adopt **Approach 1** rather than Approach 2. 
I presented Approach 2 as an alternative to the case where Approach 1 can not be adopted for any reasons. (I thought that `from_pandas_adjacency` might have been implemented to focus on order after some discussion.)

>  I guess my question is also getting at: are there backward compatibility issues we need to worry about here?

I think there is no backward compatibility problem.
If there are cases in which it is necessary to ignore the column name and create a graph structure, it will be a problem, but I think that is not a general case.

If there is no problem, I would like to send a pull request with the contents of **Approach 1**.

Resolving patch:
diff --git a/networkx/convert_matrix.py b/networkx/convert_matrix.py
--- a/networkx/convert_matrix.py
+++ b/networkx/convert_matrix.py
@@ -177,8 +177,6 @@ def from_pandas_adjacency(df, create_using=None):
 
     """
 
-    A = df.values
-    G = from_numpy_matrix(A, create_using=create_using)
     try:
         df = df[df.index]
     except:
@@ -186,6 +184,9 @@ def from_pandas_adjacency(df, create_using=None):
         missing = list(set(df.index).difference(set(df.columns)))
         raise nx.NetworkXError("Columns must match Indices.", msg % missing)
 
+    A = df.values
+    G = from_numpy_matrix(A, create_using=create_using)
+
     nx.relabel.relabel_nodes(G, dict(enumerate(df.columns)), copy=False)
     return G
 


Test patch:
diff --git a/networkx/tests/test_convert_pandas.py b/networkx/tests/test_convert_pandas.py
--- a/networkx/tests/test_convert_pandas.py
+++ b/networkx/tests/test_convert_pandas.py
@@ -163,3 +163,14 @@ def test_roundtrip(self):
         df = nx.to_pandas_adjacency(Gtrue, dtype=int)
         G = nx.from_pandas_adjacency(df)
         assert_graphs_equal(Gtrue, G)
+
+    def test_from_adjacency_named(self):
+        # example from issue #3105
+        data = {"A": {"A": 0, "B": 0, "C": 0},
+                "B": {"A": 1, "B": 0, "C": 0},
+                "C": {"A": 0, "B": 1, "C": 0}}
+        dftrue = pd.DataFrame(data)
+        df = dftrue[["A", "C", "B"]]
+        G = nx.from_pandas_adjacency(df, create_using=nx.DiGraph())
+        df = nx.to_pandas_adjacency(G, dtype=int)
+        pd.testing.assert_frame_equal(df, dftrue)