How to set edge feature in heterogeneous graph?

I use the code in the forum to transfer the networkx graph to DGL heterogeneous graph, but I get some problems here,

nxg = nx.DiGraph(...)
g = dgl.graph(nxg)
g.ndata[dgl.NTYPE] = ...    # assign node types from nxg
g.edata[dgl.ETYPE] = ...    # assign edge types from nxg
ntypes = [...]    # name of each node type ID
etypes = [...]    # name of each edge type ID
hg = dgl.to_hetero(g, ntypes, types)

edge_features = nx.get_edge_attributes(nxg, 'weight')
print(edge_features)
for etype in hg.canonical_etypes:
    nxg_edge_ids = hg.edges[etype].data[dgl.EID]
    hg.edges[etype].data['edge_weight'] = [edge_features[i.item()] for i in nxg_edge_ids]

It won’t work, because the edge_features return by networkx is index with two nodes, like (node1, node2): feature, can DGL accept this format instead of the edge id?

Will dgl.from_networkx work for you?

Can this construct a heterogeneous graph directly?

this post says it can’t

dgl.from_networkx basically acts like g = dgl.graph(nxg) in your code snippet except that dgl.graph no longer accepts NetworkX graphs as of DGL 0.5. It allows retrieving edge features directly if the features of all edge types have the same size/dimensionality.

So it can not deal with different edge types and heterogeneous graph?

You still need to use dgl.to_heterogeneous to convert the graph to a heterogeneous graph. To set edge features in your case, you can do something as follows

import dgl
import networkx as nx
import numpy as np
import torch

nx_g = nx.DiGraph()
# Add 4 nodes
nx_g.add_nodes_from([0, 1, 2, 3])
# Add 3 edges
nx_g.add_edge(1, 2, weight=np.ones((1, 1)))
nx_g.add_edge(2, 1, weight=np.ones((1, 1)))
nx_g.add_edge(1, 3, weight=np.zeros((1, 1)))
g = dgl.from_networkx(nx_g, edge_attrs=['weight'])
g.ndata[dgl.NTYPE] = torch.tensor([0, 0, 0, 0])
g.ndata[dgl.NID] = torch.tensor([0, 1, 2, 3])
g.edata[dgl.ETYPE] = torch.tensor([0, 1, 0])
g.edata[dgl.EID] = torch.tensor([0, 0, 1])
hg = dgl.to_heterogeneous(g, ['user'], ['follows', 'followed by'])

edge_features = nx.get_edge_attributes(nx_g, 'weight')
src, dst = g.edges()
for etype in hg.canonical_etypes:
     nxg_edge_ids = hg.edges[etype].data[dgl.EID]
     nxg_edge_end_nodes = [(src[i].item(), dst[i].item()) for i in nxg_edge_ids]
     hg.edges[etype].data['edge_weight'] = torch.from_numpy(np.concatenate([edge_features[e] for e in nxg_edge_end_nodes]))
1 Like

Thank you for your answer, this works, but I have a few questions.

  1. In g = dgl.from_networkx(nx_g, edge_attrs=['weight']) it seems edge_attrs=['weight'] is useless?
  2. Do we have to set dgl.NID and dgl.EID, it seems DGL can do this automatically?
  1. Yes, you are right. You can remove edge_attrs=['weight'].
  2. DGL has default values for dgl.NID and dgl.EID.

BTW. It seems the following code also runnable. Does it do everything right?

edge_features = list(nx.get_edge_attributes(nxg, 'edge_weight').values())
for etype in hg.canonical_etypes:
     nxg_edge_ids = hg.edges[etype].data[dgl.EID]
     hg.edges[etype].data['edge_weight'] = torch.as_tensor([edge_features[i.item()] for i in nxg_edge_ids], dtype=torch.float32)

It depends on whether the order of edges in nx.get_edge_attributes(nxg, 'edge_weight').values() is exactly the same as g.edges(), which I am not sure.

Thank you, It seems there’s no guarantee.