Repository: networkx/networkx

Issue:
`from_pandas_edgelist` creates empty graph if there are no attributes
Using `from_pandas_edgelist` with `edge_attr=True` results in an empty graph if there are no attribute columns. If a user passes in a dataframe which _may_ contain attribute columns, it will yield an empty graph if there are no non-source/target columns.


Discussion:
That doesn't sound good.... Do you have a short piece of code that shows  the problem?
Thanks!
Thanks for your response! Here's a snippet that illustrates the issue.
```
import networkx as nx
import pandas as pd

edgeframe = pd.DataFrame([
        {"src": 0, "tgt": 1},
        {"src": 1, "tgt": 2},
        {"src": 2, "tgt": 0}])
graph = nx.from_pandas_edgelist(
        edgeframe,
        source="src",
        target="tgt",
        edge_attr=True)
print(graph.order()) # 0
print(graph.size()) # 0

edgeframe["weight"] = 0 # add a trivial attribute
graph = nx.from_pandas_edgelist(
        edgeframe,
        source="src",
        target="tgt",
        edge_attr=True)
print(graph.order()) # 3
print(graph.size()) # 3
```
Good -- thanks for that code. It is clear that near line 325 the code presumes that the dataframe has a column that is not indicating source or target. And if that column doesn't exist it will cause the ```zip()``` function later on to terminate before any edges are added.

This is a bug and should be easy to fix.  
Let's just raise an exception when no ```edge_attr``` columns exist? 
That might be cleanest...

Resolving patch:
diff --git a/networkx/convert_matrix.py b/networkx/convert_matrix.py
--- a/networkx/convert_matrix.py
+++ b/networkx/convert_matrix.py
@@ -274,10 +274,11 @@ def from_pandas_edgelist(df, source='source', target='target', edge_attr=None,
         A valid column name (string or integer) for the target nodes (for the
         directed case).
 
-    edge_attr : str or int, iterable, True
-        A valid column name (str or integer) or list of column names that will
-        be used to retrieve items from the row and add them to the graph as
-        edge attributes. If `True`, all of the remaining columns will be added.
+    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 `None`, no edge attributes are added to the graph.
 
     create_using : NetworkX graph constructor, optional (default=nx.Graph)
        Graph type to create. If graph instance, then cleared before populated.
@@ -332,6 +333,9 @@ def from_pandas_edgelist(df, source='source', target='target', edge_attr=None,
         cols = edge_attr
     else:
         cols = [edge_attr]
+    if len(cols) == 0:
+        msg = "Invalid edge_attr argument. No columns found with name: %s"
+        raise nx.NetworkXError(msg % cols)
 
     try:
         eattrs = zip(*[df[col] for col in cols])
@@ -339,14 +343,12 @@ def from_pandas_edgelist(df, source='source', target='target', edge_attr=None,
         msg = "Invalid edge_attr argument: %s" % edge_attr
         raise nx.NetworkXError(msg)
     for s, t, attrs in zip(df[source], df[target], eattrs):
-
-        g.add_edge(s, t)
-
         if g.is_multigraph():
-            key = max(g[s][t])  # default keys just count so max is most recent
-            g[s][t][key].update((attr, val) for attr, val in zip(cols, attrs))
+            key = g.add_edge(s, t)
+            g[s][t][key].update(zip(cols, attrs))
         else:
-            g[s][t].update((attr, val) for attr, val in zip(cols, attrs))
+            g.add_edge(s, t)
+            g[s][t].update(zip(cols, attrs))
 
     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
@@ -2,7 +2,8 @@
 from nose.tools import assert_raises
 
 import networkx as nx
-from networkx.testing import assert_nodes_equal, assert_edges_equal, assert_graphs_equal
+from networkx.testing import assert_nodes_equal, assert_edges_equal, \
+                             assert_graphs_equal
 
 
 class TestConvertPandas(object):
@@ -36,7 +37,8 @@ def test_exceptions(self):
         assert_raises(nx.NetworkXError, nx.to_networkx_graph, G)
         G = pd.DataFrame(["a", 0.0])  # elist
         assert_raises(nx.NetworkXError, nx.to_networkx_graph, G)
-        df = pd.DataFrame([[1, 1], [1, 0]], dtype=int, index=[1, 2], columns=["a", "b"])
+        df = pd.DataFrame([[1, 1], [1, 0]], dtype=int,
+                          index=[1, 2], columns=["a", "b"])
         assert_raises(nx.NetworkXError, nx.from_pandas_adjacency, df)
 
     def test_from_edgelist_all_attr(self):
@@ -67,24 +69,23 @@ def test_from_edgelist_multi_attr_incl_target(self):
 
     def test_from_edgelist_multidigraph_and_edge_attr(self):
         # example from issue #2374
-        Gtrue = nx.MultiDiGraph([('X1', 'X4', {'Co': 'zA', 'Mi': 0, 'St': 'X1'}),
-                                 ('X1', 'X4', {'Co': 'zB', 'Mi': 54, 'St': 'X2'}),
-                                 ('X1', 'X4', {'Co': 'zB', 'Mi': 49, 'St': 'X3'}),
-                                 ('X1', 'X4', {'Co': 'zB', 'Mi': 44, 'St': 'X4'}),
-                                 ('Y1', 'Y3', {'Co': 'zC', 'Mi': 0, 'St': 'Y1'}),
-                                 ('Y1', 'Y3', {'Co': 'zC', 'Mi': 34, 'St': 'Y2'}),
-                                 ('Y1', 'Y3', {'Co': 'zC', 'Mi': 29, 'St': 'X2'}),
-                                 ('Y1', 'Y3', {'Co': 'zC', 'Mi': 24, 'St': 'Y3'}),
-                                 ('Z1', 'Z3', {'Co': 'zD', 'Mi': 0, 'St': 'Z1'}),
-                                 ('Z1', 'Z3', {'Co': 'zD', 'Mi': 14, 'St': 'X3'}),
-                                 ('Z1', 'Z3', {'Co': 'zE', 'Mi': 9, 'St': 'Z2'}),
-                                 ('Z1', 'Z3', {'Co': 'zE', 'Mi': 4, 'St': 'Z3'})])
+        edges = [('X1', 'X4', {'Co': 'zA', 'Mi': 0, 'St': 'X1'}),
+                 ('X1', 'X4', {'Co': 'zB', 'Mi': 54, 'St': 'X2'}),
+                 ('X1', 'X4', {'Co': 'zB', 'Mi': 49, 'St': 'X3'}),
+                 ('X1', 'X4', {'Co': 'zB', 'Mi': 44, 'St': 'X4'}),
+                 ('Y1', 'Y3', {'Co': 'zC', 'Mi': 0, 'St': 'Y1'}),
+                 ('Y1', 'Y3', {'Co': 'zC', 'Mi': 34, 'St': 'Y2'}),
+                 ('Y1', 'Y3', {'Co': 'zC', 'Mi': 29, 'St': 'X2'}),
+                 ('Y1', 'Y3', {'Co': 'zC', 'Mi': 24, 'St': 'Y3'}),
+                 ('Z1', 'Z3', {'Co': 'zD', 'Mi': 0, 'St': 'Z1'}),
+                 ('Z1', 'Z3', {'Co': 'zD', 'Mi': 14, 'St': 'X3'})]
+        Gtrue = nx.MultiDiGraph(edges)
         df = pd.DataFrame.from_dict({
-            'O': ['X1', 'X1', 'X1', 'X1', 'Y1', 'Y1', 'Y1', 'Y1', 'Z1', 'Z1', 'Z1', 'Z1'],
-            'D': ['X4', 'X4', 'X4', 'X4', 'Y3', 'Y3', 'Y3', 'Y3', 'Z3', 'Z3', 'Z3', 'Z3'],
-            'St': ['X1', 'X2', 'X3', 'X4', 'Y1', 'Y2', 'X2', 'Y3', 'Z1', 'X3', 'Z2', 'Z3'],
-            'Co': ['zA', 'zB', 'zB', 'zB', 'zC', 'zC', 'zC', 'zC', 'zD', 'zD', 'zE', 'zE'],
-            'Mi': [0,   54,   49,   44,    0,   34,   29,   24,    0,   14,    9,   4]})
+            'O': ['X1', 'X1', 'X1', 'X1', 'Y1', 'Y1', 'Y1', 'Y1', 'Z1', 'Z1'],
+            'D': ['X4', 'X4', 'X4', 'X4', 'Y3', 'Y3', 'Y3', 'Y3', 'Z3', 'Z3'],
+            'St': ['X1', 'X2', 'X3', 'X4', 'Y1', 'Y2', 'X2', 'Y3', 'Z1', 'X3'],
+            'Co': ['zA', 'zB', 'zB', 'zB', 'zC', 'zC', 'zC', 'zC', 'zD', 'zD'],
+            'Mi': [0, 54, 49, 44, 0, 34, 29, 24, 0, 14]})
         G1 = nx.from_pandas_edgelist(df, source='O', target='D',
                                      edge_attr=True,
                                      create_using=nx.MultiDiGraph)
@@ -114,6 +115,14 @@ def test_from_edgelist_invalid_attr(self):
                       self.df, 0, 'b', 'misspell')
         assert_raises(nx.NetworkXError, nx.from_pandas_edgelist,
                       self.df, 0, 'b', 1)
