Repository: wwu-mmll/photonai_graph (language: python)

Issue:
Documentation/conversions
Added documentation for conversions.
Revise documentation
Please only change inline documentation scince #21


Discussion / hints:

Please use numpy comment style only!
#43 updates:

- [X] New color scheme
- [x] New images
- [x] New menu structure
- [x] Fixed codehighlighting
- [x] Fixed admonitions
- [x] Fixed page edit uri
- [x] New code font

Fix patch:
diff --git a/documentation/docs/api/graph_conversions.md b/documentation/docs/api/graph_conversions.md
new file mode 100644
index 0000000..4be8044
--- /dev/null
+++ b/documentation/docs/api/graph_conversions.md
@@ -0,0 +1,17 @@
+# Graph Conversion functions
+
+The conversion functions are a collection of different handy helper 
+functions for converting your input graphs between different toolboxes.
+
+Popular supported formats are:
+
+* numpy arrays
+* networkx graphs
+* dgl graphs
+* scipy sparse matrices
+
+PHOTONAI Graph will handle necessary conversions in your pipeline automatically.
+
+## Graph Conversion Functions
+
+::: photonai_graph.GraphConversions
diff --git a/documentation/docs/api/population_averaging.md b/documentation/docs/api/population_averaging.md
new file mode 100644
index 0000000..1ace455
--- /dev/null
+++ b/documentation/docs/api/population_averaging.md
@@ -0,0 +1,10 @@
+# Population Averaging
+Given a representative population sample, one could simply average these
+graphs and use the average adjacency with the individual connectivity as features.
+
+This transformer is intended for this case.
+
+## Population Averaging
+::: photonai_graph.PopulationAveragingTransform.PopulationAveragingTransform.__init__
+    rendering:
+        show_root_toc_entry: False
\ No newline at end of file
diff --git a/documentation/docs/assets/img/favicon.ico b/documentation/docs/assets/img/favicon.ico
new file mode 100644
index 0000000..21fb155
Binary files /dev/null and b/documentation/docs/assets/img/favicon.ico differ
diff --git a/documentation/docs/extending_photonai_graph.md b/documentation/docs/extending_photonai_graph.md
index d39c07d..b90c475 100644
--- a/documentation/docs/extending_photonai_graph.md
+++ b/documentation/docs/extending_photonai_graph.md
@@ -1,1 +1,9 @@
+Extending PHOTONAI Graph is equally easy as extending PHOTONAI itself.
+
+For new Transformers see <a target="_blank" href="https://wwu-mmll.github.io/photonai/api/custom_transformer/">adding Transformers</a>.
+
+For new Estimators see <a target="_blank", href="https://wwu-mmll.github.io/photonai/api/custom_estimator/">adding Estimators</a>
+
+## PHOTONAI Graph specific base classes
+
 Todo
\ No newline at end of file
diff --git a/documentation/docs/getting_started.md b/documentation/docs/getting_started.md
index 0916667..3d1dacb 100644
--- a/documentation/docs/getting_started.md
+++ b/documentation/docs/getting_started.md
@@ -3,6 +3,12 @@
 In order to get started, you will need connectivity matrices or data, that is already graph data. 
 For this introduction we will assume that you are using connectivity matrices.
 
+!!! info "Connectivity matrix"
+    Connectivity matrices are simply adjacency matrics.
+    Most commonly those matrices have to be thresholded with a static threshold or a percentage.
+    If your input  adjacency matrices also require a thresholding, you can also simply use the
+    Thresholding technique described below.
+
 ## 1. Loading data
 PHOTONAI Graph offers a plethora of different methods for loading your data.
 
diff --git a/documentation/docs/load_and_save.md b/documentation/docs/load_and_save.md
index 131a456..a8309e1 100644
--- a/documentation/docs/load_and_save.md
+++ b/documentation/docs/load_and_save.md
@@ -1,9 +1,21 @@
-PHOTONAI Graph offers various methods for saving and loading graphs.
+PHOTONAI Graph offers methods for saving and loading networkx graphs.
 Additionally, there are transformation functions for converting the graphs from one format into another.
 
 !!! info
     If a specific format is required for your analysis, PHOTONAI Graph will try to automatically
     convert your input graphs.
+
+
 ## Loading graphs
+Graphs can be loaded from different formats into networkx format.
+From this networkx graph you can use different conversion functions to convert them into the 
+desired format.
+
+### Load networkx graph from file
+::: photonai_graph.GraphConversions.load_file_to_networkx
+
+## Saving graphs
+Graphs can be saved as networkx 
 
-## Saving graphs
\ No newline at end of file
+### Graph to networkx file
+::: photonai_graph.GraphConversions.save_networkx_to_file
\ No newline at end of file
diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml
index 50c6221..93bbebe 100644
--- a/documentation/mkdocs.yml
+++ b/documentation/mkdocs.yml
@@ -21,6 +21,8 @@ theme:
   font:
     text: 'Roboto'
     code: 'Space Mono'
+  logo: assets/img/favicon.ico
+  favicon: assets/img/favicon.ico
 
 # Latex
 extra_javascript:
@@ -56,6 +58,8 @@ nav:
          'api/graph_construction.md'
       - Examples:
          'api/examples/graph_construction_examples.md'
+  - Graph Conversions: 'api/graph_conversions.md'
+  - Population Averaging: 'api/population_averaging.md'
   - Dynamic Utilities: 'api/dynamic_utils.md'
   - Graph Measures: 'api/graph_measures.md'
   - Graph Embeddings: 'api/graph_embeddings.md'
diff --git a/examples/embeding_examples/GraphEmbedding_lem_example.py b/examples/embeding_examples/GraphEmbedding_lem_example.py
index 0435fb6..c361335 100644
--- a/examples/embeding_examples/GraphEmbedding_lem_example.py
+++ b/examples/embeding_examples/GraphEmbedding_lem_example.py
@@ -15,8 +15,7 @@
                     metrics=['mean_absolute_error'],
                     best_config_metric='mean_absolute_error')
 
-my_pipe.add(PipelineElement('GraphConstructorThreshold',
-                            hyperparameters={'threshold': 0.95}))
+my_pipe.add(PipelineElement('GraphConstructorThreshold', threshold=0.95))
 
 my_pipe.add(PipelineElement('GraphEmbeddingLaplacianEigenmaps'))
 
diff --git a/examples/embeding_examples/GraphEmbedding_lle_example.py b/examples/embeding_examples/GraphEmbedding_lle_example.py
index bab7a17..89abca6 100644
--- a/examples/embeding_examples/GraphEmbedding_lle_example.py
+++ b/examples/embeding_examples/GraphEmbedding_lle_example.py
@@ -15,8 +15,7 @@
                     metrics=['accuracy', 'balanced_accuracy', 'recall', 'precision'],
                     best_config_metric='accuracy')
 
-my_pipe.add(PipelineElement('GraphConstructorThreshold',
-                            hyperparameters={'threshold': 0.95}))
+my_pipe.add(PipelineElement('GraphConstructorThreshold', threshold=0.95))
 
 my_pipe.add(PipelineElement('GraphEmbeddingLocallyLinearEmbedding'))
 
diff --git a/examples/kernel_examples/GraphKernel_PyramidMatch_example.py b/examples/kernel_examples/GraphKernel_PyramidMatch_example.py
index 9866453..19582e7 100644
--- a/examples/kernel_examples/GraphKernel_PyramidMatch_example.py
+++ b/examples/kernel_examples/GraphKernel_PyramidMatch_example.py
@@ -14,15 +14,12 @@
                     metrics=['accuracy', 'balanced_accuracy', 'recall', 'precision'],
                     best_config_metric='accuracy')
 
-my_pipe.add(PipelineElement('GraphConstructorThreshold',
-                            hyperparameters={'threshold': 0.95}))
+my_pipe.add(PipelineElement('GraphConstructorThreshold', threshold=0.95))
 
-my_pipe.add(PipelineElement('GrakelAdapter',
-                            hyperparameters={'node_feature_construction': "sum"}))
+my_pipe.add(PipelineElement('GrakelAdapter', node_feature_construction="sum"))
 
 my_pipe.add(PipelineElement('PyramidMatch'))
 
-my_pipe.add(PipelineElement("SVC",
-                            hyperparameters={'kernel': 'precomputed'}))
+my_pipe.add(PipelineElement("SVC", kernel='precomputed'))
 
 my_pipe.fit(X, y)
diff --git a/examples/kernel_examples/GraphKernel_SVMtheta_example.py b/examples/kernel_examples/GraphKernel_SVMtheta_example.py
index 0a4c5b8..2087bad 100644
--- a/examples/kernel_examples/GraphKernel_SVMtheta_example.py
+++ b/examples/kernel_examples/GraphKernel_SVMtheta_example.py
@@ -14,14 +14,12 @@
                     metrics=['mean_absolute_error'],
                     best_config_metric='mean_absolute_error')
 
-my_pipe.add(PipelineElement('GraphConstructorThreshold',
-                            hyperparameters={'threshold': 0.95}))
+my_pipe.add(PipelineElement('GraphConstructorThreshold', threshold=0.95))
 
 my_pipe.add(PipelineElement('GrakelAdapter'))
 
 my_pipe.add(PipelineElement('SvmTheta'))
 
-my_pipe.add(PipelineElement("SVR",
-                            hyperparameters={'kernel': 'precomputed'}))
+my_pipe.add(PipelineElement("SVR", kernel='precomputed'))
 
 my_pipe.fit(X, y)
diff --git a/examples/measure_examples/GraphMeasure_extraction_example.py b/examples/measure_examples/GraphMeasure_extraction_example.py
index 3560acf..85e0c1c 100644
--- a/examples/measure_examples/GraphMeasure_extraction_example.py
+++ b/examples/measure_examples/GraphMeasure_extraction_example.py
@@ -7,7 +7,7 @@
 
 
 # instantiate a constructor to threshold the graphs
