Repository: networkx/networkx

Issue:
to_agraph failing on node attribute name `n`
I have some graph where I add the node attribute `n`

```
G = nx.DiGraph([(1,2)])
G.node[1]['n'] = 1
nx.to_agraph(G)

networkx/drawing/nx_agraph.py:146
TypeError: add_node() got multiple values for argument 'n'
```

Is this behavior wanted? For semantic reasons I would enjoy having 'n' as a node attribute. I am aware that `G.add_node(3, n=10)` has the same behavior. This is the part of `to_agraph` where the error occurs:

```
def to_agraph(N)
    ...
    # add nodes
    for n,nodedata in N.nodes(data=True):
        A.add_node(n,**nodedata)
    ...
```

I would suggest the following change: Allow for a keyword argument to define the attributes one wants to export.

```
def to_agraph(N, node_attr=None):
    ...
    # add nodes
    for n,nodedata in N.nodes(data=True):
        A.add_node(n,**{k: v for k,v in nodedata.values() if node_attr is None or k in node_attr})
    ...
```

The argument would have to be propagated upwards to `nx.draw_networkx` etc. but would allow for an easy attributes filter.



Discussion:
This is definitely a bug. `n` should be an acceptable node attribute.

The issue, of course, is that we have to use some name for the parameter to `add_node`. We could change it to `def add_node(self, _n, attr_dict=None, **attr)` or you can pass it in as: `G.add_node(3, attr_dict={'n': 10})`. Not sure if we should change or you should.

@chebee7i This is why I'm asking here before I decide to change my personal networkx branch or my project.

Currently it is at least consistent that both `nx.Graph.add_node` and `pygraphviz.AGraph.add_node` do not allow 'n' to be passed in as keyword argument. The latter does not allow for a `attr_dict` though. Should I propose the change in `pygraphviz` instead?

I'm not sure. 

I just opened #1583 which would change the `n` in `add_node` to a `u` (but `u` as an attribute name would still be problematic). This is mostly a consistency issue, but it would address your problem (at least for networkx). As for pygraphviz, you should definitely open an issue there and link to it here. I think adding an `attr_dict` makes sense, independent of whether `n` is changed to `u` throughout the code.

I opened an issue in pygraphviz/pygraphviz#57

@chebee7i @jdrudolph @hagberg @dschult
Is this issue still open? I would like to work on this.

We could at least fix the (narrow) issue posed here with the suggested
```python
def to_agraph(N, node_attr=None):
    ...
    # add nodes
    for n,nodedata in N.nodes(data=True):
        A.add_node(n,**{k: v for k,v in nodedata.values() if node_attr is None or k in node_attr})
   ```

Resolving patch:
diff --git a/networkx/drawing/nx_agraph.py b/networkx/drawing/nx_agraph.py
--- a/networkx/drawing/nx_agraph.py
+++ b/networkx/drawing/nx_agraph.py
@@ -150,18 +150,29 @@ def to_agraph(N):
 
     # add nodes
     for n, nodedata in N.nodes(data=True):
-        A.add_node(n, **nodedata)
+        A.add_node(n)
+        if nodedata is not None:
+            a = A.get_node(n)
+            a.attr.update({k: str(v) for k, v in nodedata.items()})
 
     # loop over edges
-
     if N.is_multigraph():
         for u, v, key, edgedata in N.edges(data=True, keys=True):
-            str_edata = {k: str(v) for k, v in edgedata.items() if k != 'key'}
-            A.add_edge(u, v, key=str(key), **str_edata)
+            str_edgedata = {k: str(v) for k, v in edgedata.items() if k != 'key'}
+            A.add_edge(u, v, key=str(key))
+            if edgedata is not None:
+                a = A.get_edge(u,v)
+                a.attr.update(str_edgedata)
+
+
     else:
         for u, v, edgedata in N.edges(data=True):
             str_edgedata = {k: str(v) for k, v in edgedata.items()}
-            A.add_edge(u, v, **str_edgedata)
+            A.add_edge(u, v)
+            if edgedata is not None:
+                a = A.get_edge(u,v)
+                a.attr.update(str_edgedata)
+
 
     return A
 


Test patch:
diff --git a/networkx/drawing/tests/test_agraph.py b/networkx/drawing/tests/test_agraph.py
--- a/networkx/drawing/tests/test_agraph.py
+++ b/networkx/drawing/tests/test_agraph.py
@@ -24,6 +24,7 @@ def build_graph(self, G):
         G.graph['metal'] = 'bronze'
         return G
 
+
     def assert_equal(self, G1, G2):
         assert_nodes_equal(G1.nodes(), G2.nodes())
         assert_edges_equal(G1.edges(), G2.edges())
@@ -79,3 +80,21 @@ def test_view_pygraphviz_edgelable(self):
         G.add_edge(1, 2, weight=7)
         G.add_edge(2, 3, weight=8)
         nx.nx_agraph.view_pygraphviz(G, edgelabel='weight')
+
+    def test_from_agraph_name(self):
+        G = nx.Graph(name='test')
+        A = nx.nx_agraph.to_agraph(G)
+        H = nx.nx_agraph.from_agraph(A)
+        assert_equal(G.name, 'test')
+
+
+    def test_graph_with_reserved_keywords(self):
+        # test attribute/keyword clash case for #1582
+        # node: n
+        # edges: u,v
+        G = nx.Graph()
+        G = self.build_graph(G)
+        G.node['E']['n']='keyword'
+        G.edges[('A','B')]['u']='keyword'
+        G.edges[('A','B')]['v']='keyword'
+        A = nx.nx_agraph.to_agraph(G)