Repository: networkx/networkx
Pull request: Fix nx.from_pandas_edgelist so edge keys are not added as edge attributes and edge keys
<!--
Please use pre-commit to lint your code.
For more details check out step 1 and 4 of
https://networkx.org/documentation/latest/developer/contribute.html
-->
Fixes #7444 

Problem statement:
keys added as edge attributes when using nx.from_pandas_edgelist
Hi there,
regarding the nx.from_pandas_edgelist function, when creating multigraphs:

### Current Behavior

Following the example in the documentation, but calling "edge_attr = True" instead of "edge_attr=["weight", "color"]", the edge keys get added to the graph not only as keys, but also as edge attributes.
i.e G.edges(data=True) outputs:
`MultiEdgeDataView([(0, 2, {'my_edge_key': 'A', 'weight': 3, 'color': 'red'}), (0, 2, {'my_edge_key': 'D', 'weight': 6, 'color': 'blue'}), (2, 1, {'my_edge_key': 'B', 'weight': 4, 'color': 'blue'}), (2, 3, {'my_edge_key': 'C', 'weight': 5, 'color': 'blue'})])`
I do not think this is intended behavior, as edge keys are not edge attributes. 
In other words If we start with a multigraph and save it to a pandas edgelist using `nx.to_pandas_edgelist()`, the graph we load from that same edgelist in this way has an extra attribute (my_edge_key).

### Expected Behavior
keys should only be added as keys, as is what happens when you manually specify which columns are edge_attr.

### Steps to Reproduce

`edges = pd.DataFrame(
    {
        "source": [0, 1, 2, 0],
        "target": [2, 2, 3, 2],
        "my_edge_key": ["A", "B", "C", "D"],
        "weight": [3, 4, 5, 6],
        "color": ["red", "blue", "blue", "blue"],
    }
)`

`
G = nx.from_pandas_edgelist(
    edges,
    edge_key="my_edge_key",
    edge_attr=["weight", "color"],
    create_using=nx.MultiGraph(),
)
`

### Environment

NetworkX version: 3.2.1

Hints from issue discussion:
I agree that this is undesirable behavior, thanks for the report!

Commit message: Fix docstring formatting

Co-authored-by: Ross Barnowski <rossbar@berkeley.edu>

Patch to review:
diff --git a/networkx/convert_matrix.py b/networkx/convert_matrix.py
index d4c924a8567..d09848586e4 100644
--- a/networkx/convert_matrix.py
+++ b/networkx/convert_matrix.py
@@ -348,7 +348,7 @@ def from_pandas_edgelist(
     edge_attr : str or int, iterable, True, or None
         A valid column name (str or int) or iterable of column names that are
         used to retrieve items and add them to the graph as edge attributes.
-        If `True`, all of the remaining columns will be added.
+        If `True`, all of the remaining columns will be added (not including `edge_key`).
         If `None`, no edge attributes are added to the graph.
 
     create_using : NetworkX graph constructor, optional (default=nx.Graph)
@@ -436,6 +436,8 @@ def from_pandas_edgelist(
         return g
 
     reserved_columns = [source, target]
+    if g.is_multigraph() and edge_key is not None:
+        reserved_columns.append(edge_key)
 
     # Additional columns requested
     attr_col_headings = []
diff --git a/networkx/tests/test_convert_pandas.py b/networkx/tests/test_convert_pandas.py
index 7076eb57abe..594fcab7d3c 100644
--- a/networkx/tests/test_convert_pandas.py
+++ b/networkx/tests/test_convert_pandas.py
@@ -241,7 +241,8 @@ def test_from_adjacency_named(self):
         df = nx.to_pandas_adjacency(G, dtype=np.intp)
         pd.testing.assert_frame_equal(df, dftrue)
 
-    def test_edgekey_with_multigraph(self):
+    @pytest.mark.parametrize("edge_attr", [["attr2", "attr3"], True])
+    def test_edgekey_with_multigraph(self, edge_attr):
         df = pd.DataFrame(
             {
                 "source": {"A": "N1", "B": "N2", "C": "N1", "D": "N1"},
@@ -264,7 +265,7 @@ def test_edgekey_with_multigraph(self):
             df,
             source="source",
             target="target",
-            edge_attr=["attr2", "attr3"],
+            edge_attr=edge_attr,
             edge_key="attr1",
             create_using=nx.MultiGraph(),
         )


Code review comments:
diff_hunk: @@ -348,7 +348,7 @@ def from_pandas_edgelist(
     edge_attr : str or int, iterable, True, or None
         A valid column name (str or int) or iterable of column names that are
         used to retrieve items and add them to the graph as edge attributes.
-        If `True`, all of the remaining columns will be added.
+        If `True`, all of the remaining columns will be added (not including `edge_key`).
path: networkx/convert_matrix.py
text: @user1:
Is this better wording?  (it is <= 88 chars -- which is why I first started looking, but the end result is an attempt to allow the user to know what's going on without having to read all the other kwargs.
```suggestion
        If `True`, all columns will be added except `source`, `target` and `edge_key`.
```

@author:
Looks better to me, thanks @user1