-g_constructor = GraphConstructorThreshold(threshold=0.95, transform_style="individual")
+g_constructor = GraphConstructorThreshold(threshold=0.95)
 
 # instantiate the measure_transformer
 g_measure_transformer = GraphMeasureTransform(graph_functions={"global_efficiency": {},
diff --git a/examples/measure_examples/GraphMeasure_pipeline_example.py b/examples/measure_examples/GraphMeasure_pipeline_example.py
index 3004a05..e73aad4 100644
--- a/examples/measure_examples/GraphMeasure_pipeline_example.py
+++ b/examples/measure_examples/GraphMeasure_pipeline_example.py
@@ -14,16 +14,14 @@
                     metrics=['mean_absolute_error'],
                     best_config_metric='mean_absolute_error')
 
-my_pipe.add(PipelineElement('GraphConstructorThreshold',
-                            hyperparameters={'threshold': 0.95}))
+my_pipe.add(PipelineElement('GraphConstructorThreshold', threshold=0.95))
 
-my_pipe.add(PipelineElement('GraphMeasureTransform',
-                            hyperparameters={'graph_functions': {"global_efficiency": {},
-                                                                 "local_efficiency": {},
-                                                                 "average_clustering": {},
-                                                                 "degree_centrality": {},
-                                                                 "betweenness_centrality": {},
-                                                                 "overall_reciprocity": {}}}))
+my_pipe.add(PipelineElement('GraphMeasureTransform', graph_functions={"global_efficiency": {},
+                                                                      "local_efficiency": {},
+                                                                      "average_clustering": {},
+                                                                      "degree_centrality": {},
+                                                                      "betweenness_centrality": {},
+                                                                      "overall_reciprocity": {}}))
 
 my_pipe.add(PipelineElement("SVR"))
 
diff --git a/photonai_graph/GraphConstruction/graph_constructor.py b/photonai_graph/GraphConstruction/graph_constructor.py
index ab9a4ed..cd94c32 100644
--- a/photonai_graph/GraphConstruction/graph_constructor.py
+++ b/photonai_graph/GraphConstruction/graph_constructor.py
@@ -11,9 +11,9 @@ class GraphConstructor(BaseEstimator, TransformerMixin, ABC):
     _estimator_type = "transformer"
 
     def __init__(self,
-                 transform_style: str = "mean",
                  one_hot_nodes: int = 0,
                  fisher_transform: int = 0,
+                 discard_original_connectivity: bool = False,
                  use_abs: int = 0,
                  zscore: int = 0,
                  use_abs_zscore: int = 0,
@@ -25,11 +25,13 @@ def __init__(self,
 
         Parameters
         ----------
-        transform_style: str, default="mean"
         one_hot_nodes: int, default=0
             whether to return a one hot node encoding as feature or not
         fisher_transform: int, default=0
             whether to perform a fisher transform of every matrix
+        discard_original_connectivity: bool,default=False
+            If true the second index of the last dimension will be the original connectivity.
+            Otherwise the original connectivity will be dropped from the matrix.
         use_abs: int
             Changes the values to absolute values. Is applied after fisher transform and before
             z-score transformation
@@ -44,9 +46,11 @@ def __init__(self,
         logs: str, default=None
             Path to the log data
         """
-        self.transform_style = transform_style
+        if one_hot_nodes not in [0, 1]:
+            raise ValueError("one_hot_nodes must be in [0, 1]")
         self.one_hot_nodes = one_hot_nodes
         self.fisher_transform = fisher_transform
+        self.discard_original_connectivity = discard_original_connectivity
         self.use_abs = use_abs
         self.zscore = zscore
         self.use_abs_zscore = use_abs_zscore
@@ -58,85 +62,46 @@ def __init__(self,
         self.mean_matrix = None
 
     def fit(self, X, y):
-        """implements a mean matrix construction"""
-
-        if self.transform_style == "mean" or self.transform_style == "Mean":
-            # generate mean matrix
-            X_mean = np.squeeze(np.mean(X, axis=0))
-            # select the proper matrix in case you have multiple
-            if np.ndim(X_mean) == 3:
-                X_mean = X_mean[:, :, self.adjacency_axis]
-            elif np.ndim(X_mean) == 2:
-                X_mean = X_mean
-            else:
-                raise ValueError('The input matrices need to have 3 or 4 dimensions. Please check your input matrix.')
-            # assign mean matrix for later use
-            self.mean_matrix = X_mean
-
-        elif self.transform_style == "individual" or self.transform_style == "Individual":
-            pass
-
-        else:
-            raise ValueError('transform_style needs to be individual or mean')
-
         return self
 
     def get_mtrx(self, X):
         """Returns a feature and adjacency matrix"""
 
-        if self.transform_style == "individual":
-
-            # ensure that the array has the "right" number of dimensions
-            if np.ndim(X) == 4:
-                adjacency_matrix = X[:, :, :, self.adjacency_axis].copy()
-                adjacency_matrix = adjacency_matrix[:, :, :, np.newaxis]  # return 4d
-                feature_matrix = X.copy()
-            # handle the case where there are 3 dimensions, meaning that there is no "adjacency axis"
-            elif np.ndim(X) == 3:
-                adjacency_matrix = X.copy().reshape(X.shape[0], X.shape[1], X.shape[2], -1)
-                feature_matrix = X.copy().reshape(X.shape[0], X.shape[1], X.shape[2], -1)
-            else:
-                raise Exception('input matrix needs to have 3 or 4 dimensions')
-
-        elif self.transform_style == "mean":
-            adjacency_matrix = self.mean_matrix.copy()
-            adjacency_matrix = adjacency_matrix[np.newaxis, :, :, np.newaxis]
-            adjacency_matrix = np.repeat(adjacency_matrix, X.shape[0], axis=0)
-            if np.ndim(X) == 4:
-                feature_matrix = X.copy()
-            elif np.ndim(X) == 3:
-                feature_matrix = X.copy().reshape(X.shape[0], X.shape[1], X.shape[2], -1)
-            else:
-                raise Exception('input matrix needs to have 3 or 4 dimensions')
-
+        # ensure that the array has the "right" number of dimensions
+        if np.ndim(X) == 4:
+            adjacency_matrix = X[:, :, :, self.adjacency_axis].copy()
+            adjacency_matrix = adjacency_matrix[:, :, :, np.newaxis]  # return 4d
+            feature_matrix = X.copy()
+        # handle the case where there are 3 dimensions, meaning that there is no "adjacency axis"
+        elif np.ndim(X) == 3:
+            adjacency_matrix = X.copy().reshape(X.shape[0], X.shape[1], X.shape[2], -1)
+            feature_matrix = X.copy().reshape(X.shape[0], X.shape[1], X.shape[2], -1)
         else:
-            raise ValueError('transform_style needs to be individual or mean')
+            raise ValueError('input matrix needs to have 3 or 4 dimensions')
 
         return adjacency_matrix, feature_matrix
 
     def prep_mtrx(self, adjacency):
         """transforms the matrix according to selected criteria"""
-        if self.fisher_transform == 1:
+        if self.fisher_transform:
             adjacency = individual_fishertransform(adjacency)
-        if self.use_abs == 1:
+        if self.use_abs:
             adjacency = np.abs(adjacency)
-        if self.zscore == 1:
+        if self.zscore:
             adjacency = individual_ztransform(adjacency)
-        if self.use_abs_zscore == 1:
+        if self.use_abs_zscore:
             adjacency = np.abs(adjacency)
 
         return adjacency
 
     def get_features(self, adjacency, features):
         """Returns a concatenated version of feature matrix and one hot nodes"""
-        if self.one_hot_nodes == 0:
-            X_transformed = np.concatenate((adjacency, features), axis=3)
-        elif self.one_hot_nodes == 1:
-            X_transformed = self.get_one_hot_nodes(adjacency)
-        else:
-            raise ValueError('one_hot_nodes needs to be 0 or 1.')
-
-        return X_transformed
+        if self.discard_original_connectivity:
+            features = features[..., 1:]
+        if self.one_hot_nodes:
+            adjacency = self.get_one_hot_nodes(adjacency)
+        transformed = np.concatenate((adjacency, features), axis=3)
+        return transformed
 
     @staticmethod
     def get_one_hot_nodes(X):
@@ -147,9 +112,8 @@ def get_one_hot_nodes(X):
         identity_matrix = identity_matrix[np.newaxis, :, :, np.newaxis]
         # repeat along first dimension and concatenate
         one_hot = np.repeat(identity_matrix, X.shape[0], 0)
-        X_concat = np.concatenate((X, one_hot), axis=3)
-
-        return X_concat
+        concat = np.concatenate((X, one_hot), axis=3)
+        return concat
 
     @staticmethod
     def adjacency(dist, idx):
diff --git a/photonai_graph/GraphConstruction/graph_constructor_knn.py b/photonai_graph/GraphConstruction/graph_constructor_knn.py
index d04cab6..cc80eeb 100644
--- a/photonai_graph/GraphConstruction/graph_constructor_knn.py
+++ b/photonai_graph/GraphConstruction/graph_constructor_knn.py
@@ -7,7 +7,6 @@ class GraphConstructorKNN(GraphConstructor):
 
     def __init__(self,
                  k_distance: int = 10,
-                 transform_style: str = "individual",
                  one_hot_nodes: int = 0,
                  fisher_transform: int = 0,
                  use_abs: int = 0,
@@ -59,8 +58,7 @@ def __init__(self,
                                     hyperparameters={'k_distance': 6}))
         ```
        """
-        super(GraphConstructorKNN, self).__init__(transform_style=transform_style,
-                                                  one_hot_nodes=one_hot_nodes,
+        super(GraphConstructorKNN, self).__init__(one_hot_nodes=one_hot_nodes,
                                                   fisher_transform=fisher_transform,
                                                   use_abs=use_abs,
                                                   zscore=zscore,
diff --git a/photonai_graph/GraphConstruction/graph_constructor_percentage.py b/photonai_graph/GraphConstruction/graph_constructor_percentage.py
index 1811daa..e32a00d 100644
--- a/photonai_graph/GraphConstruction/graph_constructor_percentage.py
+++ b/photonai_graph/GraphConstruction/graph_constructor_percentage.py
@@ -6,8 +6,7 @@ class GraphConstructorPercentage(GraphConstructor):
     _estimator_type = "transformer"
 
     def __init__(self, percentage: float = 80,
-                 retain_weights: int = 0,
-                 transform_style: str = "individual",
+                 retain_weights: float = 0,
                  one_hot_nodes: int = 0,
                  fisher_transform: int = 0,
                  use_abs: int = 0,
@@ -37,7 +36,7 @@ def __init__(self, percentage: float = 80,
             performs a zscore transformation of the data. Applied after fisher transform and np_abs
         use_abs_zscore: int, default=0
             whether to use the absolute values of the z-score transformation or allow for negative values
-        retain_weights: int
+        retain_weights: float
             whether to retain weight values or not
 
         Example
@@ -59,8 +58,7 @@ def __init__(self, percentage: float = 80,
 
 
        """
-        super(GraphConstructorPercentage, self).__init__(transform_style=transform_style,
-                                                         one_hot_nodes=one_hot_nodes,
+        super(GraphConstructorPercentage, self).__init__(one_hot_nodes=one_hot_nodes,
                                                          fisher_transform=fisher_transform,
                                                          use_abs=use_abs,
                                                          zscore=zscore,
diff --git a/photonai_graph/GraphConstruction/graph_constructor_percentage_window.py b/photonai_graph/GraphConstruction/graph_constructor_percentage_window.py
index 713238e..e8b1135 100644
--- a/photonai_graph/GraphConstruction/graph_constructor_percentage_window.py
+++ b/photonai_graph/GraphConstruction/graph_constructor_percentage_window.py
@@ -8,8 +8,7 @@ class GraphConstructorPercentageWindow(GraphConstructor):
     def __init__(self,
                  percentage_upper: float = 50,
                  percentage_lower: float = 10,
-                 retain_weights: int = 0,
-                 transform_style: str = "individual",
+                 retain_weights: float = 0,
                  one_hot_nodes: int = 0,
                  fisher_transform: int = 0,
                  use_abs: int = 0,
@@ -61,8 +60,7 @@ def __init__(self,
                                     hyperparameters={'percentage_upper': 0.9, 'percentage_lower': 0.7}))
         ```
        """
-        super(GraphConstructorPercentageWindow, self).__init__(transform_style=transform_style,
-                                                               one_hot_nodes=one_hot_nodes,
+        super(GraphConstructorPercentageWindow, self).__init__(one_hot_nodes=one_hot_nodes,
                                                                fisher_transform=fisher_transform,
                                                                use_abs=use_abs,
                                                                zscore=zscore,
diff --git a/photonai_graph/GraphConstruction/graph_constructor_random_walks.py b/photonai_graph/GraphConstruction/graph_constructor_random_walks.py
index 36d8eb3..f5ea4e7 100644
--- a/photonai_graph/GraphConstruction/graph_constructor_random_walks.py
+++ b/photonai_graph/GraphConstruction/graph_constructor_random_walks.py
@@ -13,7 +13,6 @@ def __init__(self,
                  window_size: int = 5,
                  no_edge_weight: int = 1,
                  feature_axis=1,
-                 transform_style: str = "individual",
                  one_hot_nodes: int = 0,
                  fisher_transform: int = 0,
                  use_abs: int = 0,
@@ -35,9 +34,6 @@ def __init__(self,
         ----------
         k_distance: int
             the k nearest neighbours value, for the kNN algorithm.
-        transform_style: str,default="mean"
-            generate an adjacency matrix based on the mean matrix like in Ktena et al.: "mean" or per person "individual"
-            Or generate a different matrix for every individual: "individual"
         number_of_walks: int,default=10
             number of walks to take to sample the random walk matrix
         walk_length: int,default=10
@@ -66,7 +62,6 @@ def __init__(self,
 
         ```python
         constructor = GraphConstructorRandomWalks(k_distance=5,
-                                                  transform_style="individual",
                                                   number_of_walks=25,
                                                   fisher_transform=1,
                                                   use_abs=1)
@@ -76,12 +71,11 @@ def __init__(self,
 
         ```python
         my_pipe.add(PipelineElement('GraphConstructorRandomWalks',
-                                    hyperparameters={'k_distance': 5, 'transform_style': "individual",
+                                    hyperparameters={'k_distance': 5,
                                     'number_of_walks': 25}))
         ```
        """
-        super(GraphConstructorRandomWalks, self).__init__(transform_style=transform_style,
-                                                          one_hot_nodes=one_hot_nodes,
+        super(GraphConstructorRandomWalks, self).__init__(one_hot_nodes=one_hot_nodes,
                                                           fisher_transform=fisher_transform,
                                                           use_abs=use_abs,
                                                           zscore=zscore,
diff --git a/photonai_graph/GraphConstruction/graph_constructor_spatial.py b/photonai_graph/GraphConstruction/graph_constructor_spatial.py
index 60ef8cb..672c506 100644
--- a/photonai_graph/GraphConstruction/graph_constructor_spatial.py
+++ b/photonai_graph/GraphConstruction/graph_constructor_spatial.py
@@ -12,7 +12,6 @@ def __init__(self,
                  k_distance: int = 10,
                  atlas_name: str = 'ho',
                  atlas_folder: str = "",
-                 transform_style: str = "individual",
                  one_hot_nodes: int = 0,
                  fisher_transform: int = 0,
                  use_abs: int = 0,
@@ -33,9 +32,6 @@ def __init__(self,
         ----------
         k_distance: int
             the k nearest neighbours value, for the kNN algorithm.
-        transform_style: str, default="mean"
-            generate an adjacency matrix based on the mean matrix like in Ktena et al.: "mean"
-            Or generate a different matrix for every individual: "individual"
         atlas_name: str,default="ho"
             name of the atlas coordinate file
         atlas_folder: str,default="ho"
@@ -60,7 +56,6 @@ def __init__(self,
 
         ```python
         constructor = GraphConstructorSpatial(k_distance=7,
-                                              transform_style="individual",
                                               atlas_name="ho_coords.csv",
                                               atlas_path="path/to/your/data/",
                                               fisher_transform=1,
@@ -71,12 +66,11 @@ def __init__(self,
 
         ```python
         my_pipe.add(PipelineElement('GraphConstructorSpatial',
-                                    hyperparameters={'k_distance': 7, 'transform_style': "individual",
+                                    hyperparameters={'k_distance': 7,
                                     'atlas_name': "ho_coords.csv", 'atlas_path': "path/to/your/data/"}))
         ```
        """
-        super(GraphConstructorSpatial, self).__init__(transform_style=transform_style,
-                                                      one_hot_nodes=one_hot_nodes,
+        super(GraphConstructorSpatial, self).__init__(one_hot_nodes=one_hot_nodes,
                                                       fisher_transform=fisher_transform,
                                                       use_abs=use_abs,
                                                       zscore=zscore,
diff --git a/photonai_graph/GraphConstruction/graph_constructor_threshold.py b/photonai_graph/GraphConstruction/graph_constructor_threshold.py
index ab6ca19..008b11f 100644
--- a/photonai_graph/GraphConstruction/graph_constructor_threshold.py
+++ b/photonai_graph/GraphConstruction/graph_constructor_threshold.py
@@ -8,7 +8,7 @@ def __init__(self, threshold: float = 0.1,
                  concatenation_axis: int = 3,
                  return_adjacency_only: int = 0,
                  retain_weights: int = 0,
-                 transform_style: str = "individual",
+                 discard_original_connectivity: bool = False,
                  one_hot_nodes: int = 0,
                  fisher_transform: int = 0,
                  use_abs: int = 0,
@@ -30,6 +30,8 @@ def __init__(self, threshold: float = 0.1,
             position of the adjacency matrix, default being zero
         concatenation_axis: int
             axis along which to concatenate the adjacency and feature matrix
+        discard_original_connectivity: bool,default=False
+            If true the original connectivity will be passed on. Otherwise it gets discarded
         one_hot_nodes: int
             Whether to generate a one hot encoding of the nodes in the matrix (1) or not (0)
         return_adjacency_only: int
@@ -65,9 +67,9 @@ def __init__(self, threshold: float = 0.1,
                                     hyperparameters={'threshold': 0.5}))
         ```
        """
-        super(GraphConstructorThreshold, self).__init__(transform_style=transform_style,
-                                                        one_hot_nodes=one_hot_nodes,
+        super(GraphConstructorThreshold, self).__init__(one_hot_nodes=one_hot_nodes,
                                                         fisher_transform=fisher_transform,
+                                                        discard_original_connectivity=discard_original_connectivity,
                                                         use_abs=use_abs,
                                                         zscore=zscore,
                                                         use_abs_zscore=use_abs_zscore,
diff --git a/photonai_graph/GraphConstruction/graph_constructor_threshold_window.py b/photonai_graph/GraphConstruction/graph_constructor_threshold_window.py
index 5e9d550..bfc6b25 100644
--- a/photonai_graph/GraphConstruction/graph_constructor_threshold_window.py
+++ b/photonai_graph/GraphConstruction/graph_constructor_threshold_window.py
@@ -7,8 +7,7 @@ class GraphConstructorThresholdWindow(GraphConstructor):
     def __init__(self,
                  threshold_upper: float = 1,
                  threshold_lower: float = 0.8,
-                 retain_weights: int = 0,
-                 transform_style: str = "individual",
+                 retain_weights: float = 0,
                  one_hot_nodes: int = 0,
                  fisher_transform: int = 0,
                  use_abs: int = 0,
@@ -63,8 +62,7 @@ def __init__(self,
                                     hyperparameters={'threshold_upper': 0.7, 'threshold_lower': 0.3}))
         ```
        """
-        super(GraphConstructorThresholdWindow, self).__init__(transform_style=transform_style,
-                                                              one_hot_nodes=one_hot_nodes,
+        super(GraphConstructorThresholdWindow, self).__init__(one_hot_nodes=one_hot_nodes,
                                                               fisher_transform=fisher_transform,
                                                               use_abs=use_abs,
                                                               zscore=zscore,
diff --git a/photonai_graph/GraphConversions.py b/photonai_graph/GraphConversions.py
index 6e4fed6..639eeab 100644
--- a/photonai_graph/GraphConversions.py
+++ b/photonai_graph/GraphConversions.py
@@ -6,6 +6,7 @@
 import warnings
 import yaml
 from photonai_graph.util import assert_imported
+
 try:
     import dgl
 except ImportError:
@@ -17,7 +18,7 @@
     "MultilineAdjacencyList": nx.write_multiline_adjlist,
     "EdgeList": nx.write_edgelist,
     "WeightedEdgeList": nx.write_weighted_edgelist,
-    "GEXF":  nx.write_gexf,
+    "GEXF": nx.write_gexf,
     "pickle": nx.write_gpickle,
     "GLM": nx.write_gml,
     "GraphML": nx.write_graphml,
@@ -72,35 +73,42 @@ def save_networkx_to_file(graphs, path, output_format="dot", ids=None):
             specified the graphs are just numbered as graph_x
 
     """
+    def save_single_networkx_to_file(graph, path, output_format="dot"):
+        """internal helper function
+
+        Parameters
+        ----------
+        graph
+            networkx graph object
+        path
+            path to save the graph to
+        output_format
+            desired output format
+        """
+        if not isinstance(graph, nx.classes.graph.Graph):
+            raise ValueError(f"Got unknown object for serialization: {type(graph)}")
+        output_formats[output_format](graph, path)
 
-    # run graph_writing with IDs
     if output_format not in output_formats:
         raise ValueError("The output format is not supported. Please check your output format.")
 
-    # Case 1: a list of networkx graphs as input
-    if isinstance(graphs, list):
-
-        # Check if we have got a list of ids
-        if ids is None or not isinstance(ids, list):
-            ids = np.arange(len(graphs))
-
-        # check if id and graphs they have equal length
-        if len(graphs) != len(ids):
-            raise ValueError(
-                'The photonai_graph ID list and the list of Graphs are not of equal length. '
-                'Please ensure that they have equal length.')
+    if not isinstance(graphs, list):
+        return save_single_networkx_to_file(graph=graphs, path=path, output_format=output_format)
 
-        for graph, i in zip(graphs, ids):
-            graph_filename = "graph_" + str(i)
-            graph_path = os.path.join(path, graph_filename)
-            output_formats[output_format](graph, graph_path)
+    # Check if we have got a list of ids
+    if ids is None or not isinstance(ids, list):
+        ids = np.arange(len(graphs))
 
-    # Case 2: the input is just a single photonai_graph
-    elif isinstance(graphs, nx.classes.graph.Graph):
-        output_formats[output_format](graphs, path)
+    # check if id and graphs they have equal length
+    if len(graphs) != len(ids):
+        raise ValueError(
+            'The photonai_graph ID list and the list of Graphs are not of equal length. '
+            'Please ensure that they have equal length.')
 
-    else:
-        raise ValueError('Your input is not supported right now.\nPlease provide a list of graphs or a single graph')
+    for graph, i in zip(graphs, ids):
+        graph_filename = "graph_" + str(i)
+        graph_path = os.path.join(path, graph_filename)
+        save_single_networkx_to_file(graph=graph, path=graph_path, output_format=output_format)
 
 
 def load_file_to_networkx(path, input_format="dot"):
@@ -112,6 +120,11 @@ def load_file_to_networkx(path, input_format="dot"):
             path(s) where graphs are stored
         input_format:
             format in which these graphs are stored
+
+        Returns
+        -------
+        list
+            List of loaded networkx graphs
     """
     if isinstance(path, str):
         path = [path]
@@ -134,23 +147,35 @@ def networkx_to_dense(graphs):
         ----------
             graphs: list of graphs
 
-        :returns
-            list of numpy arrays of a single numpy array
+        Returns
+        -------
+            list of numpy arrays or a single numpy array
     """
-    if isinstance(graphs, list):
-        graph_list = []
-        for graph in graphs:
-            np_graph = nx.to_numpy_array(graph)
-            graph_list.append(np_graph)
-    elif isinstance(graphs, nx.classes.graph.Graph):
-        graph_list = nx.to_numpy_array(graphs)
-    else:
-        raise ValueError('Input needs to be either a list of networkx graphs or a networkx photonai_graph.')
 
-    return graph_list
+    def single_networkx_to_dense(graph):
+        """INTERNAL HELPER FUNCTION
+        convert a networkx graph to a numpy array
+
+        Parameters
+        ----------
+        graph: nx.classes.graph.Graph
+            Input networkx graph object
+
+        Returns
+        -------
+        np.ndarray
+            Numpy representation of the input graph
+        """
+        if not isinstance(graph, nx.classes.graph.Graph):
+            raise ValueError("Non nx graph object was given as input")
+        return nx.to_numpy_array(graph)
+    if not isinstance(graphs, list):
+        return single_networkx_to_dense(graphs)
+
+    return [single_networkx_to_dense(graph) for graph in graphs]
 
 
-def networkx_to_sparse(graphs, fmt='csr'):
+def networkx_to_sparse(graphs, fmt: str = 'csr'):
     """converts networkx graph to sparse graphs
 
         Parameters
@@ -160,17 +185,29 @@ def networkx_to_sparse(graphs, fmt='csr'):
         fmt: str, default="csr"
             format of the scipy sparse matrix
     """
-    if isinstance(graphs, list):
-        graph_list = []
-        for graph in graphs:
-            sparse_graph = nx.to_scipy_sparse_matrix(graph, format=fmt)
-            graph_list.append(sparse_graph)
-    elif isinstance(graphs, nx.classes.graph.Graph):
-        graph_list = nx.to_scipy_sparse_matrix(graphs, format=fmt)
-    else:
-        raise ValueError('Input needs to be a list of networkx graphs or a networkx photonai_graph.')
+    def single_networkx_to_sparse(graph, fmt: str = 'csr'):
+        """INTERNAL HELPER FUNCTION
+        converts a single networkx graph to a scipy sparse matrix
 
-    return graph_list
+        Parameters
+        ----------
+        graph: nx.classes.graph.Graph
+            Input networkx graph
+        fmt: str,default="fmt"
+            scipy sparse matrix format
+
+        Returns
+        -------
+        sparse scipy matrix
+        """
+        if not isinstance(graph, nx.classes.graph.Graph):
+            raise ValueError(f'Got unknown object for conversion: {type(graph)}')
+        return nx.to_scipy_sparse_matrix(graph, format=fmt)
+
+    if not isinstance(graphs, list):
+        return single_networkx_to_sparse(graphs, fmt=fmt)
+
+    return [single_networkx_to_sparse(graph, fmt=fmt) for graph in graphs]
 
 
 def networkx_to_dgl(graphs, node_attrs=None, edge_attrs=None):
@@ -184,39 +221,49 @@ def networkx_to_dgl(graphs, node_attrs=None, edge_attrs=None):
             name of node attributes as list
         edge_attrs: list, default=None
             name of edge attributes as list
+
+        Returns
+        -------
+        dgl.DGLGraph
     """
+
+    def single_networkx_to_dgl(graph: nx.classes.graph.Graph, node_attrs, edge_attrs):
+        """INTERNAL HELPER FUNCTION
+        convert a single networkx graph to a dgl graph
+
+        Parameters
+        ----------
+        graph: nx.classes.graph.Graph
+            Input networkx graph
+        node_attrs: list,default=None
+            Note attributes
+        edge_attrs: list,default=None
+            Edge attributes
+
+        Returns
+        -------
+            dgl.DGLGraph
+        """
+        assert_imported(["dgl"])
+        if not isinstance(graph, nx.classes.graph.Graph):
+            raise ValueError(f"Got unexpected type {type(graph)} for conversion")
+        if not nx.is_directed(graph):
+            graph = graph.to_directed()
+        if node_attrs or edge_attrs is None:
+            g = dgl.from_networkx(graph)
+        else:
+            g = dgl.from_networkx(graph, node_attrs=node_attrs, edge_attrs=edge_attrs)
+        return g
+
     assert_imported(["dgl"])
 
-    if isinstance(graphs, list):
-        graph_list = []
-        for graph in graphs:
-            if not nx.is_directed(graph):
-                graph = graph.to_directed()
-            if node_attrs or edge_attrs is None:
-                g = dgl.from_networkx(graph)
-            else:
-                g = dgl.DGLGraph()
-                g.from_networkx(graph, node_attrs=node_attrs, edge_attrs=edge_attrs)
-            graph_list.append(g)
-    elif isinstance(graphs, np.ndarray):
-        graph_list = []
-        for graph in range(graphs.shape[0]):
-            if not nx.is_directed(graphs[graph]):
-                graphs[graph] = graphs[graph].to_directed()
-            if node_attrs or edge_attrs is None:
-                g = dgl.from_networkx(graphs[graph])
-            else:
-                g = dgl.from_networkx(graphs[graph], node_attrs=node_attrs, edge_attrs=edge_attrs)
-            graph_list.append(g)
-    elif isinstance(graphs, nx.classes.graph.Graph):
-        if not nx.is_directed(graphs):
-            graphs = graphs.to_directed()
-        g = dgl.from_networkx(graphs)
-        graph_list = g
-    else:
-        raise ValueError('networkx_to_dgl only implemented for list or single networkx graph')
+    if isinstance(graphs, np.ndarray):
+        graphs = [graphs[graph] for graph in range(graphs.shape[0])]
+    if not isinstance(graphs, list):
+        return single_networkx_to_dgl(graph=graphs, node_attrs=node_attrs, edge_attrs=edge_attrs)
 
-    return graph_list
+    return [single_networkx_to_dgl(graph, node_attrs=node_attrs, edge_attrs=edge_attrs)
+            for graph in graphs]
 
 
 def sparse_to_networkx(graphs):
@@ -227,20 +274,30 @@ def sparse_to_networkx(graphs):
         graphs:
             list of sparse matrices or single sparse matrix
     """
-    if isinstance(graphs, list):
-        graph_list = []
-        for graph in graphs:
-            nx_graph = nx.from_scipy_sparse_matrix(graph)
-            graph_list.append(nx_graph)
-    else:
+
+    def single_sparse_to_networkx(graph):
+        """INTERNAL HELPER FUNCTION
+        convert single scipy sparse matrix to networkx graph
+
+        Parameters
+        ----------
+        graph:
+            Input graph as sparse scipy matrix
+
+        Returns
+        -------
+        nx.classes.graph.Graph
+        """
         try:
-            graph_list = nx.from_scipy_sparse_matrix(graphs)
+            return nx.from_scipy_sparse_matrix(graph)
         except Exception as e:
             print("Please check your input.\n"
-                  "This function only takes a single sparese matrix or a list of sparse matrices.")
+                  "This function only takes a single sparse matrix or a list of sparse matrices.")
             raise e
+    if not isinstance(graphs, list):
+        return single_sparse_to_networkx(graphs)
 
-    return graph_list
+    return [single_sparse_to_networkx(graph) for graph in graphs]
 
 
 def dense_to_networkx(mtrx, adjacency_axis=0, feature_axis=1, feature_construction="features"):
@@ -258,39 +315,22 @@ def dense_to_networkx(mtrx, adjacency_axis=0, feature_axis=1, feature_constructi
             method of node feature construction, for more information see
             get_dense_feature function
     """
-    if isinstance(mtrx, list):
-        graph_list = []
 
-        for i in mtrx:
-            networkx_graph = nx.from_numpy_matrix(A=i[:, :, adjacency_axis])
-            features = get_dense_feature(i, adjacency_axis, feature_axis, aggregation=feature_construction)
-            nx.set_node_attributes(networkx_graph, features, name="feat")
-            graph_list.append(networkx_graph)
-
-        mtrx_conv = graph_list
-
-    # convert if Dense is an ndarray consisting of multiple arrays
-    elif isinstance(mtrx, np.ndarray):
-        # if dense is just a single array
-        if mtrx.shape[0] == 1:
-            mtrx_conv = nx.from_numpy_matrix(A=mtrx[0, :, :, adjacency_axis])
-            features = get_dense_feature(mtrx, adjacency_axis, feature_axis, feature_construction)
-            nx.set_node_attributes(mtrx_conv, features, name="feat")
-        # if dense consists of multiple arrays
-        else:
-            graph_list = []
-            # loop over each graph
-            for i in range(mtrx.shape[0]):
-                i_graph = mtrx[i, :, :, :]
-                networkx_graph = nx.from_numpy_matrix(A=i_graph[:, :, adjacency_axis])
-                features = get_dense_feature(i_graph, adjacency_axis, feature_axis, aggregation=feature_construction)
-                nx.set_node_attributes(networkx_graph, features, name="feat")
-                graph_list.append(networkx_graph)
+    if isinstance(mtrx, np.ndarray):
+        mtrx = [mtrx[idx] for idx in range(mtrx.shape[0])]
+    if not isinstance(mtrx, list):
+        raise ValueError(f"Expected np.ndarray or list as input, got {type(mtrx)}")
 
-            mtrx_conv = graph_list
+    graph_list = []
+    for item in mtrx:
+        if not isinstance(item, np.ndarray):
+            raise ValueError(f'Expected np.ndarray as input, got {type(mtrx)}')
+        networkx_graph = nx.from_numpy_matrix(A=item[:, :, adjacency_axis])
+        features = get_dense_feature(item, adjacency_axis, feature_axis, aggregation=feature_construction)
+        nx.set_node_attributes(networkx_graph, features, name="feat")
+        graph_list.append(networkx_graph)
 
-    else:
-        raise ValueError('input needs to be list of arrays or ndarray')
+    mtrx_conv = graph_list
 
     return mtrx_conv
 
@@ -329,11 +369,10 @@ def get_dense_feature(matrix, adjacency_axis, feature_axis, aggregation="sum"):
 
     features = dict(enumerate(features, 0))
 
-    # features = {str(key): value for key, value in features.items()}
-
     return features
 
 
+# todo: why is feature_axis needed here?
 def get_dense_edge_features(matrix, adjacency_axis, feature_axis):
     """returns the features for an edge label dictionary
 
@@ -351,9 +390,6 @@ def get_dense_edge_features(matrix, adjacency_axis, feature_axis):
         conn_key = (str(index[0]), str(index[1]))
         key_val = {conn_key: value}
         edge_feat.update(key_val)
-
-    # edge_feat = dict(np.ndenumerate(matrix[:, :, adjacency_axis]))
-
     return edge_feat
 
 
@@ -371,58 +407,36 @@ def dense_to_sparse(graphs, m_type="coo_matrix", adjacency_axis=None, feature_ax
         feature_axis: int, default=None
             position of the feature matrix
 
+
+        Returns
+        -------
+        list,list
+
     """
     if m_type not in sparse_types:
-        raise KeyError('Youor desired output format is not supported.\nPlease check your output format.')
+        raise KeyError('Your desired output format is not supported.\nPlease check your output format.')
 
-    if isinstance(graphs, list):
-        graph_list = []
-        feature_list = []
-        if np.ndim(graphs[0]) == 2:
-            for graph in graphs:
-                if np.ndim(graph) != 2:
-                    raise ValueError('All graphs must have same shape.')
-                graph_list.append(sparse_types[m_type](graph))
-        elif np.ndim(graphs[0]) == 3:
-            for graph in graphs:
-                if np.ndim(graph) != 3:
-                    raise ValueError('All graphs must have same shape.')
-                sparse_graph = sparse_types[m_type](graph[:, :, adjacency_axis])
-                sparse_features = sparse_types[m_type](graph[:, :, feature_axis])
-                feature_list.append(sparse_features)
-                graph_list.append(sparse_graph)
-        else:
-            raise ValueError('Unsupported graph shape.')
+    if isinstance(graphs, np.ndarray):
+        graphs = [graphs[idx] for idx in range(graphs.shape[0])]
 
-    elif isinstance(graphs, np.matrix) or isinstance(graphs, np.ndarray):
-        graph_list = []
-        feature_list = []
-        if adjacency_axis is not None and feature_axis is not None:
-            # if 4 dimensions you have subjects x values x values x adjacency/features
-            if np.ndim(graphs) == 4:
-                for i in range(graphs.shape[0]):
-                    adjacency = sparse_types[m_type](graphs[i, :, :, adjacency_axis])
-                    graph_list.append(adjacency)
-                    feature = sparse_types[m_type](graphs[i, :, :, feature_axis])
-                    feature_list.append(feature)
-            elif np.ndim(graphs) == 3:
-                graph_list = sparse_types[m_type](graphs[:, :, adjacency_axis])
-                feature_list = sparse_types[m_type](graphs[:, :, feature_axis])
-            else:
-                raise ValueError("Matrix needs to have 4 or 3 dimensions when axis arguments are given.")
+    if not isinstance(graphs, list):
+        raise ValueError("Input needs to be a numpy array or a list of arrays.")
 
+    graph_list = []
+    feature_list = []
+    initial_dim = np.ndim(graphs[0])
+    if initial_dim not in [2, 3]:
+        raise ValueError(f"Got unexpected graph shape: {initial_dim}")
+    for graph in graphs:
+        if np.ndim(graph) != initial_dim:
+            raise ValueError("All graphs must have same shape.")
+        if initial_dim == 2:
+            graph_list.append(sparse_types[m_type](graph))
         else:
-            if np.ndim(graphs) == 3:
-                for i in range(graphs.shape[0]):
-                    adjacency = sparse_types[m_type](graphs[i, :, :])
-                    graph_list.append(adjacency)
-            elif np.ndim(graphs) == 2:
-                graph_list = sparse_types[m_type](graphs)
-            else:
-                raise ValueError("Matrix needs to have 3 or 2 dimension when no axis arguments are given.")
-
-    else:
-        raise ValueError("Input needs to be a numpy array or a list of arrays.")
+            sparse_graph = sparse_types[m_type](graph[:, :, adjacency_axis])
+            sparse_features = sparse_types[m_type](graph[:, :, feature_axis])
+            feature_list.append(sparse_features)
+            graph_list.append(sparse_graph)
 
     return graph_list, feature_list
 
@@ -464,42 +478,31 @@ def convert_matrix(current_graph_mtrx, current_features):
         """
         Helper function
         """
-        current_graph_mtrx = np.reshape(current_graph_mtrx, (current_graph_mtrx.shape[0], current_graph_mtrx.shape[1], -1))
+        current_graph_mtrx = np.reshape(current_graph_mtrx,
+                                        (current_graph_mtrx.shape[0], current_graph_mtrx.shape[1], -1))
         current_features = current_features.toarray()
         current_features = np.reshape(current_features, (current_features.shape[0], current_features.shape[1], -1))
         return np.concatenate((current_graph_mtrx, current_features), axis=2)
 
-    if features is not None:
-        if isinstance(graphs, list):
-            matrices = []
-            for graph, feature in zip(graphs, features):
-                graph_mtrx = graph.toarray()
-                matrices.append(convert_matrix(graph_mtrx, feature))
-        else:
-            try:
-                graph_mtrx = graphs.toarray()
-                matrices = convert_matrix(graph_mtrx, features)
-            except Exception as e:
-                print('Could not convert matrices.'
-                      'Your matrices need to be a list or a single sparse matrix.')
-                raise e
-    # in this case there is only an adjacency matrix
-    else:
-        if isinstance(graphs, list):
-            matrices = []
-            for graph in graphs:
-                graph_mtrx = graph.toarray()
+    if isinstance(graphs, list):
+        matrices = []
+        for idx, graph in enumerate(graphs):
+            graph_mtrx = graph.toarray()
+            if features is not None:
+                matrices.append(convert_matrix(graph_mtrx, features[idx]))
+            else:
                 matrices.append(graph_mtrx)
-        else:
-            try:
-                matrices = graphs.toarray()
-            except Exception as e:
-                print('Could not convert matrices.'
-                      'Your matrices need to a list or a single sparse matrix.')
-                raise e
+    else:
+        try:
+            graph_mtrx = graphs.toarray()
+            if features is not None:
+                matrices = convert_matrix(graph_mtrx, features)
+        except Exception as e:
+            print('Could not convert matrices.'
+                  'Your matrices need to be a list or a single sparse matrix.')
+            raise e
 
     matrices = np.asarray(matrices)
-
     return matrices
 
 
@@ -517,6 +520,9 @@ def sparse_to_dgl(graphs, adjacency_axis=0, feature_axis=1):
     """
     assert_imported(["dgl"])
 
+    if isinstance(graphs, np.ndarray):
+        graphs = [graphs[idx] for idx in range(graphs.shape[0])]
+
     if isinstance(graphs, tuple):
         graph_list = []
         for adj, feat in zip(graphs[adjacency_axis], graphs[feature_axis]):
@@ -527,14 +533,6 @@ def sparse_to_dgl(graphs, adjacency_axis=0, feature_axis=1):
         for adj in graphs:
             g = dgl.from_scipy(sp_mat=adj)
             graph_list.append(g)
-    elif isinstance(graphs, np.ndarray) or isinstance(graphs, np.matrix):
-        if np.ndim(graphs) != 1:
-            raise Exception('Input needs to be 1d array if it is a numpy array')
-        graph_list = []
-        for adj in range(graphs.shape[0]):
-            g = dgl.from_scipy(sp_mat=graphs[adj])
-            graph_list.append(g)
-
     else:
         raise Exception('Expected tuple, list or 1d-array as input.')
 
diff --git a/photonai_graph/GraphMeasureTransform.py b/photonai_graph/GraphMeasureTransform.py
index 4cee582..f021772 100644
--- a/photonai_graph/GraphMeasureTransform.py
+++ b/photonai_graph/GraphMeasureTransform.py
@@ -28,6 +28,7 @@
 from dask.diagnostics import ProgressBar
 from sklearn.base import BaseEstimator, TransformerMixin
 from photonai_graph.GraphConversions import dense_to_networkx
+import networkx as nx
 import pandas as pd
 import numpy as np
 import json
@@ -76,15 +77,14 @@ def __init__(self,
             self.logs = os.getcwd()
 
     def fit(self, X, y):
-        pass
-
-    def transform(self, X):
+        return self
 
+    def _inner_transform(self, X):
         X_transformed = []
 
         if isinstance(X, np.ndarray) or isinstance(X, np.matrix):
             graphs = dense_to_networkx(X, adjacency_axis=0)
-        elif isinstance(X, list):
+        elif isinstance(X, list) and min([isinstance(g, nx.classes.graph.Graph) for g in X]):
             graphs = X
         else:
             raise TypeError("Input needs to be list of networkx graphs or numpy array.")
@@ -95,183 +95,119 @@ def transform(self, X):
         with open(measure_json, 'r') as measure_json_file:
             measure_j = json.load(measure_json_file)
 
-        # compute graph metrics for first graph
-        # ToDo: will only work in case first graph in list is not empty
         # ToDo: write unit test for parallelization
-        measures_first_graph = self._compute_graph_metrics(graphs[0], self.graph_functions, measure_j)
-        n_measures_to_expect = len(measures_first_graph)
 
         if self.n_processes > 1:
             task_list = []
             for graph in graphs:
-                tmp = dask.delayed(self._compute_graph_metrics)(graph, self.graph_functions, measure_j, n_measures=None)
+                tmp = dask.delayed(self._compute_graph_metrics)(graph, self.graph_functions, measure_j)
                 task_list.append(tmp)
             with ProgressBar():
                 X_transformed = list(dask.compute(*task_list, num_workers=self.n_processes, scheduler='threads'))
         else:
-
             for graph in graphs:
-                measure_list_graph = self._compute_graph_metrics(graph, self.graph_functions, measure_j,
-                                                                 n_measures_to_expect)
+                measure_list_graph = self._compute_graph_metrics(graph, self.graph_functions, measure_j)
                 X_transformed.append(measure_list_graph)
 
+        for c_measure in range(len(self.graph_functions)):
+            expected_values = max([len(graph[c_measure]) for graph in X_transformed])
+            for graph in X_transformed:
+                if len(graph[c_measure]) < expected_values:
+                    graph[c_measure] = [np.NAN] * expected_values
+
+        return X_transformed
+
+    def transform(self, X):
+        X_transformed = self._inner_transform(X)
+
+        for graph_idx in range(len(X_transformed)):
+            g_m = list()
+            for measure in X_transformed[graph_idx]:
+                g_m.extend(measure)
+            X_transformed[graph_idx] = g_m
+
         X_transformed = np.asarray(X_transformed)
 
         return X_transformed
 
-    @staticmethod
-    def _compute_graph_metrics(graph, graph_functions, measure_j, n_measures=None):
+    def _compute_graph_metrics(self, graph, graph_functions, measure_j):
         if networkx.classes.function.is_empty(graph):
             print("Graph is empty")
-            if n_measures is None:
-                return None
-            else:
-                return [np.nan] * n_measures
         measure_list_graph = []
         for key, value in graph_functions.items():
             measure_list = list()
 
-            if key in measure_j:
-
-                measure = measure_j[key]
-                # remove self loops if not allowed
-                if not measure['self_loops_allowed']:
-                    graph.remove_edges_from(networkx.selfloop_edges(graph))
-                # make photonai_graph directed or undirected depending on what is needed
-                if measure['Undirected']:
-                    graph.to_undirected()
-                elif not measure['Undirected']:
-                    graph.to_directed()
-
-                # call function
-                results = getattr(networkx, measure["path"].split(".")[-1])(graph, **value)
-
-                # handle results
-                if measure['Output'] == "dict":
-                    for rskey, rsval in results.items():
-                        measure_list.append(rsval)
-                elif measure['Output'] == "number":
-                    measure_list.append(results)
-                elif measure['Output'] == "tuple":
-                    measure_list.append(results[0])
-                elif measure['Output'] == "dict_dict":
-                    for rskey, rsval in sorted(results.items()):
-                        for rs2key, rs2val in sorted(rsval.items()):
-                            measure_list.append(rs2val)
-                elif measure['Output'] == "list":
-                    measure_list.extend(results)
-                elif measure['Output'] == "float_or_dict":
-                    if isinstance(results, float):
-                        measure_list.append(results)
-                    if isinstance(results, dict):
-                        for rskey, rsval in sorted(results.items()):
-                            measure_list.append(rsval)
-                elif measure['Output'] == "dual_tuple":
-                    measure_list.append(results[0])
-                    measure_list.append(results[1])
-                elif measure['Output'] == "tuple_dict":
-                    for rskey, rsval in sorted(results[0].items()):
-                        measure_list.append(rsval)
-                    for rskey, rsval in sorted(results[1].items()):
-                        measure_list.append(rsval)
-                if "compute_average" in measure.keys() and measure['compute_average']:
-                    measure_list_graph.append(np.mean(measure_list))
-                else:
-                    measure_list_graph.extend(measure_list)
+            if key not in measure_j:
+                raise ValueError(f"Measure functino {key} not found")
+
+            measure = measure_j[key]
+            # remove self loops if not allowed
+            if not measure['self_loops_allowed']:
+                graph.remove_edges_from(networkx.selfloop_edges(graph))
+            # make photonai_graph directed or undirected depending on what is needed
+            if measure['Undirected']:
+                graph.to_undirected()
+            elif not measure['Undirected']:
+                graph.to_directed()
+
+            # call function
+            results = getattr(networkx, measure["path"].split(".")[-1])(graph, **value)
+            measure_list = self.handle_outputs(results, measure_list)
+
+            if "compute_average" in measure.keys() and measure['compute_average']:
+                measure_list_graph.append([np.mean(measure_list)])
+            else:
+                measure_list_graph.append(measure_list)
         return measure_list_graph
 
+    @staticmethod
+    def handle_outputs(results, measure_list):
+        # handle results
+        if isinstance(results, dict):
+            for rskey, rsval in results.items():
+                GraphMeasureTransform.handle_outputs(rsval, measure_list)
+            return measure_list
+
+        if isinstance(results, list):
+            measure_list.extend(results)
+            return measure_list
+
+        # currently only networkx functions return tuples
+        # The second return value can be discarded in these functions
+        if isinstance(results, tuple):
+            for result in results:
+                GraphMeasureTransform.handle_outputs(result, measure_list)
+            return measure_list
+
+        if isinstance(results, nx.Graph):
+            return measure_list
+
+        measure_list.append(results)
+        return measure_list
+
     def get_measure_info(self):
         pass
 
-    def extract_measures(self, x_graphs, path="", ids=None):
-
-        measure_list = []
-        # check that graphs have networkx format
-        if isinstance(x_graphs, np.ndarray) or isinstance(x_graphs, np.matrix):
-            graphs = dense_to_networkx(x_graphs, adjacency_axis=0)
-        elif isinstance(x_graphs, list):
-            graphs = x_graphs
+    def extract_measures(self, x_graphs_in, path="", ids=None):
+        x_graphs = x_graphs_in.copy()
+        if ids is None:
+            raise ValueError('No id provided')
+        if isinstance(x_graphs, np.ndarray):
+            # [..., 0] because we are discarding the feature axis
+            x_graphs = [nx.from_numpy_array(x_graphs[cid][..., 0]) for cid in ids]
         else:
-            raise TypeError("Input needs to be list of networkx graphs or numpy array.")
-
-        # load json file
-        base_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-        measure_json = os.path.join(base_folder, 'photonai_graph/GraphMeasures.json')
-        with open(measure_json, 'r') as measure_json_file:
-            measure_j = json.load(measure_json_file)
-
-        if ids is not None:
-
-            for graph, i in zip(graphs, ids):
-
-                for key, value in self.graph_functions.items():
-
-                    # do all the extraction steps
-                    if key in measure_j:
-
-                        measure = measure_j[key]
-                        # remove self loops if not allowed
-                        if not measure['self_loops_allowed']:
-                            graph.remove_edges_from(networkx.selfloop_edges(graph))
-                        # make photonai_graph directed or undirected depending on what is needed
-                        if measure['Undirected']:
-                            graph.to_undirected()
-                        elif not measure['Undirected']:
-                            graph.to_directed()
-                        # call function
-                        results = getattr(networkx, key)(graph, **value)
-
-                        # if the values are numbers:
-                        # make a list of Subject ID, value, measure_name, edge=None, node=None
-                        if measure['Output'] == "number":
-                            list_to_append = [i, results, key, "None", "None"]
-                            measure_list.append(list_to_append)
-                        # if the values are dicts:
-                        # for every value in the dict make a list with:
-                        # subject ID, value, measure name, edge=None if it isn't an edge_method
-                        if measure['Output'] == "dict":
-                            for rskey, rsval in results.items():
-                                if measure['node_or_edge'] == 'node':
-                                    list_to_append = [i, rsval, key, rskey, "None"]
-                                    measure_list.append(list_to_append)
-                                elif measure['node_or_edge'] == 'edge':
-                                    list_to_append = [i, rsval, key, "None", rskey]
-                                    measure_list.append(list_to_append)
-                        # if the values are tuples
-                        elif measure['Output'] == "tuple":
-                            list_to_append = [i, results[0], key, "None", "None"]
-                            measure_list.append(list_to_append)
-                        # if the output is a dict of dicts
-                        elif measure['Output'] == "dict_dict":
-                            raise NotImplementedError("Dictionary of dictionary outputs are not implemented.")
-                        # if output is list: list outputs are not implemented
-                        elif measure['Output'] == "list":
-                            raise NotImplementedError("List outputs are not implemented")
-                        # if the output is float or dict based output
-                        elif measure['Output'] == "float_or_dict":
-                            if isinstance(results, float):
-                                list_to_append = [i, results, key, "None", "None"]
-                                measure_list.append(list_to_append)
-                            elif isinstance(results, dict):
-                                for rskey, rsval in results.items():
-                                    if measure['node_or_edge'] == "node":
-                                        list_to_append = [i, rsval, key, rskey, "None"]
-                                        measure_list.append(list_to_append)
-                                    elif measure['node_or_edge'] == "edge":
-                                        list_to_append = [i, rsval, key, "None", rskey]
-                                        measure_list.append(list_to_append)
-                        # if output is dual_tuple
-                        elif measure['Output'] == "dual_tuple":
-                            raise NotImplementedError("Dual tuple outputs are not implemented.")
-                        # if output is tuple_dict
-                        elif measure['Output'] == "tuple_dict":
-                            raise NotImplementedError("Tuple-Dict outputs are not implemented.")
+            x_graphs = [x_graphs[cid] for cid in ids]
+        X_transformed = self._inner_transform(x_graphs)
 
-        else:
-            raise Exception('no ID provided')
+        measurements = []
+        for graph, gid in zip(X_transformed, ids):
+            for measurement_id, result in enumerate(graph):
+                for res in result:
+                    current_measurement = [gid, list(self.graph_functions.keys())[measurement_id], res]
+                    measurements.append(current_measurement)
 
-        df = pd.DataFrame(measure_list)
+        df = pd.DataFrame(measurements)
 
-        col_names = ["ID", "value", "measure", "nodes", "edges"]
+        col_names = ["graph_id", "measure", "value"]
 
         df.to_csv(path_or_buf=path, header=col_names, index=None)
diff --git a/photonai_graph/GraphMeasures.json b/photonai_graph/GraphMeasures.json
index 114d86b..c9c54f8 100644
--- a/photonai_graph/GraphMeasures.json
+++ b/photonai_graph/GraphMeasures.json
@@ -3,581 +3,490 @@
       "path" : "networkx.algorithms.approximation.connectivity.all_pairs_node_connectivity",
       "self_loops_allowed": false,
       "Undirected" : true,
-     "Output": "dict",
      "node_or_edge": "edge"
 },
   "local_node_connectivity" : {
     "path": "networkx.algorithms.approximation.connectivity.local_node_connectivity",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "edge"
   },
   "node_connectivity" : {
     "path": "networkx.algorithms.approximation.connectivity.node_connectivity",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "k_components" : {
     "path": "networkx.algorithms.approximation.kcomponents.k_components",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict"
+    "Undirected": true
   },
   "large_clique_size" : {
     "path": "networkx.algorithms.approximation.clique.large_clique_size",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "average_clustering" : {
     "path": "networkx.algorithms.approximation.clustering_coefficient.average_clustering",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "treewidth_min_degree" : {
     "path": "networkx.algorithms.approximation.treewidth.treewidth_min_degreed",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "tuple"
+    "Undirected": true
   },
   "treewidth_min_fill_in" : {
     "path": "networkx.algorithms.approximation.treewidth.treewidth_min_fill_in",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "tuple"
+    "Undirected": true
   },
   "degree_assortativity_coefficient" : {
     "path": "networkx.algorithms.assortativity.degree_assortativity_coefficient",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "attribute_assortativity_coefficient" : {
     "path": "networkx.algorithms.assortativity.attribute_assortativity_coefficient",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "numeric_assortativity_coefficient" : {
     "path": "networkx.algorithms.assortativity.numeric_assortativity_coefficient",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "degree_pearson_correlation_coefficient" : {
     "path": "networkx.algorithms.assortativity.degree_pearson_correlation_coefficient",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "average_neighbor_degree" : {
     "path": "networkx.algorithms.assortativity.average_neighbor_degree",
     "self_loops_allowed": false,
     "Undirected": false,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "average_degree_connectivity" : {
     "path": "networkx.algorithms.assortativity.average_degree_connectivity",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "dict"
+    "Undirected": false
   },
   "k_nearest_neighbors" : {
     "path": "networkx.algorithms.assortativity.k_nearest_neighbors",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "dict"
+    "Undirected": false
   },
   "degree_centrality" : {
     "path": "networkx.algorithms.centrality.degree_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "in_degree_centrality" : {
     "path": "networkx.algorithms.centrality.in_degree_centrality",
     "self_loops_allowed": false,
     "Undirected": false,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "out_degree_centrality" : {
     "path": "networkx.algorithms.centrality.out_degree_centrality",
     "self_loops_allowed": false,
     "Undirected": false,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "eigenvector_centrality" : {
     "path": "networkx.algorithms.centrality.eigenvector_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "katz_centrality" : {
     "path": "nnetworkx.algorithms.centrality.katz_centrality",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict"
+    "Undirected": true
   },
   "closeness_centrality" : {
     "path": "networkx.algorithms.centrality.closeness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "incremental_closeness_centrality" : {
     "path": "networkx.algorithms.centrality.incremental_closeness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "current_flow_closeness_centrality" : {
     "path": "networkx.algorithms.centrality.current_flow_closeness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "information_centrality" : {
     "path": "networkx.algorithms.centrality.information_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "betweenness_centrality" : {
     "path": "networkx.algorithms.centrality.betweenness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "edge_betweenness_centrality" : {
     "path": "networkx.algorithms.centrality.edge_betweenness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "betweenness_centrality_subset" : {
     "path": "networkx.algorithms.centrality.betweenness_centrality_subset",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict"
+    "Undirected": true
   },
   "edge_betweenness_centrality_subset" : {
     "path": "networkx.algorithms.centrality.edge_betweenness_centrality_subset",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "edge"
   },
   "current_flow_betweenness_centrality" : {
     "path": "networkx.algorithms.centrality.current_flow_betweenness_centrality",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict"
+    "Undirected": true
   },
   "edge_current_flow_betweenness_centrality" : {
     "path": "networkx.algorithms.centrality.edge_current_flow_betweenness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "edge"
   },
   "approximate_current_flow_betweenness_centrality" : {
     "path": "networkx.algorithms.centrality.approximate_current_flow_betweenness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "current_flow_betweenness_centrality_subset" : {
     "path": "networkx.algorithms.centrality.current_flow_betweenness_centrality_subset",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "edge_current_flow_betweenness_centrality_subset" : {
     "path": "networkx.algorithms.centrality.edge_current_flow_betweenness_centrality_subset",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "edge"
   },
   "communicability_betweenness_centrality" : {
     "path": "networkx.algorithms.centrality.communicability_betweenness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "group_betweenness_centrality" : {
     "path": "networkx.algorithms.centrality.group_betweenness_centrality",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "group_closeness_centrality" : {
     "path": "networkx.algorithms.centrality.group_closeness_centrality",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "group_degree_centrality" : {
     "path": "networkx.algorithms.centrality.group_degree_centrality",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "group_in_degree_centrality" : {
     "path": "networkx.algorithms.centrality.group_in_degree_centrality",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "group_out_degree_centrality" : {
     "path": "networkx.algorithms.centrality.group_out_degree_centrality",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "load_centrality" : {
     "path": "networkx.algorithms.centrality.load_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "edge_load_centrality" : {
     "path": "networkx.algorithms.centrality.edge_load_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "subgraph_centrality" : {
     "path": "networkx.algorithms.centrality.subgraph_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "subgraph_centrality_exp" : {
     "path": "networkx.algorithms.centrality.subgraph_centrality_exp",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "estrada_index" : {
     "path": "networkx.algorithms.centrality.estrada_index",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "harmonic_centrality" : {
     "path": "networkx.algorithms.centrality.harmonic_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "dispersion" : {
     "path": "networkx.algorithms.centrality.dispersion",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict_dict"
+    "Undirected": true
   },
   "local_reaching_centrality" : {
     "path": "networkx.algorithms.centrality.local_reaching_centrality",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "global_reaching_centrality" : {
     "path": "networkx.algorithms.centrality.global_reaching_centrality",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "percolation_centrality" : {
     "path": "networkx.algorithms.centrality.percolation_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "second_order_centrality" : {
     "path": "networkx.algorithms.centrality.second_order_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "voterank" : {
     "path": "networkx.algorithms.centrality.voterank",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "list"
+    "Undirected": true
   },
   "graph_clique_number" : {
     "path": "networkx.algorithms.clique.graph_clique_number",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "graph_number_of_cliques" : {
     "path": "networkx.algorithms.clique.graph_number_of_cliques",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "node_clique_number" : {
     "path": "networkx.algorithms.clique.node_clique_number",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "number_of_cliques" : {
     "path": "networkx.algorithms.clique.number_of_cliques",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "triangles" : {
     "path": "networkx.algorithms.cluster.triangles",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "transitivity" : {
     "path": "networkx.algorithms.cluster.transitivity",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "clustering" : {
     "path": "networkx.algorithms.cluster.clustering",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "float_or_dict",
     "node_or_edge": "node"
   },
   "average_clustering" : {
     "path": "networkx.algorithms.cluster.average_clustering",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "number",
     "node_or_edge": "node"
   },
   "square_clustering" : {
     "path": "networkx.algorithms.cluster.square_clustering",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "communicability" : {
     "path": "networkx.algorithms.communicability_alg.communicability",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict_dict"
+    "Undirected": true
   },
   "communicability_exp" : {
     "path": "networkx.algorithms.communicability_alg.communicability_exp",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict_dict"
+    "Undirected": true
   },
   "number_connected_components" : {
     "path": "networkx.algorithms.components.number_connected_components",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "number_strongly_connected_components" : {
     "path": "networkx.algorithms.components.number_strongly_connected_components",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "number_weakly_connected_components" : {
     "path": "networkx.algorithms.components.number_weakly_connected_components",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "number_attracting_components" : {
     "path": "networkx.algorithms.components.number_attracting_components",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "average_node_connectivity" : {
     "path": "networkx.algorithms.connectivity.connectivity.average_node_connectivity",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "all_pairs_node_connectivity" : {
     "path": "networkx.algorithms.connectivity.connectivity.all_pairs_node_connectivity",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "edge"
   },
   "edge_connectivity" : {
     "path": "networkx.algorithms.connectivity.connectivity.edge_connectivity",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "local_edge_connectivity" : {
     "path": "networkx.algorithms.connectivity.connectivity.local_edge_connectivity",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "local_node_connectivity" : {
     "path": "networkx.algorithms.connectivity.connectivity.local_node_connectivity",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "node_connectivity" : {
     "path": "networkx.algorithms.connectivity.connectivity.node_connectivity",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "integer"
+    "Undirected": true
   },
   "core_number" : {
     "path": "networkx.algorithms.core.core_number",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "onion_layers" : {
     "path": "networkx.algorithms.core.onion_layers",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "boundary_expansion" : {
     "path": "networkx.algorithms.cuts.boundary_expansion",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "conductance" : {
     "path": "networkx.algorithms.cuts.conductance",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "cut_size" : {
     "path": "networkx.algorithms.cuts.cut_size",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "edge_expansion" : {
     "path": "networkx.algorithms.cuts.edge_expansion",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "mixing_expansion" : {
     "path": "networkx.algorithms.cuts.mixing_expansion",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "normalized_cut_size" : {
     "path": "networkx.algorithms.cuts.normalized_cut_size",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "volume" : {
     "path": "networkx.algorithms.cuts.volume",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "diameter" : {
     "path": "networkx.algorithms.distance_measures.diameter",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "eccentricity" : {
     "path": "networkx.algorithms.distance_measures.eccentricity",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict"
+    "Undirected": true
   },
   "radius" : {
     "path": "networkx.algorithms.distance_measures.radius",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "resistance_distance" : {
     "path": "networkx.algorithms.distance_measures.resistance_distance",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "efficiency" : {
     "path": "efficiency",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "local_efficiency" : {
     "path": "networkx.algorithms.efficiency_measures.local_efficiency",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "global_efficiency" : {
     "path": "networkx.algorithms.efficiency_measures.global_efficiency",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "flow_hierarchy" : {
     "path": "networkx.algorithms.hierarchy.flow_hierarchy",
     "self_loops_allowed": false,
-    "Undirected": false,
-    "Output": "number"
+    "Undirected": false
   },
   "number_of_isolates" : {
     "path": "networkx.algorithms.isolate.number_of_isolates",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "pagerank" : {
     "path": "networkx.algorithms.link_analysis.pagerank_alg.pagerank",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "hits" : {
@@ -589,102 +498,86 @@
   "non_randomness" : {
     "path": "networkx.algorithms.non_randomness.non_randomness",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dual_tuple"
+    "Undirected": true
   },
   "reciprocity" : {
     "path": "networkx.algorithms.reciprocity.reciprocity",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "overall_reciprocity" : {
     "path": "networkx.algorithms.reciprocity.overall_reciprocity",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "rich_club_coefficient" : {
     "path": "networkx.algorithms.richclub.rich_club_coefficient",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "edge"
   },
   "average_shortest_path_length" : {
     "path": "networkx.algorithms.shortest_paths.generic.average_shortest_path_length",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "sigma" : {
     "path": "networkx.algorithms.smallworld.sigma",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "omega" : {
     "path": "networkx.algorithms.smallworld.omega",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "s_metric" : {
     "path": "networkx.algorithms.smetric.s_metric",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "constraint" : {
     "path": "networkx.algorithms.structuralholes.constraint",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "effective_size" : {
     "path": "networkx.algorithms.structuralholes.effective_size",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node"
   },
   "local_constraint" : {
     "path": "networkx.algorithms.structuralholes.local_constraint",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "triadic_census" : {
     "path": "networkx.algorithms.triads.triadic_census",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict"
+    "Undirected": true
   },
   "closeness_vitality" : {
     "path": "networkx.algorithms.vitality.closeness_vitality",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "float_or_dict"
+    "Undirected": true
   },
   "wiener_index" : {
     "path": "networkx.algorithms.wiener.wiener_index",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   },
   "transformer_name" : {
     "path": "path.to.method",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "dict"
+    "Undirected": true
   },
   "average_degree_centrality" : {
     "path": "networkx.algorithms.centrality.degree_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node",
     "compute_average": true
   },
@@ -692,14 +585,12 @@
     "path": "networkx.algorithms.centrality.betweenness_centrality",
     "self_loops_allowed": false,
     "Undirected": true,
-    "Output": "dict",
     "node_or_edge": "node",
     "compute_average": true
   },
   "number_of_edges": {
     "path": "networkx.classes.function.number_of_edges",
     "self_loops_allowed": false,
-    "Undirected": true,
-    "Output": "number"
+    "Undirected": true
   }
 }
\ No newline at end of file
diff --git a/photonai_graph/PopulationAveragingTransform.py b/photonai_graph/PopulationAveragingTransform.py
new file mode 100644
index 0000000..af9d805
--- /dev/null
+++ b/photonai_graph/PopulationAveragingTransform.py
@@ -0,0 +1,75 @@
+from sklearn.base import BaseEstimator, TransformerMixin
+from typing import Tuple, Union
+import numpy as np
+
+
+class PopulationAveragingTransform(BaseEstimator, TransformerMixin):
+    def __init__(self,
+                 adjacency_axis: int = 0,
+                 connectivity_axis: int = 1,
+                 feature_axis: Union[int, Tuple[int, ...]] = 2):
+        """Transformation for averaging over the population
+        The connectivity of an individual is discarded by this transformation.
+        Instead, the precalculated mean is used and the original connectivity will be passed as features.
+
+        !!! warning
+            The original adjacency matrix of unseen data is discarded by application of this transformer!
+
+        Parameters
+        ----------
+        adjacency_axis: int,default=0
+            Index of the adjacency part of the input arrays.
+            If a GraphConstructor is used with default parameters the adjacency is stored at the first dimension.
+        connectivity_axis: int,default=1
+            Index of the connectivity part of the input arrays for unseen data.
+            If a GraphConstructor is used with default parameters the connectivity is at the second dimension.
+        feature_axis: Union[int, List[int, ...]],default=2
+            Index (or indices) of the feature part of the unseen data.
+
+        Example
+        -------
+        ```python
+        my_pipe.add(PipelineElement('PopulationAveragingTransform'))
+        ```
+
+        Notes
+        -----
+        Using the feature axis as additional feature is not implemented yet.
+        """
+        self.connectivtiy_axis = connectivity_axis
+        self.adjacency_axis = adjacency_axis
+        self.feature_axis = feature_axis
+        self.learned_mean = None
+        if self.feature_axis != 2:
+            raise NotImplementedError("Feature passing is not implemented yet.")
+
+    def fit(self, X, y):
+        if isinstance(X, list):
+            x_int = np.asarray(X)
+        else:
+            x_int = X
+        mean = np.mean(x_int, axis=0)
+        if np.ndim(x_int) == 4:
+            self.learned_mean = mean[..., self.adjacency_axis]
+        elif np.ndim(x_int) == 3:
+            self.learned_mean = mean
+        else:
+            raise ValueError("The input matrices need to have 3 or 4 dimensions. Please check your input matrix.")
+        return self
+
+    def transform(self, X):
+        if self.learned_mean is None:
+            raise ValueError("This transformer has not been fitted yet.")
+        if np.ndim(X) < 3:
+            raise ValueError("Usage of population averaging without features or original connectivity is discouraged.\n"
+                             "Please read the documentation of this transformer")
+        if isinstance(X, list):
+            x_int = np.array(X)
+        else:
+            x_int = X
+        if not isinstance(X, np.ndarray):
+            raise ValueError(f"Expected list or np.ndarray as input type, got {type(X)}")
+        new_adjacency = self.learned_mean.copy()
+        new_adjacency = new_adjacency[np.newaxis, ..., np.newaxis]
+        new_adjacency = np.repeat(new_adjacency, x_int.shape[0], axis=0)
+        return np.concatenate((new_adjacency, x_int[..., self.feature_axis, np.newaxis]), axis=3)
diff --git a/photonai_graph/photonai_graph.json b/photonai_graph/photonai_graph.json
index be34dae..fcec879 100644
--- a/photonai_graph/photonai_graph.json
+++ b/photonai_graph/photonai_graph.json
@@ -1,4 +1,8 @@
 {
+   "PopulationAveragingTransform": [
+      "photonai_graph.PopulationAveragingTransform.PopulationAveragingTransform",
+      "Transformer"
+   ],
    "GraphMeasureTransform":[
       "photonai_graph.GraphMeasureTransform.GraphMeasureTransform",
       "Transformer"
diff --git a/photonai_graph/util.py b/photonai_graph/util.py
index 4eb5675..9b809cf 100644
--- a/photonai_graph/util.py
+++ b/photonai_graph/util.py
@@ -1,3 +1,5 @@
+import sys
+
 from photonai.base import PhotonRegistry
 import os
 try:
@@ -32,49 +34,11 @@ def register_photonai_graph():
     reg = PhotonRegistry()
     reg.add_module(current_path)
 
-    
-packages = ["dgl", "grakel", "pytorch", "gem"]
-
 
-def assert_imported(package: list = None):
-    if "dgl" in package:
-        try:
-            dgl.graph()
-        except NameError as e:
-            print("dgl has to be installed. Please install running version with pip install.")
-            raise e
-        except Exception:
-            pass
-    if "grakel" in package:
-        try:
-            grakel.Graph()
-        except NameError as e:
-            print("Grakel has to be installed. Please install running version with pip install.")
-            raise e
-        except Exception:
-            pass
-    if "pytorch" in package:
-        try:
-            torch.tensor()
-        except NameError as e:
-            print("pytorch has to be installed. Please install running version with pip install.")
-            raise e
-        except Exception:
-            pass
-    if "gem" in package:
-        try:
-            HOPE()
-        except NameError as e:
-            print("gem has to be installed. Please install running version with pip install.")
-            raise e
-        except Exception:
-            pass
-    if "euler" in package:
-        try:
-            euler()
-        except NameError as e:
-            print("pytorch has to be installed. Please install running version with pip install.")
-            raise e
-        except Exception:
-            pass
+def assert_imported(packages: list = None):
+    for package in packages:
+        if package in sys.modules:
+            return True
+        else:
+            raise ImportError(f"Could not load {package}. Please install it according to the documentation")
     


Test patch:
diff --git a/test/constructor_tests/test_graph_constructor_base.py b/test/constructor_tests/test_graph_constructor_base.py
index 8537c50..029eec9 100644
--- a/test/constructor_tests/test_graph_constructor_base.py
+++ b/test/constructor_tests/test_graph_constructor_base.py
@@ -6,67 +6,104 @@
 class ThresholdTests(unittest.TestCase):
 
     def setUp(self):
-        self.X4d = np.ones((20, 20, 20, 2))
+        self.X4d_adjacency = np.ones((20, 20, 20, 1))
+        self.X4d_features = np.random.rand(20, 20, 20, 1)
+        self.X4d = np.concatenate((self.X4d_adjacency, self.X4d_features), axis=3)
         self.X3d = np.ones((20, 20, 20))
-        self.Xrandom4d = np.random.rand(20, 20, 20, 2)
-        self.y = np.ones((20))
+        self.y = np.ones(20)
 
-    def test_threshold_mean_shape_3d(self):
-        # ensure that mean transform style with a 3d matrix returns the right shape
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="mean")
+    def test_threshold_3d(self):
+        # ensure that individual transform style with a 3d matrix returns the right shape
+        g_constr = GraphConstructorThreshold(threshold=0.5)
         g_constr.fit(self.X3d, self.y)
         trans = g_constr.transform(self.X3d)
         self.assertEqual(trans.shape, (20, 20, 20, 2))
+        # thresholding an array of ones with 0.5 should not change anything
+        self.assertTrue(np.array_equal(trans[..., 0], self.X3d))
+        # as the original connectivity is not discarded, it should be present in second dim
+        self.assertTrue(np.array_equal(trans[..., 1], self.X3d))
 
-    def test_threshold_mean_shape_4d(self):
-        # ensure that mean transform style with a 4d matrix returns the right shape
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="mean")
-        g_constr.fit(self.X4d, self.y)
-        trans = g_constr.transform(self.X4d)
-        self.assertEqual(trans.shape, (20, 20, 20, 3))
-
-    def test_threshold_individual_shape_3d(self):
-        # ensure that individual transform style with a 3d matrix returns the right shape
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="individual")
+    def test_threshold_3d_discard_connectivity(self):
+        g_constr = GraphConstructorThreshold(threshold=0.5,
+                                             discard_original_connectivity=True)
         g_constr.fit(self.X3d, self.y)
         trans = g_constr.transform(self.X3d)
-        self.assertEqual(trans.shape, (20, 20, 20, 2))
+        self.assertEqual(trans.shape, (20, 20, 20, 1))
+        self.assertTrue(np.array_equal(trans[..., 0], self.X3d))
 
-    def test_threshold_individual_shape_4d(self):
+    def test_threshold_4d(self):
         # ensure that individual transform style with a 4d matrix returns the right shape
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="individual")
+        g_constr = GraphConstructorThreshold(threshold=0.5)
         g_constr.fit(self.X4d, self.y)
         trans = g_constr.transform(self.X4d)
         self.assertEqual(trans.shape, (20, 20, 20, 3))
+        # first dimension should be thresholded but unchanged
+        self.assertTrue(np.array_equal(trans[..., 0, np.newaxis], self.X4d_adjacency))
+        # second dimension should contain original connectivity
+        self.assertTrue(np.array_equal(trans[..., 1, np.newaxis], self.X4d_adjacency))
+        # last dimension should contain the random features
+        self.assertTrue(np.array_equal(trans[..., 2, np.newaxis], self.X4d_features))
 
-    def test_threshold_mean_shape_3d_onehot(self):
-        # ensure that a mean transform with a 3d matrix returns the right shape
-        # when using one hot encoded features
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="mean", one_hot_nodes=1)
-        g_constr.fit(self.X3d, self.y)
-        trans = g_constr.transform(self.X3d)
-        self.assertEqual(trans.shape, (20, 20, 20, 2))
-
-    def test_threshold_mean_shape_4d_onehot(self):
-        # ensure that a mean transform with a 3d matrix returns the right shape
-        # when using one hot encoded features
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="mean", one_hot_nodes=1)
+    def test_threshold_4d_discard_connectivity(self):
+        # ensure that individual transform style with a 4d matrix returns the right shape
+        g_constr = GraphConstructorThreshold(threshold=0.5,
+                                             discard_original_connectivity=True)
         g_constr.fit(self.X4d, self.y)
         trans = g_constr.transform(self.X4d)
         self.assertEqual(trans.shape, (20, 20, 20, 2))
+        # first dimension should be thresholded but unchanged
+        self.assertTrue(np.array_equal(trans[..., 0, np.newaxis], self.X4d_adjacency))
+        # last dimension should contain the random features
+        self.assertTrue(np.array_equal(trans[..., 1, np.newaxis], self.X4d_features))
 
-    def test_threshold_individual_shape_3d_onehot(self):
+    def test_threshold_shape_3d_onehot(self):
         # ensure that an individual transform with a 3d matrix returns the right shape
         # when using one hot encoded features
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="individual", one_hot_nodes=1)
+        g_constr = GraphConstructorThreshold(threshold=0.5, one_hot_nodes=1)
         g_constr.fit(self.X3d, self.y)
         trans = g_constr.transform(self.X3d)
-        self.assertEqual(trans.shape, (20, 20, 20, 2))
+        self.assertEqual(trans.shape, (20, 20, 20, 3))
+        # the first dimension contains the original connectivity
+        self.assertTrue(np.array_equal(trans[..., 0], self.X3d))
+        # the second dimension contains the one hot encoding
+        # we know the one hot encoding, as we created the matrix accordingly
+        self.assertTrue(np.array_equal(trans[..., 1, np.newaxis],
+                                       np.repeat(np.eye(20)[np.newaxis, ...], 20, axis=0)[..., np.newaxis]))
+        # the last dimension contains the original connectivity again
+        self.assertTrue(np.array_equal(trans[..., 2], self.X3d))
 
-    def test_threshold_individual_shape_4d_onehot(self):
+    def test_threshold_shape_4d_onehot(self):
         # ensure that an individual transform with a 3d matrix returns the right shape
         # when using one hot encoded features
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="individual", one_hot_nodes=1)
+        g_constr = GraphConstructorThreshold(threshold=0.5, one_hot_nodes=1)
         g_constr.fit(self.X4d, self.y)
         trans = g_constr.transform(self.X4d)
-        self.assertEqual(trans.shape, (20, 20, 20, 2))
+        self.assertEqual(trans.shape, (20, 20, 20, 4))
+        # the first dimension still contains the (thresholded but unchanged) values
+        self.assertTrue(np.array_equal(trans[..., 0, np.newaxis], self.X4d_adjacency))
+        # the second dimesion contains the one hot encoding
+        # We know the one hot encoding, as we created the matrix accordingly
+        self.assertTrue(np.array_equal(trans[..., 1, np.newaxis],
+                                       np.repeat(np.eye(20)[np.newaxis, ...], 20, axis=0)[..., np.newaxis]))
+        # the third dimension contains again the original values
+        self.assertTrue(np.array_equal(trans[..., 2, np.newaxis], self.X4d_adjacency))
+        # the last dimension contains the features
+        self.assertTrue(np.array_equal(trans[..., 3, np.newaxis], self.X4d_features))
+
+    def test_threshold_individual_shape_4d_onehot_discard_connectivity(self):
+        # ensure that an individual transform with a 3d matrix returns the right shape
+        # when using one hot encoded features
+        g_constr = GraphConstructorThreshold(threshold=0.5,
+                                             one_hot_nodes=1,
+                                             discard_original_connectivity=True)
+        g_constr.fit(self.X4d, self.y)
+        trans = g_constr.transform(self.X4d)
+        self.assertEqual(trans.shape, (20, 20, 20, 3))
+        # the first dimension still contains the (thresholded but unchanged) values
+        self.assertTrue(np.array_equal(trans[..., 0, np.newaxis], self.X4d_adjacency))
+        # the second dimesion contains the one hot encoding
+        # We know the one hot encoding, as we created the matrix accordingly
+        self.assertTrue(np.array_equal(trans[..., 1, np.newaxis],
+                                       np.repeat(np.eye(20)[np.newaxis, ...], 20, axis=0)[..., np.newaxis]))
+        # the last dimension contains the features
+        self.assertTrue(np.array_equal(trans[..., 2, np.newaxis], self.X4d_features))
diff --git a/test/constructor_tests/test_graph_constructor_knn.py b/test/constructor_tests/test_graph_constructor_knn.py
index 9385bb4..9756b08 100644
--- a/test/constructor_tests/test_graph_constructor_knn.py
+++ b/test/constructor_tests/test_graph_constructor_knn.py
@@ -18,26 +18,20 @@ def setUp(self):
         self.Xtest4d = np.repeat(test_array4d, 10, axis=0)
         self.y = np.ones((20))
 
-    def test_knn_mean(self):
-        g_constr = GraphConstructorKNN(transform_style="mean")
-        g_constr.fit(self.Xrandom4d, self.y)
-        trans = g_constr.transform(self.Xrandom4d)
-        self.assertEqual(np.shape(trans), (20, 20, 20, 3))
-
     def test_knn_individual(self):
-        g_constr = GraphConstructorKNN(transform_style="individual")
+        g_constr = GraphConstructorKNN()
         g_constr.fit(self.Xrandom4d, self.y)
         trans = g_constr.transform(self.Xrandom4d)
         self.assertEqual(np.shape(trans), (20, 20, 20, 3))
 
     def test_knn_assert_failure(self):
         with self.assertRaises(AssertionError):
-            g_constr = GraphConstructorKNN(transform_style="individual")
+            g_constr = GraphConstructorKNN()
             g_constr.fit(self.X4d, self.y)
             g_constr.transform(self.X4d)
 
     def test_knn_mechanism(self):
-        g_constr = GraphConstructorKNN(k_distance=2, transform_style="individual")
+        g_constr = GraphConstructorKNN(k_distance=2)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         bool_mask = [[True, False, False, True, True, True],
diff --git a/test/constructor_tests/test_graph_constructor_percentage.py b/test/constructor_tests/test_graph_constructor_percentage.py
index 53ca7ac..e30d868 100644
--- a/test/constructor_tests/test_graph_constructor_percentage.py
+++ b/test/constructor_tests/test_graph_constructor_percentage.py
@@ -12,14 +12,8 @@ def setUp(self):
         self.Xtest4d = np.repeat(test_array, 10, axis=0)
         self.y = np.ones((20))
 
-    def test_percentage_mean(self):
-        g_constr = GraphConstructorPercentage(transform_style="mean")
-        g_constr.fit(self.X4d, self.y)
-        trans = g_constr.transform(self.X4d)
-        self.assertEqual(np.shape(trans), (20, 20, 20, 3))
-
     def test_percentage_individual(self):
-        g_constr = GraphConstructorPercentage(transform_style="individual")
+        g_constr = GraphConstructorPercentage()
         g_constr.fit(self.X4d, self.y)
         trans = g_constr.transform(self.X4d)
         self.assertEqual(np.shape(trans), (20, 20, 20, 3))
@@ -47,7 +41,7 @@ def test_threshold_window_retain_weights_error(self):
 
     def test_percentage_contains(self):
         # ensure that the threshold actually picks the right rows of the matrix
-        g_constr = GraphConstructorPercentage(percentage=90, transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorPercentage(percentage=90, retain_weights=1)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         expected_elements = [91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
@@ -56,7 +50,7 @@ def test_percentage_contains(self):
 
     def test_percentage_not_contains(self):
         # ensure that the threshold actually picks the right rows of the matrix
-        g_constr = GraphConstructorPercentage(percentage=90, transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorPercentage(percentage=90, retain_weights=1)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         expected_elements = [80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
diff --git a/test/constructor_tests/test_graph_constructor_percentage_window.py b/test/constructor_tests/test_graph_constructor_percentage_window.py
index 49287c1..a7b7da1 100644
--- a/test/constructor_tests/test_graph_constructor_percentage_window.py
+++ b/test/constructor_tests/test_graph_constructor_percentage_window.py
@@ -12,14 +12,8 @@ def setUp(self):
         self.Xtest4d = np.repeat(test_array, 10, axis=0)
         self.y = np.ones((20))
 
-    def test_percentage_window_mean(self):
-        g_constr = GraphConstructorPercentageWindow(transform_style="mean")
-        g_constr.fit(self.X4d, self.y)
-        trans = g_constr.transform(self.X4d)
-        self.assertEqual(np.shape(trans), (20, 20, 20, 3))
-
     def test_percentage_window_individual(self):
-        g_constr = GraphConstructorPercentageWindow(transform_style="individual")
+        g_constr = GraphConstructorPercentageWindow()
         g_constr.fit(self.X4d, self.y)
         trans = g_constr.transform(self.X4d)
         self.assertEqual(np.shape(trans), (20, 20, 20, 3))
@@ -47,8 +41,9 @@ def test_threshold_window_retain_weights_error(self):
 
     def test_percentage_contains(self):
         # ensure that the threshold actually picks the right rows of the matrix
-        g_constr = GraphConstructorPercentageWindow(percentage_upper=80, percentage_lower=60,
-                                                    transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorPercentageWindow(percentage_upper=80,
+                                                    percentage_lower=60,
+                                                    retain_weights=1)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         expected_elements = [70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80]
@@ -57,8 +52,9 @@ def test_percentage_contains(self):
 
     def test_percentage_not_contains(self):
         # ensure that the threshold actually picks the right rows of the matrix
-        g_constr = GraphConstructorPercentageWindow(percentage_upper=80, percentage_lower=60,
-                                                    transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorPercentageWindow(percentage_upper=80,
+                                                    percentage_lower=60,
+                                                    retain_weights=1)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         expected_elements = [50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
diff --git a/test/constructor_tests/test_graph_constructor_random_walk.py b/test/constructor_tests/test_graph_constructor_random_walk.py
index 27f54b3..97dd077 100644
--- a/test/constructor_tests/test_graph_constructor_random_walk.py
+++ b/test/constructor_tests/test_graph_constructor_random_walk.py
@@ -17,13 +17,13 @@ def test_rw_mean(self):
         self.assertEqual(np.shape(trans), (20, 20, 20, 3))
 
     def test_rw_individual(self):
-        g_constr = GraphConstructorRandomWalks(transform_style="individual")
+        g_constr = GraphConstructorRandomWalks()
         g_constr.fit(self.Xrandom4d, self.y)
         trans = g_constr.transform(self.Xrandom4d)
         self.assertEqual(np.shape(trans), (20, 20, 20, 3))
 
     def test_rw_assert_failure(self):
         with self.assertRaises(AssertionError):
-            g_constr = GraphConstructorRandomWalks(transform_style="individual")
+            g_constr = GraphConstructorRandomWalks()
             g_constr.fit(self.X4d, self.y)
             g_constr.transform(self.X4d)
diff --git a/test/constructor_tests/test_graph_constructor_threshold.py b/test/constructor_tests/test_graph_constructor_threshold.py
index 9bac459..6692792 100644
--- a/test/constructor_tests/test_graph_constructor_threshold.py
+++ b/test/constructor_tests/test_graph_constructor_threshold.py
@@ -15,7 +15,7 @@ def setUp(self):
 
     def test_threshold_individual_3d(self):
         # ensure that a individual transform style on a 3d matrix does not return values other than one
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="individual")
+        g_constr = GraphConstructorThreshold(threshold=0.5)
         g_constr.fit(self.X3d, self.y)
         trans = g_constr.transform(self.X3d)
         self.assertEqual(np.min(trans), 1)
@@ -23,7 +23,7 @@ def test_threshold_individual_3d(self):
 
     def test_threshold_individual_4d(self):
         # ensure that a individual transform style on a 4d matrix does not return values other than one
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="individual")
+        g_constr = GraphConstructorThreshold(threshold=0.5)
         g_constr.fit(self.X4d, self.y)
         trans = g_constr.transform(self.X4d)
         self.assertEqual(np.min(trans), 1)
@@ -31,7 +31,7 @@ def test_threshold_individual_4d(self):
 
     def test_threshold_mean_3d(self):
         # ensure that a individual transform style on a 3d matrix does not return values other than zero
-        g_constr = GraphConstructorThreshold(threshold=1.5, transform_style="individual")
+        g_constr = GraphConstructorThreshold(threshold=1.5)
         g_constr.fit(self.X3d, self.y)
         trans = g_constr.transform(self.X3d)
         self.assertEqual(np.min(trans[:, :, :, 0]), 0)
@@ -39,7 +39,7 @@ def test_threshold_mean_3d(self):
 
     def test_threshold_mean_4d(self):
         # ensure that a individual transform style on a 4d matrix does not return values other than zero
-        g_constr = GraphConstructorThreshold(threshold=1.5, transform_style="individual")
+        g_constr = GraphConstructorThreshold(threshold=1.5)
         g_constr.fit(self.X4d, self.y)
         trans = g_constr.transform(self.X4d)
         self.assertEqual(np.min(trans[:, :, :, 0]), 0)
@@ -47,7 +47,7 @@ def test_threshold_mean_4d(self):
 
     def test_threshold_retain_weights(self):
         # ensure that retain weights leads to a diverse set of weights (more than 0 and 1)
-        g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorThreshold(threshold=0.5, retain_weights=1)
         g_constr.fit(self.Xrandom4d, self.y)
         trans = g_constr.transform(self.Xrandom4d)
         self.assertEqual(np.min(trans[:, :, :, 0]), 0)
@@ -56,13 +56,13 @@ def test_threshold_retain_weights(self):
     def test_threshold_retain_weights_error(self):
         # ensure a Value error is being raised when retain weights is neither 0 or 1
         with self.assertRaises(ValueError):
-            g_constr = GraphConstructorThreshold(threshold=0.5, transform_style="individual", retain_weights=0.5)
+            g_constr = GraphConstructorThreshold(threshold=0.5, retain_weights=0.5)
             g_constr.fit(self.Xrandom4d, self.y)
             g_constr.transform(self.Xrandom4d)
 
     def test_threshold_contains(self):
         # ensure that the threshold actually picks the right rows of the matrix
-        g_constr = GraphConstructorThreshold(threshold=90, transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorThreshold(threshold=90, retain_weights=1)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         expected_elements = [90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
@@ -71,7 +71,7 @@ def test_threshold_contains(self):
 
     def test_threshold_not_contains(self):
         # ensure that the threshold actually picks the right rows of the matrix
-        g_constr = GraphConstructorThreshold(threshold=90, transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorThreshold(threshold=90, retain_weights=1)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         expected_elements = [80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
diff --git a/test/constructor_tests/test_graph_constructor_threshold_window.py b/test/constructor_tests/test_graph_constructor_threshold_window.py
index 52ba205..660f8ab 100644
--- a/test/constructor_tests/test_graph_constructor_threshold_window.py
+++ b/test/constructor_tests/test_graph_constructor_threshold_window.py
@@ -20,7 +20,7 @@ def test_treshold_window_mean(self):
         self.assertEqual(np.shape(trans), (20, 20, 20, 3))
 
     def test_threshold_window_individual(self):
-        g_constr = GraphConstructorThresholdWindow(transform_style="individual")
+        g_constr = GraphConstructorThresholdWindow()
         g_constr.fit(self.X4d, self.y)
         trans = g_constr.transform(self.X4d)
         self.assertEqual(np.shape(trans), (20, 20, 20, 3))
@@ -48,8 +48,9 @@ def test_threshold_window_retain_weights_error(self):
 
     def test_threshold_contains(self):
         # ensure that the threshold actually picks the right rows of the matrix
-        g_constr = GraphConstructorThresholdWindow(threshold_upper=80, threshold_lower=70,
-                                                   transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorThresholdWindow(threshold_upper=80,
+                                                   threshold_lower=70,
+                                                   retain_weights=1)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         expected_elements = [70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80]
@@ -58,8 +59,9 @@ def test_threshold_contains(self):
 
     def test_threshold_not_contains(self):
         # ensure that the threshold actually picks the right rows of the matrix
-        g_constr = GraphConstructorThresholdWindow(threshold_upper=80, threshold_lower=70,
-                                                   transform_style="individual", retain_weights=1)
+        g_constr = GraphConstructorThresholdWindow(threshold_upper=80,
+                                                   threshold_lower=70,
+                                                   retain_weights=1)
         g_constr.fit(self.Xtest4d, self.y)
         trans = g_constr.transform(self.Xtest4d)
         expected_elements = [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
diff --git a/test/conversion_tests/test_dense_to_networkx.py b/test/conversion_tests/test_dense_to_networkx.py
index 4da0cc9..6da5dd8 100644
--- a/test/conversion_tests/test_dense_to_networkx.py
+++ b/test/conversion_tests/test_dense_to_networkx.py
@@ -8,26 +8,27 @@ class DenseToNetworkxTest(unittest.TestCase):
 
     def setUp(self):
         self.random4d = np.random.rand(20, 20, 20, 2)
-        self.single4d = np.random.rand(1, 20, 20, 2)
         self.array_list = [np.random.rand(20, 20, 2)] * 10
         self.edge_dict = self.edge_dict = {(1, 0): 1, (2, 0): 1}
 
     def test_output_list(self):
         mtrx = dense_to_networkx(self.random4d)
         self.assertEqual(type(mtrx), list)
-
-    def test_output_list_element(self):
-        mtrx = dense_to_networkx(self.random4d)
-        self.assertEqual(type(mtrx[0]), nx.Graph)
-
-    def test_input_single_dense(self):
-        mtrx = dense_to_networkx(self.single4d)
-        self.assertEqual(type(mtrx), nx.Graph)
+        for itm in mtrx:
+            self.assertEqual(type(itm), nx.Graph)
 
     def test_input_list_dense(self):
         mtrx = dense_to_networkx(self.array_list)
         self.assertEqual(type(mtrx[0]), nx.Graph)
 
+    def test_input_nonsense_list(self):
+        with self.assertRaises(ValueError):
+            dense_to_networkx([self.edge_dict] * 10)
+
+    def test_input_nonsense_array(self):
+        with self.assertRaises(ValueError):
+            dense_to_networkx(np.array([self.edge_dict] * 10))
+
     def test_input_nonsense(self):
         with self.assertRaises(ValueError):
             dense_to_networkx(self.edge_dict)
diff --git a/test/conversion_tests/test_dense_to_sparse.py b/test/conversion_tests/test_dense_to_sparse.py
index b17d1fe..82c4488 100644
--- a/test/conversion_tests/test_dense_to_sparse.py
+++ b/test/conversion_tests/test_dense_to_sparse.py
@@ -36,8 +36,8 @@ def test_wrong_input(self):
             dense_to_sparse(self.edge_dict)
 
     def test_2d_matrix(self):
-        mtrx = dense_to_sparse(self.matrix_2d)
-        self.assertTrue((mtrx[0] != self.sparse_2d).nnz == 0)
+        mtrx = dense_to_sparse([self.matrix_2d])
+        self.assertTrue((mtrx[0][0] != self.sparse_2d).nnz == 0)
 
     def test_2d_list(self):
         mtrx = dense_to_sparse(self.matrix_2d_list)
@@ -50,8 +50,8 @@ def test_3d_matrix_no_adj(self):
         self.assertTrue((adj_list[0] != self.sparse_2d).nnz == 0)
 
     def test_3d_adj(self):
-        mtrx = dense_to_sparse(self.matrix_3d, adjacency_axis=0, feature_axis=1)
-        self.assertTrue((mtrx[0] != self.sparse_2d).nnz == 0)
+        mtrx = dense_to_sparse([self.matrix_3d], adjacency_axis=0, feature_axis=1)
+        self.assertTrue((mtrx[0][0] != self.sparse_2d).nnz == 0)
 
     def test_3d_matrix_list(self):
         mtrx = dense_to_sparse(self.matrix_3d_list, adjacency_axis=0, feature_axis=1)
@@ -60,7 +60,7 @@ def test_3d_matrix_list(self):
 
     def test_4d_no_adj(self):
         with self.assertRaises(ValueError):
-            dense_to_sparse(self.matrix_4d)
+            dense_to_sparse([self.matrix_4d])
 
     def test_4d_adj(self):
         mtrx = dense_to_sparse(self.matrix_4d, adjacency_axis=0, feature_axis=0)
diff --git a/test/conversion_tests/test_networkx_to_dense.py b/test/conversion_tests/test_networkx_to_dense.py
index fe10852..ab69a2d 100644
--- a/test/conversion_tests/test_networkx_to_dense.py
+++ b/test/conversion_tests/test_networkx_to_dense.py
@@ -17,12 +17,10 @@ def setUp(self):
                                       [1, 0, 0, 1, 0]])
 
     def test_list(self):
-        mtrx = networkx_to_dense(self.graphs)
-        self.assertEqual(type(mtrx), list)
-
-    def test_np_type(self):
-        mtrx = networkx_to_dense(self.graphs)
-        self.assertEqual(type(mtrx[0]), np.ndarray)
+        mtrxs = networkx_to_dense(self.graphs)
+        self.assertEqual(type(mtrxs), list)
+        for mtrx in mtrxs:
+            self.assertEqual(type(mtrx), np.ndarray)
 
     def test_single_graph(self):
         mtrx = networkx_to_dense(self.graph)
@@ -39,3 +37,7 @@ def test_check_graph(self):
     def test_check_nonsense_input(self):
         with self.assertRaises(ValueError):
             networkx_to_dense(self.check_matrix)
+
+    def test_check_nonsense_list_input(self):
+        with self.assertRaises(ValueError):
+            networkx_to_dense([self.check_matrix] * 10)
diff --git a/test/conversion_tests/test_networkx_to_dgl.py b/test/conversion_tests/test_networkx_to_dgl.py
index 4050dc3..e50cf6e 100644
--- a/test/conversion_tests/test_networkx_to_dgl.py
+++ b/test/conversion_tests/test_networkx_to_dgl.py
@@ -16,12 +16,10 @@ def setUp(self):
         self.nx_array = np.array([nx.cycle_graph(20), nx.cycle_graph(15), nx.cycle_graph(20)])
 
     def test_list(self):
-        mtrx = networkx_to_dgl(self.graphs)
-        self.assertEqual(type(mtrx), list)
-
-    def test_dgl_type(self):
-        mtrx = networkx_to_dgl(self.graphs)
-        self.assertEqual(type(mtrx[0]), dgl.DGLGraph)
+        mtrxs = networkx_to_dgl(self.graphs)
+        self.assertEqual(type(mtrxs), list)
+        for mtrx in mtrxs:
+            self.assertEqual(type(mtrx), dgl.DGLGraph)
 
     def test_single_graph(self):
         mtrx = networkx_to_dgl(self.graph)
@@ -39,6 +37,15 @@ def test_check_nonsense_input(self):
         with self.assertRaises(ValueError):
             networkx_to_dgl(self.edge_dict)
 
+    def test_check_nonsense_list_input(self):
+        with self.assertRaises(ValueError):
+            networkx_to_dgl([self.edge_dict] * 10)
+
+    def test_check_nonsense_array_input(self):
+        with self.assertRaises(ValueError):
+            networkx_to_dgl(np.array([self.edge_dict] * 10))
+
     def test_nx_array(self):
-        mtrx = networkx_to_dgl(self.nx_array)
-        self.assertEqual(type(mtrx[0]), dgl.DGLGraph)
+        mtrxs = networkx_to_dgl(self.nx_array)
+        for mtrx in mtrxs:
+            self.assertEqual(type(mtrx), dgl.DGLGraph)
diff --git a/test/conversion_tests/test_networkx_to_sparse.py b/test/conversion_tests/test_networkx_to_sparse.py
index 7bd82ea..86de846 100644
--- a/test/conversion_tests/test_networkx_to_sparse.py
+++ b/test/conversion_tests/test_networkx_to_sparse.py
@@ -39,3 +39,7 @@ def test_check_graph(self):
     def test_check_nonsense_input(self):
         with self.assertRaises(ValueError):
             networkx_to_sparse(self.check_matrix)
+
+    def test_check_nonsense_list_input(self):
+        with self.assertRaises(ValueError):
+            networkx_to_sparse([self.check_matrix] * 10)
diff --git a/test/conversion_tests/test_save_networkx_to_file.py b/test/conversion_tests/test_save_networkx_to_file.py
index 1302e4b..fe99a88 100644
--- a/test/conversion_tests/test_save_networkx_to_file.py
+++ b/test/conversion_tests/test_save_networkx_to_file.py
@@ -50,3 +50,9 @@ def test_nonsense_input(self):
         path = "/tmp/graph25"
         with self.assertRaises(ValueError):
             save_networkx_to_file(self.nonsense, path=path, output_format=out_format)
+
+    def test_random_list_input(self):
+        out_format = "dot"
+        path = "/tmp/graph25"
+        with self.assertRaises(ValueError):
+            save_networkx_to_file([self.nonsense] * 20, path=path, output_format=out_format)
diff --git a/test/conversion_tests/test_sparse_to_dense.py b/test/conversion_tests/test_sparse_to_dense.py
index c36029c..0bcf15b 100644
--- a/test/conversion_tests/test_sparse_to_dense.py
+++ b/test/conversion_tests/test_sparse_to_dense.py
@@ -10,30 +10,37 @@ def setUp(self):
         # create sparse matrices
         sparse_matrix = sparse.eye(5)
         self.single_matrix = sparse_matrix
+        self.check_features = np.random.rand(5, 5)
+        self.features = sparse.csr_matrix(self.check_features.copy())
         self.matrix_list = [sparse_matrix] * 10
+        self.feature_list = [self.features] * 10
         # create dense control matrix
         self.control_matrix = np.eye(5)
         self.edge_dict = {(1, 0): 1, (2, 0): 1}
 
     def test_single_no_feat(self):
-        g = sparse_to_dense(self.single_matrix)
-        self.assertTrue(np.array_equal(g, self.control_matrix))
+        g = sparse_to_dense([self.single_matrix])
+        self.assertTrue(np.array_equal(g[0], self.control_matrix))
 
     def test_list_no_feat(self):
         g = sparse_to_dense(self.matrix_list)
-        self.assertTrue(np.array_equal(g[0], self.control_matrix))
+        for i in range(10):
+            self.assertTrue(np.array_equal(g[i], self.control_matrix))
 
     def test_nonsene_no_feat(self):
         with self.assertRaises(Exception):
             sparse_to_dense(self.edge_dict)
 
     def test_single_feat(self):
-        g = sparse_to_dense(self.single_matrix, features=self.single_matrix)
+        g = sparse_to_dense(self.single_matrix, features=self.features)
         self.assertEqual(np.shape(g), (5, 5, 2))
+        self.assertTrue(np.array_equal(g[..., 0], self.control_matrix))
+        self.assertTrue(np.array_equal(g[..., 1], self.check_features))
 
     def test_list_feat(self):
-        g = sparse_to_dense(self.matrix_list, features=self.matrix_list)
+        g = sparse_to_dense(self.matrix_list, features=self.feature_list)
         self.assertEqual(np.shape(g), (10, 5, 5, 2))
+        self.assertTrue(np.array_equal(g[0, ..., 1], self.check_features))
 
     def test_nonsense_feat(self):
         with self.assertRaises(Exception):
diff --git a/test/conversion_tests/test_sparse_to_dgl.py b/test/conversion_tests/test_sparse_to_dgl.py
index c8ae00b..644b2cc 100644
--- a/test/conversion_tests/test_sparse_to_dgl.py
+++ b/test/conversion_tests/test_sparse_to_dgl.py
@@ -48,3 +48,11 @@ def test_np_array(self):
     def test_nonsense(self):
         with self.assertRaises(Exception):
             sparse_to_dgl(self.edge_dict)
+
+    def test_nonsense_list(self):
+        with self.assertRaises(Exception):
+            sparse_to_dgl([self.edge_dict] * 10)
+
+    def test_nonsense_array(self):
+        with self.assertRaises(Exception):
+            sparse_to_dgl(np.array([self.edge_dict] * 10))
diff --git a/test/conversion_tests/test_sparse_to_networkx.py b/test/conversion_tests/test_sparse_to_networkx.py
index 1d14e77..9211eb5 100644
--- a/test/conversion_tests/test_sparse_to_networkx.py
+++ b/test/conversion_tests/test_sparse_to_networkx.py
@@ -19,10 +19,8 @@ def setUp(self):
     def test_list(self):
         lst = sparse_to_networkx(self.matrix_list)
         self.assertEqual(type(lst), list)
-
-    def test_list_element(self):
-        lst = sparse_to_networkx(self.matrix_list)
-        self.assertEqual(type(lst[0]), nx.classes.Graph)
+        for itm in lst:
+            self.assertEqual(type(itm), nx.classes.Graph)
 
     def test_single(self):
         single = sparse_to_networkx(self.single_matrix)
@@ -44,3 +42,7 @@ def test_adjacency(self):
     def test_nonsense(self):
         with self.assertRaises(Exception):
             sparse_to_networkx(self.edge_dict)
+
+    def test_nonsense_list(self):
+        with self.assertRaises(Exception):
+            sparse_to_networkx([self.edge_dict] * 10)
diff --git a/test/measure_tests/test_measure_transform.py b/test/measure_tests/test_measure_transform.py
index 3f22edc..db3d134 100644
--- a/test/measure_tests/test_measure_transform.py
+++ b/test/measure_tests/test_measure_transform.py
@@ -1,4 +1,5 @@
 import unittest
+
 import numpy as np
 import networkx as nx
 import os
@@ -8,7 +9,7 @@
 class GraphMeasureTransformTests(unittest.TestCase):
 
     def setUp(self):
-        self.X_nx = [nx.erdos_renyi_graph(20, p=0.3)] * 10
+        self.X_nx = [nx.barabasi_albert_graph(20, 2)] * 10
         self.y = np.random.rand(10)
         self.ids = list(range(10))
         # generate random matrices
@@ -41,6 +42,14 @@ def test_transform_own_measures(self):
         measures = g_transform.transform(self.X_nx)
         self.assertEqual(measures.shape, (10, 2))
 
+    def test_transform_own_measures_parallel(self):
+        g_transform = GraphMeasureTransform(graph_functions={"global_efficiency": {},
+                                                             "local_efficiency": {}},
+                                            n_processes=2)
+        g_transform.fit(self.X_nx, self.y)
+        measures = g_transform.transform(self.X_nx)
+        self.assertEqual(measures.shape, (10, 2))
+
     def test_transform_dict_node(self):
         g_transform = GraphMeasureTransform(graph_functions={"eigenvector_centrality": {}})
         g_transform.fit(self.X_nx, self.y)
@@ -97,9 +106,9 @@ def test_extract_input_mtrx(self):
         self.assertTrue(os.path.exists(path))
         os.remove(path)
 
-    def test_extract_nonsense_input(self):
+    def test_extract_nonsense_input_no_ids(self):
         g_transform = GraphMeasureTransform()
-        with self.assertRaises(TypeError):
+        with self.assertRaises(ValueError):
             g_transform.fit(self.edge_dict, self.y)
             g_transform.extract_measures(self.edge_dict)
 
@@ -132,18 +141,6 @@ def test_extract_dict_edge(self):
         self.assertTrue(os.path.exists(path))
         os.remove(path)
 
-    def test_extract_list(self):
-        path = "/tmp/test.csv"
-        g_transform = GraphMeasureTransform(graph_functions={"voterank": {}})
-        with self.assertRaises(NotImplementedError):
-            g_transform.extract_measures(self.X_nx, path, self.ids)
-
-    def test_extract_dict_dict(self):
-        path = "/tmp/test.csv"
-        g_transform = GraphMeasureTransform(graph_functions={"communicability": {}})
-        with self.assertRaises(NotImplementedError):
-            g_transform.extract_measures(self.X_nx, path, self.ids)
-
     def test_extract_float_or_dict(self):
         path = "/tmp/test.csv"
         g_transform = GraphMeasureTransform(graph_functions={"clustering": {}})
@@ -151,18 +148,6 @@ def test_extract_float_or_dict(self):
         self.assertTrue(os.path.exists(path))
         os.remove(path)
 
-    def test_extract_tuple_dict(self):
-        path = "/tmp/test.csv"
-        g_transform = GraphMeasureTransform(graph_functions={"hits": {}})
-        with self.assertRaises(NotImplementedError):
-            g_transform.extract_measures(self.X_nx, path, self.ids)
-
-    def test_extract_dual_tuple(self):
-        path = "/tmp/test.csv"
-        g_transform = GraphMeasureTransform(graph_functions={"non_randomness": {}})
-        with self.assertRaises(NotImplementedError):
-            g_transform.extract_measures(self.X_nx, path, self.ids)
-
     def test_extract_no_id(self):
         g_transform = GraphMeasureTransform()
         with self.assertRaises(Exception):
@@ -187,3 +172,39 @@ def test_compute_average(self):
         X_average = g_transform.transform(self.X_nx)
 
         np.testing.assert_array_equal(X_average, X_nodes_average)
+
+    # !!!
+    #   Below this line are legacy tests where
+    #   NotImplementedErrors are expected.
+    #   ---------------------------------------
+    #   todo: Reimplement all these tests!
+    # !!!
+
+    # todo: rewrite
+    """def test_extract_list(self):
+        path = "/tmp/test.csv"
+        g_transform = GraphMeasureTransform(graph_functions={"voterank": {}})
+        with self.assertRaises(NotImplementedError):
+            g_transform.extract_measures(self.X_nx, path, self.ids)"""
+
+    # todo: rewrite
+    """def test_extract_dict_dict(self):
+        path = "/tmp/test.csv"
+        g_transform = GraphMeasureTransform(graph_functions={"communicability": {}})
+        with self.assertRaises(NotImplementedError):
+            g_transform.extract_measures(self.X_nx, path, self.ids)"""
+
+    # todo: reimplement
+    """def test_extract_tuple_dict(self):
+        path = "/tmp/test.csv"
+        g_transform = GraphMeasureTransform(graph_functions={"hits": {}})
+        with self.assertRaises(NotImplementedError):
+            g_transform.extract_measures(self.X_nx, path, self.ids)"""
+
+    # todo: reimplement
+    """    
+    def test_extract_dual_tuple(self):
+        path = "/tmp/test.csv"
+        g_transform = GraphMeasureTransform(graph_functions={"non_randomness": {}})
+        with self.assertRaises(NotImplementedError):
+            g_transform.extract_measures(self.X_nx, path, self.ids)"""
diff --git a/test/population_averaging_tests/__init__.py b/test/population_averaging_tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/population_averaging_tests/test_population_averaging.py b/test/population_averaging_tests/test_population_averaging.py
new file mode 100644
index 0000000..7ae58ed
--- /dev/null
+++ b/test/population_averaging_tests/test_population_averaging.py
@@ -0,0 +1,33 @@
+import unittest
+
+import numpy as np
+import networkx as nx
+import os
+from photonai_graph.PopulationAveragingTransform import PopulationAveragingTransform
+
+
+class PopulationAveragingTests(unittest.TestCase):
+
+    def setUp(self):
+        # Randomly create 10 barabasi albert graphs and store the adjacency as numpy array
+        self.graphs = np.array([nx.adjacency_matrix(nx.barabasi_albert_graph(20, 2)).todense() for _ in range(10)])
+        # Generate random graphs for 10 subjects containing 3 measurements
+        self.other_graphs = np.random.rand(10, 20, 20, 3)
+
+    def test_averaging(self):
+        mean = np.squeeze(np.mean(self.graphs, axis=0))
+        pat = PopulationAveragingTransform()
+        pat.fit(self.graphs[..., np.newaxis], 0)
+        self.assertTrue(np.array_equal(mean, pat.learned_mean))
+
+        transformed = pat.transform(self.other_graphs)
+        for subject in range(transformed.shape[0]):
+            self.assertTrue(np.array_equal(mean, transformed[subject, ..., 0]))
+            self.assertTrue(np.array_equal(self.other_graphs[subject, ..., 2], transformed[subject, ..., 1]))
+
+    def test_unfitted_transformer(self):
+        pat = PopulationAveragingTransform()
+        with self.assertRaises(ValueError):
+            pat.transform(self.other_graphs)
+
+