+        # see Issue #3562
+        edgeframe = pd.DataFrame([[0, 1], [1, 2], [2, 0]], columns=['s', 't'])
+        assert_raises(nx.NetworkXError, nx.from_pandas_edgelist,
+                      edgeframe, 's', 't', True)
+        assert_raises(nx.NetworkXError, nx.from_pandas_edgelist,
+                      edgeframe, 's', 't', 'weight')
+        assert_raises(nx.NetworkXError, nx.from_pandas_edgelist,
+                      edgeframe, 's', 't', ['weight', 'size'])
 
     def test_from_edgelist_no_attr(self):
         Gtrue = nx.Graph([('E', 'C', {}),
@@ -144,7 +153,8 @@ def test_from_edgelist(self):
 
     def test_from_adjacency(self):
         nodelist = [1, 2]
-        dftrue = pd.DataFrame([[1, 1], [1, 0]], dtype=int, index=nodelist, columns=nodelist)
+        dftrue = pd.DataFrame([[1, 1], [1, 0]], dtype=int,
+                              index=nodelist, columns=nodelist)
         G = nx.Graph([(1, 1), (1, 2)])
         df = nx.to_pandas_adjacency(G, dtype=int)
         pd.testing.assert_frame_equal(df, dftrue)
@@ -156,7 +166,8 @@ def test_roundtrip(self):
         G = nx.from_pandas_edgelist(df)
         assert_graphs_equal(Gtrue, G)
         # adjacency
-        Gtrue = nx.Graph(({1: {1: {'weight': 1}, 2: {'weight': 1}}, 2: {1: {'weight': 1}}}))
+        adj = {1: {1: {'weight': 1}, 2: {'weight': 1}}, 2: {1: {'weight': 1}}}
+        Gtrue = nx.Graph(adj)
         df = nx.to_pandas_adjacency(Gtrue, dtype=int)
         G = nx.from_pandas_adjacency(df)
         assert_graphs_equal(Gtrue, G)