Repository: networkx/networkx

Issue:
Question: set_node_attributes ignoring missing nodes
I wanted to double-check on the desired behavior of `nx.set_node_attributes` when `values` is a dictionary that contains nodes that are not in `G`.

Currently, `set_node_attributes` ignores any nodes that are in the `values` dict but not in `G`, e.g.

```python
>>> G = nx.Graph()
>>> G.add_node(0, data='foo')
>>> nx.set_node_attributes(G, values={0: 'bar', 1:'baz'}, name='data')
>>> G.nodes.data()
NodeDataView({0: {'data': 'bar'}})
```

Whereas if you tried to do the equivalent operation "by hand", you would get an exception:

```python
>>> G = nx.Graph()
>>> G.add_node(0, data='foo')
>>> G[0]['data'] = 'bar'
>>> G[1]['data'] = 'baz'
Traceback (most recent call last)
   ...
KeyError: 1
```

Ignoring the `KeyError` in `set_node_attributes` is currently not covered by the test suite, so I just wanted to double check that it is desired behavior before adding tests for it. 


Discussion:
Good question...  The use-case envisioned was someone who has a large dict with attributes for nodes from many graphs (for example, the graphs are the component subgraphs of an original graph). Our idea is that they could use that same dict in a `set_node_attributes` function call and if it can't find the node it just ignores that node.  We certainly shouldn't add the node to the graph -- for that we have `add_nodes_from`.  But should we silently ignore, or loudly raise an error. I can see arguments both ways. The current code silently ignores.

Resolving patch:
diff --git a/networkx/classes/function.py b/networkx/classes/function.py
--- a/networkx/classes/function.py
+++ b/networkx/classes/function.py
@@ -656,6 +656,17 @@ def set_node_attributes(G, values, name=None):
         >>> G.nodes[2]
         {}
 
+    Note that if the dictionary contains nodes that are not in `G`, the
+    values are silently ignored::
+
+        >>> G = nx.Graph()
+        >>> G.add_node(0)
+        >>> nx.set_node_attributes(G, {0: "red", 1: "blue"}, name="color")
+        >>> G.nodes[0]["color"]
+        'red'
+        >>> 1 in G.nodes
+        False
+
     """
     # Set node attributes based on type of `values`
     if name is not None:  # `values` must not be a dict of dict
@@ -765,6 +776,14 @@ def set_edge_attributes(G, values, name=None):
         >>> G[1][2]["attr2"]
         3
 
+    Note that if the dict contains edges that are not in `G`, they are
+    silently ignored::
+
+        >>> G = nx.Graph([(0, 1)])
+        >>> nx.set_edge_attributes(G, {(1, 2): {"weight": 2.0}})
+        >>> (1, 2) in G.edges()
+        False
+
     """
     if name is not None:
         # `values` does not contain attribute names


Test patch:
diff --git a/networkx/classes/tests/test_function.py b/networkx/classes/tests/test_function.py
--- a/networkx/classes/tests/test_function.py
+++ b/networkx/classes/tests/test_function.py
@@ -487,98 +487,151 @@ def test_custom2(self):
         self.test(G, 0, 0, [1, 2, 3])
 
 
