Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generating graphs from dense/numpy matrices #89

Open
VHolstein opened this issue May 2, 2023 · 1 comment
Open

Generating graphs from dense/numpy matrices #89

VHolstein opened this issue May 2, 2023 · 1 comment

Comments

@VHolstein
Copy link

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.

  1. A stack of adjacency matrices with Subjects x Nodes x Nodes. For each subject matrix I have edge strength indicated in node x node.
  2. 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

@ysig
Copy link
Owner

ysig commented Sep 7, 2023

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants