Generating graphs from dense/numpy matrices
Is your feature request related to a problem? Please describe. Dear Grakel developers, I'm trying to generate grakel graphs from numpy connectivity matrices. I have two possible scenarios.
- A stack of adjacency matrices with Subjects x Nodes x Nodes. For each subject matrix I have edge strength indicated in node x node.
- A stack of adjacency/features matrices with Subjects x Nodes x Nodes x Adjacency/Features. The adjacency matrix is the same as in the first case, the feature matrix has the same dimensions (nodes x nodes) with slightly different features.
Now I want to create grakel graphs from these. What is the best way to do this?
Describe the solution you'd like Would it be possible to implement a method that automatically generates grakel graphs from dense adjacency matrices?
Describe alternatives you've considered Currently I have written my own (somewhat complicated) graph kernel conversion (https://github.com/wwu-mmll/photonai_graph/blob/main/photonai_graph/GraphKernels/grakel_adapter.py).
I first generate lists of node and edge features and then loop over each subject adjacency constructing the graph from the adjacency + features.
def transform(self, X):
"""sklearn compatible graph conversion"""
if self.input_type == "dense":
node_features = self.construct_node_features(X)
edge_features = self.construct_edge_features(X)
else:
node_features = None
edge_features = None
transformed = self.convert_grakel(X, self.input_type, node_features, edge_features, self.adjacency_axis)
I have attached the different functions for reference.
@staticmethod
def convert_grakel(graphs, in_format, node_labels, edge_features, adjacency_axis):
"""convert graphs into grakel format"""
g_trans = []
if in_format == "dense":
for g in range(graphs.shape[0]):
conv_g = graph.Graph(graphs[g, :, :, adjacency_axis], node_labels=node_labels[g],
edge_labels=edge_features[g])
g_trans.append(conv_g)
if in_format == "networkx":
g_trans = grakel.graph_from_networkx(graphs)
return g_trans
def construct_node_features(self, matrices):
"""construct node features from the feature matrix"""
label_list = []
for mtrx in range(matrices.shape[0]):
feat = self.get_dense_feature(matrices[mtrx, :, :, :], adjacency_axis=self.adjacency_axis,
feature_axis=self.feature_axis, aggregation=self.node_feature_construction)
label_list.append(feat)
return label_list
def construct_edge_features(self, matrices):
"""construct edge features from the feature or adjacency matrix"""
label_list = []
for mtrx in range(matrices.shape[0]):
feat = self.get_dense_edge_features(matrices[mtrx, :, :, :], adjacency_axis=self.adjacency_axis,
feature_axis=self.feature_axis)
label_list.append(feat)
return label_list
@staticmethod
def get_dense_edge_features(matrix, adjacency_axis, feature_axis):
"""returns the features for an edge label dictionary
Parameters
---------
matrix: np.matrix/np.ndarray
feature matrix
adjacency_axis: int
position of the adjacency matrix
feature_axis: int
position of the feature matrix
"""
edge_feat = {}
for index, value in np.ndenumerate(matrix[:, :, adjacency_axis]):
conn_key = (str(index[0]), str(index[1]))
key_val = {conn_key: value}
edge_feat.update(key_val)
return edge_feat
@staticmethod
def get_dense_feature(matrix, adjacency_axis, feature_axis, aggregation="sum"):
"""returns the features for a networkx graph
Parameters
---------
matrix: np.matrix/np.ndarray
feature matrix
adjacency_axis: int
position of the adjacency matrix
feature_axis: int
position of the feature matrix
aggregation:
method of feature construction, sum gives a row-wise sum,
"mean" gives a row-wise mean, "node_degree" give a row-wise node-degree,
features returns the entire row as the feature vector
"""
if aggregation == "sum":
features = np.sum(matrix[:, :, feature_axis], axis=1)
features = features.tolist()
elif aggregation == "mean":
features = (np.sum(matrix[:, :, feature_axis], axis=1)) / matrix.shape[0]
features = features.tolist()
elif aggregation == "node_degree":
features = np.count_nonzero(matrix[:, :, adjacency_axis], axis=1, keepdims=False)
features = features.tolist()
elif aggregation == "features":
features = matrix[:, :, feature_axis]
features = features.reshape((features.shape[0], -1))
features = features.tolist()
else:
raise KeyError('Only sum, mean, node_degree and all features are supported')
features = dict(enumerate(features, 0))
return features
@VHolstein thank you for opening this issue! We are really welcome to contributions! You are welcome to create a pull request about this issue, with the proper unit tests and optionally add the relevant documentation and examples.