-def test_set_node_attributes():
-    graphs = [nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()]
-    for G in graphs:
-        # Test single value
-        G = nx.path_graph(3, create_using=G)
-        vals = 100
-        attr = "hello"
-        nx.set_node_attributes(G, vals, attr)
-        assert G.nodes[0][attr] == vals
-        assert G.nodes[1][attr] == vals
-        assert G.nodes[2][attr] == vals
-
-        # Test dictionary
-        G = nx.path_graph(3, create_using=G)
-        vals = dict(zip(sorted(G.nodes()), range(len(G))))
-        attr = "hi"
-        nx.set_node_attributes(G, vals, attr)
-        assert G.nodes[0][attr] == 0
-        assert G.nodes[1][attr] == 1
-        assert G.nodes[2][attr] == 2
-
-        # Test dictionary of dictionaries
-        G = nx.path_graph(3, create_using=G)
-        d = {"hi": 0, "hello": 200}
-        vals = dict.fromkeys(G.nodes(), d)
-        vals.pop(0)
-        nx.set_node_attributes(G, vals)
-        assert G.nodes[0] == {}
-        assert G.nodes[1]["hi"] == 0
-        assert G.nodes[2]["hello"] == 200
-
-
-def test_set_edge_attributes():
-    graphs = [nx.Graph(), nx.DiGraph()]
-    for G in graphs:
-        # Test single value
-        G = nx.path_graph(3, create_using=G)
-        attr = "hello"
-        vals = 3
-        nx.set_edge_attributes(G, vals, attr)
-        assert G[0][1][attr] == vals
-        assert G[1][2][attr] == vals
-
-        # Test multiple values
-        G = nx.path_graph(3, create_using=G)
-        attr = "hi"
-        edges = [(0, 1), (1, 2)]
-        vals = dict(zip(edges, range(len(edges))))
-        nx.set_edge_attributes(G, vals, attr)
-        assert G[0][1][attr] == 0
-        assert G[1][2][attr] == 1
-
-        # Test dictionary of dictionaries
-        G = nx.path_graph(3, create_using=G)
-        d = {"hi": 0, "hello": 200}
-        edges = [(0, 1)]
-        vals = dict.fromkeys(edges, d)
-        nx.set_edge_attributes(G, vals)
-        assert G[0][1]["hi"] == 0
-        assert G[0][1]["hello"] == 200
-        assert G[1][2] == {}
-
-
-def test_set_edge_attributes_multi():
-    graphs = [nx.MultiGraph(), nx.MultiDiGraph()]
-    for G in graphs:
-        # Test single value
-        G = nx.path_graph(3, create_using=G)
-        attr = "hello"
-        vals = 3
-        nx.set_edge_attributes(G, vals, attr)
-        assert G[0][1][0][attr] == vals
-        assert G[1][2][0][attr] == vals
-
-        # Test multiple values
-        G = nx.path_graph(3, create_using=G)
-        attr = "hi"
-        edges = [(0, 1, 0), (1, 2, 0)]
-        vals = dict(zip(edges, range(len(edges))))
-        nx.set_edge_attributes(G, vals, attr)
-        assert G[0][1][0][attr] == 0
-        assert G[1][2][0][attr] == 1
-
-        # Test dictionary of dictionaries
-        G = nx.path_graph(3, create_using=G)
-        d = {"hi": 0, "hello": 200}
-        edges = [(0, 1, 0)]
-        vals = dict.fromkeys(edges, d)
-        nx.set_edge_attributes(G, vals)
-        assert G[0][1][0]["hi"] == 0
-        assert G[0][1][0]["hello"] == 200
-        assert G[1][2][0] == {}
+@pytest.mark.parametrize(
+    "graph_type", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
+)
+def test_set_node_attributes(graph_type):
+    # Test single value
+    G = nx.path_graph(3, create_using=graph_type)
+    vals = 100
+    attr = "hello"
+    nx.set_node_attributes(G, vals, attr)
+    assert G.nodes[0][attr] == vals
+    assert G.nodes[1][attr] == vals
+    assert G.nodes[2][attr] == vals
+
+    # Test dictionary
+    G = nx.path_graph(3, create_using=graph_type)
+    vals = dict(zip(sorted(G.nodes()), range(len(G))))
+    attr = "hi"
+    nx.set_node_attributes(G, vals, attr)
+    assert G.nodes[0][attr] == 0
+    assert G.nodes[1][attr] == 1
+    assert G.nodes[2][attr] == 2
+
+    # Test dictionary of dictionaries
+    G = nx.path_graph(3, create_using=graph_type)
+    d = {"hi": 0, "hello": 200}
+    vals = dict.fromkeys(G.nodes(), d)
+    vals.pop(0)
+    nx.set_node_attributes(G, vals)
+    assert G.nodes[0] == {}
+    assert G.nodes[1]["hi"] == 0
+    assert G.nodes[2]["hello"] == 200
+
+
+@pytest.mark.parametrize(
+    ("values", "name"),
+    (
+        ({0: "red", 1: "blue"}, "color"),  # values dictionary
+        ({0: {"color": "red"}, 1: {"color": "blue"}}, None),  # dict-of-dict
+    ),
+)
+def test_set_node_attributes_ignores_extra_nodes(values, name):
+    """
+    When `values` is a dict or dict-of-dict keyed by nodes, ensure that keys
+    that correspond to nodes not in G are ignored.
+    """
+    G = nx.Graph()
+    G.add_node(0)
+    nx.set_node_attributes(G, values, name)
+    assert G.nodes[0]["color"] == "red"
+    assert 1 not in G.nodes
+
+
+@pytest.mark.parametrize("graph_type", (nx.Graph, nx.DiGraph))
+def test_set_edge_attributes(graph_type):
+    # Test single value
+    G = nx.path_graph(3, create_using=graph_type)
+    attr = "hello"
+    vals = 3
+    nx.set_edge_attributes(G, vals, attr)
+    assert G[0][1][attr] == vals
+    assert G[1][2][attr] == vals
+
+    # Test multiple values
+    G = nx.path_graph(3, create_using=graph_type)
+    attr = "hi"
+    edges = [(0, 1), (1, 2)]
+    vals = dict(zip(edges, range(len(edges))))
+    nx.set_edge_attributes(G, vals, attr)
+    assert G[0][1][attr] == 0
+    assert G[1][2][attr] == 1
+
+    # Test dictionary of dictionaries
+    G = nx.path_graph(3, create_using=graph_type)
+    d = {"hi": 0, "hello": 200}
+    edges = [(0, 1)]
+    vals = dict.fromkeys(edges, d)
+    nx.set_edge_attributes(G, vals)
+    assert G[0][1]["hi"] == 0
+    assert G[0][1]["hello"] == 200
+    assert G[1][2] == {}
+
+
+@pytest.mark.parametrize(
+    ("values", "name"),
+    (
+        ({(0, 1): 1.0, (0, 2): 2.0}, "weight"),  # values dict
+        ({(0, 1): {"weight": 1.0}, (0, 2): {"weight": 2.0}}, None),  # values dod
+    ),
+)
+def test_set_edge_attributes_ignores_extra_edges(values, name):
+    """If `values` is a dict or dict-of-dicts containing edges that are not in
+    G, data associate with these edges should be ignored.
+    """
+    G = nx.Graph([(0, 1)])
+    nx.set_edge_attributes(G, values, name)
+    assert G[0][1]["weight"] == 1.0
+    assert (0, 2) not in G.edges
+
+
+@pytest.mark.parametrize("graph_type", (nx.MultiGraph, nx.MultiDiGraph))
+def test_set_edge_attributes_multi(graph_type):
+    # Test single value
+    G = nx.path_graph(3, create_using=graph_type)
+    attr = "hello"
+    vals = 3
+    nx.set_edge_attributes(G, vals, attr)
+    assert G[0][1][0][attr] == vals
+    assert G[1][2][0][attr] == vals
+
+    # Test multiple values
+    G = nx.path_graph(3, create_using=graph_type)
+    attr = "hi"
+    edges = [(0, 1, 0), (1, 2, 0)]
+    vals = dict(zip(edges, range(len(edges))))
+    nx.set_edge_attributes(G, vals, attr)
+    assert G[0][1][0][attr] == 0
+    assert G[1][2][0][attr] == 1
+
+    # Test dictionary of dictionaries
+    G = nx.path_graph(3, create_using=graph_type)
+    d = {"hi": 0, "hello": 200}
+    edges = [(0, 1, 0)]
+    vals = dict.fromkeys(edges, d)
+    nx.set_edge_attributes(G, vals)
+    assert G[0][1][0]["hi"] == 0
+    assert G[0][1][0]["hello"] == 200
+    assert G[1][2][0] == {}
+
+
+@pytest.mark.parametrize(
+    ("values", "name"),
+    (
+        ({(0, 1, 0): 1.0, (0, 2, 0): 2.0}, "weight"),  # values dict
+        ({(0, 1, 0): {"weight": 1.0}, (0, 2, 0): {"weight": 2.0}}, None),  # values dod
+    ),
+)
+def test_set_edge_attributes_multi_ignores_extra_edges(values, name):
+    """If `values` is a dict or dict-of-dicts containing edges that are not in
+    G, data associate with these edges should be ignored.
+    """
+    G = nx.MultiGraph([(0, 1, 0), (0, 1, 1)])
+    nx.set_edge_attributes(G, values, name)
+    assert G[0][1][0]["weight"] == 1.0
+    assert G[0][1][1] == {}
+    assert (0, 2) not in G.edges()
 
 
 def test_get_node_attributes():