Repository: AKSW/frankgraphbench (language: python)

Issue:
Refactor: Header of standardized .csv dataset files
**Currently behaviour:**
The user needs to indicate if the column feature is a list of properties with the directive `grouped`.
Example:
```yaml
....
enrich:
      map_path: datasets/ml-100k/processed/map.csv
      enrich_path: datasets/ml-100k/processed/enriched.csv
      remove_unmatched: False
      properties:
        - type: subject
          grouped: True
          sep: "::"
        - type: director
          grouped: True
          sep: "::"
```
Problems: 
* The user would need to indicate this for all features. However the user doesn't need to know in advance if the feature is a list of properties.
* The user should be able to set the features that they want to add to the pipeline only by their name, without requiring any extra information.

**Proposed Solution:**
Use a new Header standard that indicates the feature name and its type, such as `column_name::type`.  Example of .csv header:
```csv
item_id::string, abstract::string, producer::string_list, distributor::string_list,
```
Pros:
* This allows the Dataloader to know in advance the feature type and perform the proper processing method.
* The user only needs to specify the feature names on the config file

The example above following the new standard:
```
enrich:
      map_path: datasets/ml-100k/processed/map.csv
      enrich_path: datasets/ml-100k/processed/enriched.csv
      remove_unmatched: False
      properties: [subject, director]
```

Currently types allowed:
- *string*
- *string_list*, separated by "::"
- *number*


Discussion / hints:

Fix patch:
diff --git a/data_integration/dataset.py b/data_integration/dataset.py
index 4589271..e23e8cc 100644
--- a/data_integration/dataset.py
+++ b/data_integration/dataset.py
@@ -8,7 +8,8 @@
 from SPARQLWrapper import SPARQLWrapper, JSON
 from tqdm import tqdm
 
-class Dataset():
+
+class Dataset:
     def __init__(self, input_path, output_path, n_workers=1):
         r"""
         Base class for Recommender System's datasets
@@ -21,75 +22,76 @@ def __init__(self, input_path, output_path, n_workers=1):
         self.timeout = 180
 
         # Output files
-        self.item_filename = os.path.join(self.output_path, 'item.csv')
-        self.user_filename = os.path.join(self.output_path, 'user.csv')
-        self.rating_filename = os.path.join(self.output_path, 'rating.csv')
-        self.social_filename = os.path.join(self.output_path, 'social.csv')
-        self.map_filename = os.path.join(self.output_path, 'map.csv')
-        self.enriched_filename = os.path.join(self.output_path, 'enriched.csv')
+        self.item_filename = os.path.join(self.output_path, "item.csv")
+        self.user_filename = os.path.join(self.output_path, "user.csv")
+        self.rating_filename = os.path.join(self.output_path, "rating.csv")
+        self.social_filename = os.path.join(self.output_path, "social.csv")
+        self.map_filename = os.path.join(self.output_path, "map.csv")
+        self.enriched_filename = os.path.join(self.output_path, "enriched.csv")
 
         # create output path if doesn't exist
         self.has_output_path()
         # Mapping regex special chars
-        self._special_chars_map = str.maketrans({x:'' for x in string.punctuation})
-
+        self._special_chars_map = str.maketrans({x: "" for x in string.punctuation})
 
         # The following variables need to be overwritten by the subclasses of Dataset()
-        self.dataset_name = ''
-        self.item_separator = '' 
-        self.user_separator = ''
-        self.rating_separator = ''
-        self.social_separator = ''
-        # Chosen features to be extracted from dataset
-        self.item_features = []
-        self.user_features = []
-        self.rating_features = []
+        self.dataset_name = ""
+        self.item_separator = ""
+        self.user_separator = ""
+        self.rating_separator = ""
+        self.social_separator = ""
+        # Chosen fields to be extracted from dataset and mapping between the original column name and
+        # their standardized name
+        # Example: {"movie_title": "movie_title::string"}
+        self.reverse_dict = lambda x: {v: k for k, v in x.items()}
+        self.item_fields = {}
+        self.user_fields = {}
+        self.rating_fields = {}
         self.map_query_template = Template(None)
-        self.enrich_query_template =  Template(None)
-
+        self.enrich_query_template = Template(None)
 
     def has_output_path(self):
         if not os.path.isdir(self.output_path):
             os.mkdir(self.output_path)
-    
-    def load_item_data(self) -> pd.DataFrame():
+
+    def load_item_data(self) -> pd.DataFrame:
         """
         Loads item info of Dataset
-        :return: returns pd.Dataframe() containing each item info.
+        :return: returns pd.Dataframe containing each item info.
         """
         raise NotImplementedError
 
-    def load_user_data(self) -> pd.DataFrame():
+    def load_user_data(self) -> pd.DataFrame:
         """
         Loads user info of Dataset
-        :return: returns pd.Dataframe() containing each user info.
+        :return: returns pd.Dataframe containing each user info.
         """
         raise NotImplementedError
 
-    def load_rating_data(self) -> pd.DataFrame():
+    def load_rating_data(self) -> pd.DataFrame:
         """
         Loads rating interactions of Dataset
-        :return: returns pd.Dataframe() containing each rating interaction.
+        :return: returns pd.Dataframe containing each rating interaction.
         """
         raise NotImplementedError
-    
-    def load_social_data(self) -> pd.DataFrame():
+
+    def load_social_data(self) -> pd.DataFrame:
         """
         Loads social links of Dataset
-        :return: returns pd.Dataframe() containing each social link between users.
+        :return: returns pd.Dataframe containing each social link between users.
         """
         raise NotImplementedError
-    
-    def entity_linking(self, df_item) -> pd.DataFrame():
+
+    def entity_linking(self, df_item) -> pd.DataFrame:
         """
         Entity link each item to their corresponding DBpedia's URI
-        :return: returns pd.Dataframe() containing each item_id and their mapped URI 
+        :return: returns pd.Dataframe containing each item_id and their mapped URI
         """
         raise NotImplementedError
 
     def enrich(self, df_map):
         raise NotImplementedError
-    
+
     def get_map_query(self, *args, **kwargs) -> str:
         """
         Returns query parameters to be substituted in the map query template.
@@ -107,14 +109,14 @@ def get_enrich_query(self, *args, **kwargs) -> str:
     def parallel_queries(self, queue, return_type=JSON):
         """
         Parallel query SPARQL endpoint using Threads
-        :arguments: 
+        :arguments:
             queue: queue of tuples indicating the item_id and SPARQL query string
         :returns: list containing tuples with item_id and response result in JSON
         """
         n_iters = queue.qsize()
         pbar = tqdm(total=n_iters)
-        pbar.set_description('Requesting SPARQL endpoint for each item')
-        
+        pbar.set_description("Requesting SPARQL endpoint for each item")
+
         # instantiating each Worker
         workers = []
         for _ in range(self.n_workers):
@@ -130,18 +132,18 @@ def parallel_queries(self, queue, return_type=JSON):
         responses = []
         for worker in workers:
             responses.extend(worker.local_results)
-        
+
         return responses
-    
+
     def sequential_queries(self, q, return_type=JSON):
         """
         Sequential query SPARQL endpoint
-        :arguments: 
+        :arguments:
             queue: queue of tuples indicating the item_id and SPARQL query string
         :returns: list containing tuples with item_id and response result in JSON
         """
         pbar = tqdm(total=q.qsize())
-        pbar.set_description('Requesting SPARQL endpoint for each item')
+        pbar.set_description("Requesting SPARQL endpoint for each item")
         responses = []
 
         while True:
@@ -153,12 +155,12 @@ def sequential_queries(self, q, return_type=JSON):
             except queue.Empty:
                 break
             except Exception as e:
-                print(f'Exception:')
+                print(f"Exception:")
                 print(e)
 
         return responses
 
-    def _query(self, query, return_type=JSON) -> dict():
+    def _query(self, query, return_type=JSON) -> dict:
         sparql = SPARQLWrapper(self.sparql_endpoint)
         sparql.setTimeout(self.timeout)
         sparql.setQuery(query)
@@ -168,57 +170,57 @@ def _query(self, query, return_type=JSON) -> dict():
             return sparql.query().convert()
         except Exception as e:
             raise e
-    
+
     def convert_item_data(self):
         """
         Converts loaded item data to a processed csv (item.csv).
         """
         try:
-            print(f'Creating file: {self.item_filename}.')
+            print(f"Creating file: {self.item_filename}.")
             df_item = self.load_item_data()
-            print(f'{df_item.shape[0]} items with {df_item.shape[1]} Fields')
-            print('Fields: ' + ', '.join(self.item_fields))
+            print(f"{df_item.shape[0]} items with {df_item.shape[1]} Fields")
+            print("Fields: " + ", ".join(df_item.columns))
             df_item.to_csv(self.item_filename, index=False)
         except NotImplementedError:
-            print('Override load_item_data() method of your Dataset subclass.')
-    
+            print("Override load_item_data() method of your Dataset subclass.")
+
     def convert_user_data(self):
         """
-        Converts loaded user data to a processed csv (user.csv). 
+        Converts loaded user data to a processed csv (user.csv).
         """
         try:
-            print(f'Creating file: {self.user_filename}')
+            print(f"Creating file: {self.user_filename}")
             df_user = self.load_user_data()
-            print(f'{df_user.shape[0]} users with {df_user.shape[1]} Fields')
-            print('Fields: ' + ', '.join(self.user_fields))
+            print(f"{df_user.shape[0]} users with {df_user.shape[1]} Fields")
+            print("Fields: " + ", ".join(df_user.columns))
             df_user.to_csv(self.user_filename, index=False)
         except NotImplementedError:
-            print(f'Override load_user_data() of your Dataset subclass.')
+            print(f"Override load_user_data() of your Dataset subclass.")
 
     def convert_rating_data(self):
         """
         Converts loaded rating data to a processed csv (rating.csv)
         """
         try:
-            print(f'Creating file: {self.rating_filename}')
+            print(f"Creating file: {self.rating_filename}")
             df_rating = self.load_rating_data()
-            print(f'{df_rating.shape[0]} ratings with {df_rating.shape[1]} Fields')
-            print('Fields: ' + ', '.join(self.rating_fields))
+            print(f"{df_rating.shape[0]} ratings with {df_rating.shape[1]} Fields")
+            print("Fields: " + ", ".join(df_rating.columns))
             df_rating.to_csv(self.rating_filename, index=False)
         except NotImplementedError:
-            print(f'Override load_rating_data() of your Dataset subclass.')
+            print(f"Override load_rating_data() of your Dataset subclass.")
 
     def convert_social_data(self):
         """
         Converts loaded social data to a processed csv (social.csv)
         """
         try:
-            print(f'Creating file: {self.social_filename}')
+            print(f"Creating file: {self.social_filename}")
             df_social = self.load_social_data()
-            print(f'{df_social.shape[0]} social link between users')
+            print(f"{df_social.shape[0]} social link between users")
             df_social.to_csv(self.social_filename, index=False)
         except NotImplementedError:
-            print(f'Override load_rating_data() of your Dataset subclass.')
+            print(f"Override load_rating_data() of your Dataset subclass.")
 
     def map_URIs(self):
         """
@@ -226,18 +228,20 @@ def map_URIs(self):
         """
         try:
             df_item = pd.read_csv(self.item_filename)
-            print(f'Mapping each dataset item with DBpedia: {self.map_filename}')
+            df_item.columns = [col.split("::")[0] for col in df_item.columns]
+            print(f"Mapping each dataset item with DBpedia: {self.map_filename}")
             df_map = self.entity_linking(df_item)
             df_map.to_csv(self.map_filename, index=False)
-            
+
             # print mapping statistics
             n_items = df_map.shape[0]
-            n_unmatched = df_map['URI'].isna().sum()
-            print(f"{n_unmatched} items weren't matched, corresponding to {n_unmatched/n_items*100:.2f}% of a total of {n_items}.")
-            
+            n_unmatched = df_map["URI"].isna().sum()
+            print(
+                f"{n_unmatched} items weren't matched, corresponding to {n_unmatched/n_items*100:.2f}% of a total of {n_items}."
+            )
 
         except NotImplementedError:
-            print('Override entity_linking() method of your Dataset subclass.')
+            print("Override entity_linking() method of your Dataset subclass.")
 
     def enrich_data(self):
         """
@@ -246,27 +250,21 @@ def enrich_data(self):
 
         try:
             df_map = pd.read_csv(self.map_filename)
-            print(f'Enriching each item with DBpedia resources: {self.enriched_filename}')
+            print(
+                f"Enriching each item with DBpedia resources: {self.enriched_filename}"
+            )
             df_enrich = self.enrich(df_map)
             df_enrich.to_csv(self.enriched_filename)
 
-            self.report_enriching(df_enrich)            
+            self.report_enriching(df_enrich)
 
         except NotImplementedError:
-            print('Override enrich() method of your Dataset subclass.')
-    
+            print("Override enrich() method of your Dataset subclass.")
+
     def report_enriching(self, df):
         n_rows = df.shape[0]
         for col in df.columns:
             n_rows_with = df[col].notna().sum()
-            print(f'number of entities with the property {col}: {n_rows_with} ({n_rows_with/n_rows*100:.2f}%)')
-    
-
-
-
-
-    
-
-
-        
-        
\ No newline at end of file
+            print(
+                f"number of entities with the property {col}: {n_rows_with} ({n_rows_with/n_rows*100:.2f}%)"
+            )
diff --git a/data_integration/datasets/lastfm.py b/data_integration/datasets/lastfm.py
index e86917f..6be5aa1 100644
--- a/data_integration/datasets/lastfm.py
+++ b/data_integration/datasets/lastfm.py
@@ -11,26 +11,31 @@
 import pandas as pd
 from tqdm import tqdm
 from thefuzz import process
-from SPARQLWrapper import CSV 
+from SPARQLWrapper import CSV
 
 
 class LastFM(Dataset):
     def __init__(self, input_path, output_path, n_workers=1):
         super().__init__(input_path, output_path, n_workers)
-        self.dataset_name = 'LastFM'
+        self.dataset_name = "LastFM"
 
-        self.item_separator = '\t'
+        self.item_separator = "\t"
         # self.user_separator = '|'
-        self.rating_separator = '\t'
-        self.social_separator = '\t'
-
-        self.item_fields = ['item_id', 'name']
-        self.user_fields = ['user_id']
-        self.rating_fields = ['user_id', 'item_id', 'rating']
-        # self.map_fields = ['item_id', 'URI']
-        self.social_fields = ['user1', 'user2']
-
-        self.map_query_template = Template('''
+        self.rating_separator = "\t"
+        self.social_separator = "\t"
+
+        self.item_fields = {"id": "item_id::string", "name": "name::string"}
+        self.user_fields = {"userID": "user_id::string"}
+        self.rating_fields = {
+            "userID": "user_id::string",
+            "artistID": "item_id::string",
+            "weight": "rating::number",
+        }
+        self.map_fields = {"item_id": "item_id::string", "URI": "URI::string"}
+        self.social_fields = {"userID": "user1::string", "friendID": "user2::string"}
+
+        self.map_query_template = Template(
+            """
             PREFIX dct:  <http://purl.org/dc/terms/>
             PREFIX dbo:  <http://dbpedia.org/ontology/>
             PREFIX dbr:  <http://dbpedia.org/resource/>
@@ -63,9 +68,11 @@ def __init__(self, input_path, output_path, n_workers=1):
                     FILTER regex(?label, "$name_regex", "i") .
                 }
             }
-        ''')
+        """
+        )
 
-        self.enrich_query_template = Template('''
+        self.enrich_query_template = Template(
+            """
             PREFIX dct:  <http://purl.org/dc/terms/>
             PREFIX dbo:  <http://dbpedia.org/ontology/>
             PREFIX dbr:  <http://dbpedia.org/resource/>
@@ -92,36 +99,36 @@ def __init__(self, input_path, output_path, n_workers=1):
                                               
                 FILTER(LANG(?abstract) = 'en')
             }
-        ''')
+        """
+        )
 
     def load_item_data(self) -> pd.DataFrame():
-        filename = os.path.join(self.input_path, 'artists.dat')
-        columns = ['id','name','url','pictureURL']
+        filename = os.path.join(self.input_path, "artists.dat")
 
         df = pd.read_csv(filename, sep=self.item_separator)
-        df = df.drop(columns[2:], axis=1) # Will not use url and picture URL
-        df.columns = self.item_fields
+        df = df.rename(self.item_fields, axis=1)
+        df = df[self.item_fields.values()]
         return df
 
     def entity_linking(self, df_item) -> pd.DataFrame():
         q = queue.Queue()
-        for idx, row in df_item[['name', 'item_id']].iterrows():
-            query = self.get_map_query(row['name'])
-            q.put((row['item_id'], query))
-        
+        for idx, row in df_item[["name", "item_id"]].iterrows():
+            query = self.get_map_query(row["name"])
+            q.put((row["item_id"], query))
+
         if self.n_workers > 1:
             responses = self.parallel_queries(q)
         else:
             responses = self.sequential_queries(q)
-        
+
         URI_mapping = {}
-        for response in tqdm(responses, desc='Disambiguating query return'):
+        for response in tqdm(responses, desc="Disambiguating query return"):
             candidate_URIs = []
             idx, result = response
-            for binding in result['results']['bindings']:
-                URI = binding['artist']['value']
+            for binding in result["results"]["bindings"]:
+                URI = binding["artist"]["value"]
                 candidate_URIs.append(URI)
-            
+
             expected_URI = f'http://dbpedia.org/resource/{df_item.loc[df_item.item_id == idx]["name"]}'
             str_matching_result = process.extractOne(expected_URI, candidate_URIs)
 
@@ -129,79 +136,79 @@ def entity_linking(self, df_item) -> pd.DataFrame():
                 URI, _ = str_matching_result
                 URI_mapping[idx] = URI
 
-        df_map = pd.DataFrame({'item_id': df_item['item_id']})
-        df_map.set_index('item_id')
-        df_map['URI'] = df_map['item_id'].apply(lambda id: URI_mapping.get(id))
+        df_map = pd.DataFrame({"item_id": df_item["item_id"]})
+        df_map.set_index("item_id")
+        df_map["URI"] = df_map["item_id"].apply(lambda id: URI_mapping.get(id))
 
         return df_map
-    
+
     def get_map_query(self, name) -> str:
         name = name.translate(self._special_chars_map)
-        name = name.replace(' ', '.*')
-        name = '^' + name
-        name = name + '$'
-        
-        params = {'name_regex': name}
+        name = name.replace(" ", ".*")
+        name = "^" + name
+        name = name + "$"
+
+        params = {"name_regex": name}
         query = self.map_query_template.substitute(**params)
         return query
-    
+
     def enrich(self, df_map):
-        df_map = df_map[df_map['URI'].notna()]
+        df_map = df_map[df_map["URI"].notna()]
 
         q = queue.Queue()
-        for _, row in df_map[['URI', 'item_id']].iterrows():
-            query = self.get_enrich_query(row['URI'])
-            q.put((row['item_id'], query))
+        for _, row in df_map[["URI", "item_id"]].iterrows():
+            query = self.get_enrich_query(row["URI"])
+            q.put((row["item_id"], query))
 
         if self.n_workers > 1:
             responses = self.parallel_queries(q, CSV)
         else:
             responses = self.sequential_queries(q, CSV)
-        
+
         item_enriching = defaultdict(dict)
         for response in responses:
             idx, result = response
             df = pd.read_csv(BytesIO(result))
 
             if df.shape[0] > 1:
-                print('At least one property has more than one value!')
+                print("At least one property has more than one value!")
                 print(df.value_counts(dropna=False))
 
-            item_enriching[idx] = df.iloc[0] # getting pd.Series
+            item_enriching[idx] = df.iloc[0]  # getting pd.Series
 
-        df_enrich = pd.DataFrame.from_dict(item_enriching, orient='index')
-        df_enrich.index.name = 'item_id'
+        df_enrich = pd.DataFrame.from_dict(item_enriching, orient="index")
+        df_enrich.index.name = "item_id"
 
         return df_enrich
 
     def get_enrich_query(self, URI) -> str:
-        params = {'URI': URI}
+        params = {"URI": URI}
         query = self.enrich_query_template.substitute(**params)
         return query
-        
-    
+
     def load_rating_data(self) -> pd.DataFrame():
-        filename = os.path.join(self.input_path, 'user_artists.dat')
+        filename = os.path.join(self.input_path, "user_artists.dat")
         df = pd.read_csv(filename, sep=self.rating_separator)
-        df.columns = self.rating_fields
+        df = df.rename(self.rating_fields, axis=1)
+        df = df[self.rating_fields.values()]
 
         return df
-    
+
     def load_social_data(self) -> pd.DataFrame():
-        filename = os.path.join(self.input_path, 'user_friends.dat')
+        filename = os.path.join(self.input_path, "user_friends.dat")
         df = pd.read_csv(filename, sep=self.social_separator)
-        df.columns = self.social_fields
+        df = df.rename(self.social_fields, axis=1)
+        df = df[self.social_fields.values()]
 
         return df
-    
+
     def load_user_data(self) -> pd.DataFrame():
         # There is no .dat only for user (none aditional user info)
         # Will extract the users from the rating data
-        filename = os.path.join(self.input_path, 'user_artists.dat')
+        filename = os.path.join(self.input_path, "user_artists.dat")
         df = pd.read_csv(filename, sep=self.rating_separator)
-        df.columns = self.rating_fields
-        users = df['user_id'].unique()
-        df = pd.DataFrame(pd.Series(users), columns=['user_id'])
+        users = df["userID"].unique()
+        df = df.rename(self.user_fields)
+        df = pd.DataFrame(pd.Series(users), columns=self.user_fields.values())
 
         return df
-    
\ No newline at end of file
diff --git a/data_integration/datasets/movielens.py b/data_integration/datasets/movielens.py
index 8f929b6..3ac1591 100644
--- a/data_integration/datasets/movielens.py
+++ b/data_integration/datasets/movielens.py
@@ -11,16 +11,18 @@
 import pandas as pd
 from tqdm import tqdm
 from thefuzz import process
-from SPARQLWrapper import CSV 
+from SPARQLWrapper import CSV
 
 
 class MovieLens(Dataset):
     """
     General MovieLens(Dataset) class for data integration between DBpedia and MovieLens
     """
+
     def __init__(self, input_path, output_path, n_workers=1):
         super().__init__(input_path, output_path, n_workers)
-        self.map_query_template = Template('''
+        self.map_query_template = Template(
+            """
             PREFIX dct:  <http://purl.org/dc/terms/>
             PREFIX dbo:  <http://dbpedia.org/ontology/>
             PREFIX dbr:  <http://dbpedia.org/resource/>
@@ -42,9 +44,11 @@ def __init__(self, input_path, output_path, n_workers=1):
                     FILTER regex(?label, "$name_regex", "i") .
                 }
             }
-        ''')
-        
-        self.enrich_query_template = Template('''
+        """
+        )
+
+        self.enrich_query_template = Template(
+            """
             PREFIX dct:  <http://purl.org/dc/terms/>
             PREFIX dbo:  <http://dbpedia.org/ontology/>
             PREFIX dbr:  <http://dbpedia.org/resource/>
@@ -71,27 +75,30 @@ def __init__(self, input_path, output_path, n_workers=1):
 
                 FILTER(LANG(?abstract) = 'en')
             }
-        ''')
-    
+        """
+        )
+
     def _extract_title(self, title):
-        regex = re.compile(r'\((\d{4})\)')
+        regex = re.compile(r"\((\d{4})\)")
         rgx_match = regex.search(title)
         year_start, _ = rgx_match.span()
-        movie_title = title[:year_start-1].strip() 
+        movie_title = title[: year_start - 1].strip()
 
         # removing parenthesis (name of the film in other languages)
-        movie_title = [s.split(')')[-1] for s in movie_title.split('(')][0]
+        movie_title = [s.split(")")[-1] for s in movie_title.split("(")][0]
 
         # Reversing titles with X, The
-        movie_title = movie_title.split(',')
-        movie_title = [movie_title[-1].strip()] + [title.strip() for title in movie_title[:-1]]
-        movie_title = ' '.join(movie_title)
-        
+        movie_title = movie_title.split(",")
+        movie_title = [movie_title[-1].strip()] + [
+            title.strip() for title in movie_title[:-1]
+        ]
+        movie_title = " ".join(movie_title)
+
         movie_title = movie_title.strip()
         return movie_title
 
     def _extract_year(self, title):
-        regex = re.compile(r'\((\d{4})\)')
+        regex = re.compile(r"\((\d{4})\)")
         rgx_match = regex.search(title)
         movie_year = rgx_match.group()
         movie_year = int(movie_year[1:-1])
@@ -99,224 +106,270 @@ def _extract_year(self, title):
 
     def entity_linking(self, df_item) -> pd.DataFrame():
         q = queue.Queue()
-        for idx, row in df_item[['movie_title', 'movie_year', 'item_id']].iterrows():
-            query = self.get_map_query(row['movie_title'], row['movie_year'])
-            q.put((row['item_id'], query))
+        for idx, row in df_item[["movie_title", "movie_year", "item_id"]].iterrows():
+            query = self.get_map_query(row["movie_title"], row["movie_year"])
+            q.put((row["item_id"], query))
 
         if self.n_workers > 1:
             responses = self.parallel_queries(q)
         else:
             responses = self.sequential_queries(q)
-        
+
         URI_mapping = defaultdict(dict)
-        for response in tqdm(responses, desc='Disambiguating query return'):
+        for response in tqdm(responses, desc="Disambiguating query return"):
             candidate_URIs = []
             idx, result = response
-            for binding in result['results']['bindings']:
-                URI = binding['film']['value']
+            for binding in result["results"]["bindings"]:
+                URI = binding["film"]["value"]
                 candidate_URIs.append(URI)
-                
+
             expected_URI = f"http://dbpedia.org/resource/{df_item.loc[df_item.item_id == idx]['movie_title']}"
             str_matching_result = process.extractOne(expected_URI, candidate_URIs)
-            
+
             if str_matching_result is not None:
                 URI, _ = str_matching_result
                 URI_mapping[idx] = URI
-        
-        df_map = pd.DataFrame({'item_id': df_item['item_id']})
-        df_map.set_index('item_id')
-        df_map['URI'] = df_map['item_id'].apply(lambda id: URI_mapping.get(id))
+
+        df_map = pd.DataFrame({"item_id": df_item["item_id"]})
+        df_map.set_index("item_id")
+        df_map["URI"] = df_map["item_id"].apply(lambda id: URI_mapping.get(id))
+        df_map.rename(self.map_fields, axis=1)
 
         return df_map
 
     def get_map_query(self, title, year) -> str:
         # title = title.translate(self._special_chars_map)
-        title = title.replace(' ', '.*')
-        title = '^' + title
-        
+        title = title.replace(" ", ".*")
+        title = "^" + title
+
         params = {
-            'name_regex': title,
-            'year_category': f'dbr:Category:{str(year)}_films'
+            "name_regex": title,
+            "year_category": f"dbr:Category:{str(year)}_films",
         }
         query = self.map_query_template.substitute(**params)
         return query
-    
+
     def enrich(self, df_map) -> pd.DataFrame():
-        df_map = df_map[df_map['URI'].notna()]
+        df_map = df_map[df_map["URI"].notna()]
 
         q = queue.Queue()
-        for _, row in df_map[['URI', 'item_id']].iterrows():
-            query = self.get_enrich_query(row['URI'])
-            q.put((row['item_id'], query))
+        for _, row in df_map[["URI", "item_id"]].iterrows():
+            query = self.get_enrich_query(row["URI"])
+            q.put((row["item_id"], query))
 
         if self.n_workers > 1:
             responses = self.parallel_queries(q, CSV)
         else:
             responses = self.sequential_queries(q, CSV)
-        
+
         item_enriching = defaultdict(dict)
         for response in responses:
             idx, result = response
             df = pd.read_csv(BytesIO(result))
 
             if df.shape[0] > 1:
-                print('At least one property has more than one value!')
+                print("At least one property has more than one value!")
                 print(df.value_counts(dropna=False))
 
-            item_enriching[idx] = df.iloc[0] # getting pd.Series
+            item_enriching[idx] = df.iloc[0]  # getting pd.Series
 
-        df_enrich = pd.DataFrame.from_dict(item_enriching, orient='index')
-        df_enrich.index.name = 'item_id'
+        df_enrich = pd.DataFrame.from_dict(item_enriching, orient="index")
+        df_enrich.index.name = "item_id"
 
         return df_enrich
-    
+
     def get_enrich_query(self, URI) -> str:
-        params = {'URI': URI}
+        params = {"URI": URI}
         query = self.enrich_query_template.substitute(**params)
         return query
 
+
 class MovieLens100k(MovieLens):
     """
     Dataset class for data integration between DBpedia and MovieLens-100k
     """
+
     def __init__(self, input_path, output_path, n_workers=1):
         super().__init__(input_path, output_path, n_workers)
-        self.dataset_name = 'MovieLens100k'
-
-        self.item_separator = '|'
-        self.user_separator = '|'
-        self.rating_separator = '\t'
-
-        self.item_fields = ['item_id', 'movie_title', 'movie_year']
-        self.user_fields = ['user_id', 'age', 'gender', 'occupation']
-        self.rating_fields = ['user_id', 'item_id', 'rating', 'timestamp']
-        self.map_fields = ['item_id', 'URI']
+        self.dataset_name = "MovieLens100k"
+
+        self.item_separator = "|"
+        self.user_separator = "|"
+        self.rating_separator = "\t"
+        self.item_fields = {
+            "movie id": "item_id::string",
+            "movie_title": "movie_title::string",
+            "movie_year": "movie_year::string",  # extracted from movie_title
+        }
+        self.map_fields = {"item_id": "item_id::string", "URI": "URI::string"}
+        self.rating_fields = {
+            "user id": "user_id::string",
+            "item id": "item_id::string",
+            "rating": "rating::number",
+            "timestamp": "timestamp::number",
+        }
+        self.user_fields = {
+            "user id": "user_id::string",
+            "age": "age::string",
+            "gender": "gender::string",
+            "occupation": "occupation::string",
+        }
 
     def load_item_data(self) -> pd.DataFrame():
         # Reading item file of MovieLens100k dataset
-        filename = os.path.join(self.input_path, 'u.item')
-        column_names = '''movie id | movie title | release date | video release date |
+        filename = os.path.join(self.input_path, "u.item")
+        column_names = """movie id | movie title | release date | video release date |
                     IMDb URL | unknown | Action | Adventure | Animation |
                     Children's | Comedy | Crime | Documentary | Drama | Fantasy |
                     Film-Noir | Horror | Musical | Mystery | Romance | Sci-Fi |
-                    Thriller | War | Western'''
-        column_names = [name.strip() for name in column_names.split('|')]
+                    Thriller | War | Western"""
+        column_names = [name.strip() for name in column_names.split("|")]
         df = pd.read_csv(
-            filename, header=None, sep=self.item_separator,
-            encoding='latin-1', names=column_names
+            filename,
+            header=None,
+            sep=self.item_separator,
+            encoding="latin-1",
+            names=column_names,
         )
 
         # Pre processing item
         # 1- Dropping unused fields
-        df.set_index('movie id')
-        df = df.drop(['video release date', 'unknown', 'IMDb URL'], axis=1)
+        df.set_index("movie id")
+        df = df.drop(["video release date", "unknown", "IMDb URL"], axis=1)
         df = df.dropna()
-        
+
         # 2- Extracting title and year
-        df['movie_title'] = df['movie title'].apply(self._extract_title)
-        df['movie_year'] = df['movie title'].apply(self._extract_year)
-        df['item_id'] = df['movie id']
-        
-        df = df[self.item_fields]
+        df["movie_title"] = df["movie title"].apply(self._extract_title)
+        df["movie_year"] = df["movie title"].apply(self._extract_year)
+
+        df = df.rename(self.item_fields, axis=1)
+        df = df[self.item_fields.values()]
 
         return df
-        
+
     def load_user_data(self) -> pd.DataFrame():
-        filename = os.path.join(self.input_path, 'u.user')
-        column_names = 'user id | age | gender | occupation | zip code'
-        column_names = [name.strip() for name in column_names.split('|')]
+        filename = os.path.join(self.input_path, "u.user")
+        column_names = "user id | age | gender | occupation | zip code"
+        column_names = [name.strip() for name in column_names.split("|")]
 
         df = pd.read_csv(
-            filename, header=None, sep=self.user_separator,
-            encoding='latin-1', names=column_names
+            filename,
+            header=None,
+            sep=self.user_separator,
+            encoding="latin-1",
+            names=column_names,
         )
-        # Removing zip code
-        df = df.drop(['zip code'], axis=1)
-        df.columns = self.user_fields
+        df = df.rename(self.user_fields, axis=1)
+        df = df[self.user_fields.values()]
 
         return df
 
-
     def load_rating_data(self) -> pd.DataFrame():
-        filename = os.path.join(self.input_path, 'u.data')
-        column_names = 'user id | item id | rating | timestamp'
-        column_names = [name.strip() for name in column_names.split('|')]
+        filename = os.path.join(self.input_path, "u.data")
+        column_names = "user id | item id | rating | timestamp"
+        column_names = [name.strip() for name in column_names.split("|")]
 
         df = pd.read_csv(
-            filename, header=None, sep=self.rating_separator,
-            encoding='latin-1', names=column_names
+            filename,
+            header=None,
+            sep=self.rating_separator,
+            encoding="latin-1",
+            names=column_names,
         )
-        # Will use all fields provided by the dataset
-        # renaming columns
-        df.columns = self.rating_fields
+        df = df.rename(self.rating_fields, axis=1)
+        df = df[self.rating_fields.values()]
 
         return df
-    
+
 
 class MovieLens1M(MovieLens):
     """
     Dataset class for data integration between DBpedia and MovieLens 1M
     """
+
     def __init__(self, input_path, output_path, n_workers=1):
         super().__init__(input_path, output_path, n_workers)
-        self.dataset_name = 'MovieLens1M'
-
-        self.item_separator = '::'
-        self.user_separator = '::'
-        self.rating_separator = '::'
+        self.dataset_name = "MovieLens1M"
+
+        self.item_separator = "::"
+        self.user_separator = "::"
+        self.rating_separator = "::"
+        self.item_fields = {
+            "MovieID": "item_id::string",
+            "movie_title": "movie_title::string",
+            "movie_year": "movie_year::string",  # extracted from movie_title
+        }
+        self.map_fields = {"item_id": "item_id::string", "URI": "URI::string"}
+        self.rating_fields = {
+            "UserID": "user_id::string",
+            "MovieID": "item_id::string",
+            "Rating": "rating::number",
+            "Timestamp": "timestamp::number",
+        }
+        self.user_fields = {
+            "UserID": "user_id::string",
+            "Gender": "gender::string",
+            "Age": "age::string",
+            "Occupation": "occupation::string",
+        }
 
-        self.item_fields = ['item_id', 'movie_title', 'movie_year']
-        self.user_fields = ['user_id', 'gender', 'age', 'occupation']
-        self.rating_fields = ['user_id', 'item_id', 'rating', 'timestamp']
-        self.map_fields = ['item_id', 'URI']
-    
     def load_item_data(self) -> pd.DataFrame():
         # Reading item file of MovieLens100k dataset
-        filename = os.path.join(self.input_path, 'movies.dat')
-        column_names = 'MovieID::Title::Genres'
-        column_names = [name.strip() for name in column_names.split('::')]
+        filename = os.path.join(self.input_path, "movies.dat")
+        column_names = "MovieID::Title::Genres"
+        column_names = [name.strip() for name in column_names.split("::")]
         df = pd.read_csv(
-            filename, header=None, sep=self.item_separator,
-            encoding='latin-1', names=column_names, engine='python'
+            filename,
+            header=None,
+            sep=self.item_separator,
+            encoding="latin-1",
+            names=column_names,
+            engine="python",
         )
 
-        # Pre processing item       
+        # Pre processing item
         # Extracting title and year
-        df['movie_title'] = df['Title'].apply(self._extract_title)
-        df['movie_year'] = df['Title'].apply(self._extract_year)
-        df['item_id'] = df['MovieID']
-        
-        df = df[self.item_fields]
+        df["movie_title"] = df["Title"].apply(self._extract_title)
+        df["movie_year"] = df["Title"].apply(self._extract_year)
+
+        df = df.rename(self.item_fields, axis=1)
+        df = df[self.item_fields.values()]
 
         return df
 
     def load_user_data(self) -> pd.DataFrame():
-        filename = os.path.join(self.input_path, 'users.dat')
-        column_names = 'UserID::Gender::Age::Occupation::Zip-code'
-        column_names = [name.strip() for name in column_names.split('::')]
+        filename = os.path.join(self.input_path, "users.dat")
+        column_names = "UserID::Gender::Age::Occupation::Zip-code"
+        column_names = [name.strip() for name in column_names.split("::")]
 
         df = pd.read_csv(
-            filename, header=None, sep=self.user_separator,
-            encoding='latin-1', names=column_names, engine='python'
+            filename,
+            header=None,
+            sep=self.user_separator,
+            encoding="latin-1",
+            names=column_names,
+            engine="python",
         )
-        # Removing zip code
-        df = df.drop('Zip-code', axis=1)
-        df.columns = self.user_fields
+
+        df = df.rename(self.user_fields, axis=1)
+        df = df[self.user_fields.values()]
 
         return df
 
     def load_rating_data(self) -> pd.DataFrame():
-        filename = os.path.join(self.input_path, 'ratings.dat')
-        column_names = 'UserID::MovieID::Rating::Timestamp'
-        column_names = [name.strip() for name in column_names.split('::')]
+        filename = os.path.join(self.input_path, "ratings.dat")
+        column_names = "UserID::MovieID::Rating::Timestamp"
+        column_names = [name.strip() for name in column_names.split("::")]
 
         df = pd.read_csv(
-            filename, header=None, sep=self.rating_separator,
-            encoding='latin-1', names=column_names, engine='python'
+            filename,
+            header=None,
+            sep=self.rating_separator,
+            encoding="latin-1",
+            names=column_names,
+            engine="python",
         )
-        # Will use all fields provided by the dataset
-        # renaming columns
-        df.columns = self.rating_fields
+        df = df.rename(self.rating_fields, axis=1)
+        df = df[self.rating_fields.values()]
 
         return df
-        
\ No newline at end of file
diff --git a/datasets/lastfm/processed/enriched.csv b/datasets/lastfm/processed/enriched.csv
index 6b6dcc1..2fe92f9 100644
--- a/datasets/lastfm/processed/enriched.csv
+++ b/datasets/lastfm/processed/enriched.csv
@@ -1,4 +1,4 @@
-item_id,abstract,bandMember,genre,associatedMusicalArtist,awards,recordLabel,associatedBand,origin
+item_id::string,abstract::string,bandMember::string_list,genre:string_list,associatedMusicalArtist::string_list,awards::string_list,recordLabel::string_list,associatedBand::string_list,origin::string_list
 1,"Malice Mizer (stylized as MALICE MIZER) was a Japanese visual kei rock band active from August 1992 to December 2001. Formed by guitarists Mana and Közi, their earlier music and themes were characterized by their strong French and classical influences, later moving away from deliberate French romanticism and incorporating Gothic aspects after several difficulties befell the band. Their official fan club is named ""Ma chérie"". Malice Mizer was as famous for their live shows as they were for their music, featuring lavish historical costumes and stage sets, short silent theater pieces preluding various songs, and even a particularly notable instance of the vocalist descending to Earth (the stage) as a fallen angel, only to ascend again at the end of the concert. Throughout their history, the band has gone through several different lineups and three drastic image changes. At their peak, they were considered one of ""the big four of visual kei"" alongside La'cryma Christi, Fanatic Crisis and Shazna. On December 11, 2001, it was announced that Malice Mizer would go on an indefinite hiatus. Mana, Közi and bassist Yu~ki have performed together several times since 2010.",http://dbpedia.org/resource/Közi::http://dbpedia.org/resource/Mana_(Japanese_musician),http://dbpedia.org/resource/Art_rock::http://dbpedia.org/resource/Gothic_rock::http://dbpedia.org/resource/Neoclassical_darkwave::http://dbpedia.org/resource/Synthpop,http://dbpedia.org/resource/Eve_of_Destiny::http://dbpedia.org/resource/Moi_dix_Mois::http://dbpedia.org/resource/Nil_(band)::http://dbpedia.org/resource/Zigzo,,http://dbpedia.org/resource/Midi:Nette::http://dbpedia.org/resource/Nippon_Columbia,http://dbpedia.org/resource/Eve_of_Destiny::http://dbpedia.org/resource/Moi_dix_Mois::http://dbpedia.org/resource/Nil_(band)::http://dbpedia.org/resource/Zigzo,http://dbpedia.org/resource/Japan
 2,"Diary of Dreams is a German darkwave band. The lead singer and founding member Adrian Hates has produced most of the albums by himself or with minimal help from others. He rarely uses a full band, except when on tour.",http://dbpedia.org/resource/Adrian_Hates,http://dbpedia.org/resource/Darkwave::http://dbpedia.org/resource/Electro-industrial,http://dbpedia.org/resource/Diorama_(band),,http://dbpedia.org/resource/Accession_Records,http://dbpedia.org/resource/Diorama_(band),http://dbpedia.org/resource/Germany
 3,Carpathian Forest is a Norwegian black metal band formed by Nattefrost and Nordavind in 1992.,http://dbpedia.org/resource/Anders_Kobro::http://dbpedia.org/resource/Nattefrost::http://dbpedia.org/resource/Vrangsinn,http://dbpedia.org/resource/Black_'n'_roll::http://dbpedia.org/resource/Black_metal,http://dbpedia.org/resource/Aura_Noir::http://dbpedia.org/resource/Blood_Red_Throne::http://dbpedia.org/resource/Emperor_(band)::http://dbpedia.org/resource/Gorgoroth::http://dbpedia.org/resource/Green_Carnation::http://dbpedia.org/resource/In_the_Woods...::http://dbpedia.org/resource/Kvelertak::http://dbpedia.org/resource/Solefald::http://dbpedia.org/resource/Taake::http://dbpedia.org/resource/The_3rd_Attempt,,http://dbpedia.org/resource/Avantgarde_Music::http://dbpedia.org/resource/Peaceville_Records::http://dbpedia.org/resource/Season_of_Mist,http://dbpedia.org/resource/Aura_Noir::http://dbpedia.org/resource/Blood_Red_Throne::http://dbpedia.org/resource/Emperor_(band)::http://dbpedia.org/resource/Gorgoroth::http://dbpedia.org/resource/Green_Carnation::http://dbpedia.org/resource/In_the_Woods...::http://dbpedia.org/resource/Kvelertak::http://dbpedia.org/resource/Solefald::http://dbpedia.org/resource/Taake::http://dbpedia.org/resource/The_3rd_Attempt,"Sandnes, Norway"
diff --git a/datasets/ml-100k/processed/enriched.csv b/datasets/ml-100k/processed/enriched.csv
index 77aa974..6e6235e 100644
--- a/datasets/ml-100k/processed/enriched.csv
+++ b/datasets/ml-100k/processed/enriched.csv
@@ -1,4 +1,4 @@
-item_id,abstract,producer,distributor,writer,cinematography,subject,starring,director
+item_id::string,abstract::string,producer::string_list,distributor::string_list,writer::string_list,cinematography::string_list,subject::string_list,starring::string_list,director::string_list
 1,,http://dbpedia.org/resource/Bonnie_Arnold::http://dbpedia.org/resource/Ralph_Guggenheim,http://dbpedia.org/resource/Buena_Vista_Pictures_Distribution,http://dbpedia.org/resource/Alec_Sokolow::http://dbpedia.org/resource/Andrew_Stanton::http://dbpedia.org/resource/Joe_Ranft::http://dbpedia.org/resource/Joel_Cohen_(writer)::http://dbpedia.org/resource/John_Lasseter::http://dbpedia.org/resource/Joss_Whedon::http://dbpedia.org/resource/Pete_Docter,,http://dbpedia.org/resource/Category:1990s_American_animated_films::http://dbpedia.org/resource/Category:1990s_English-language_films::http://dbpedia.org/resource/Category:1990s_buddy_comedy_films::http://dbpedia.org/resource/Category:1990s_children's_animated_films::http://dbpedia.org/resource/Category:1990s_fantasy_comedy_films::http://dbpedia.org/resource/Category:1995_animated_films::http://dbpedia.org/resource/Category:1995_children's_films::http://dbpedia.org/resource/Category:1995_comedy_films::http://dbpedia.org/resource/Category:1995_computer-animated_films::http://dbpedia.org/resource/Category:1995_directorial_debut_films::http://dbpedia.org/resource/Category:1995_films::http://dbpedia.org/resource/Category:3D_re-releases::http://dbpedia.org/resource/Category:American_3D_films::http://dbpedia.org/resource/Category:American_adventure_comedy_films::http://dbpedia.org/resource/Category:American_animated_feature_films::http://dbpedia.org/resource/Category:American_buddy_comedy_films::http://dbpedia.org/resource/Category:American_children's_animated_adventure_films::http://dbpedia.org/resource/Category:American_children's_animated_comedy_films::http://dbpedia.org/resource/Category:American_children's_animated_fantasy_films::http://dbpedia.org/resource/Category:American_computer-animated_films::http://dbpedia.org/resource/Category:Animated_buddy_films::http://dbpedia.org/resource/Category:Animated_films_about_friendship::http://dbpedia.org/resource/Category:Best_Animated_Feature_Annie_Award_winners::http://dbpedia.org/resource/Category:Films_about_dolls::http://dbpedia.org/resource/Category:Films_about_toys::http://dbpedia.org/resource/Category:Films_adapted_into_television_shows::http://dbpedia.org/resource/Category:Films_directed_by_John_Lasseter::http://dbpedia.org/resource/Category:Films_produced_by_Bonnie_Arnold::http://dbpedia.org/resource/Category:Films_scored_by_Randy_Newman::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Alec_Sokolow::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Andrew_Stanton::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Joe_Ranft::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Joel_Cohen::http://dbpedia.org/resource/Category:Films_with_screenplays_by_John_Lasseter::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Joss_Whedon::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Pete_Docter::http://dbpedia.org/resource/Category:Pixar_animated_films::http://dbpedia.org/resource/Category:Sentient_toys_in_fiction::http://dbpedia.org/resource/Category:Toy_Story::http://dbpedia.org/resource/Category:United_States_National_Film_Registry_films::http://dbpedia.org/resource/Category:Walt_Disney_Pictures_films,http://dbpedia.org/resource/Annie_Potts::http://dbpedia.org/resource/Don_Rickles::http://dbpedia.org/resource/Erik_von_Detten::http://dbpedia.org/resource/Jim_Varney::http://dbpedia.org/resource/John_Morris_(actor)::http://dbpedia.org/resource/John_Ratzenberger::http://dbpedia.org/resource/Tim_Allen::http://dbpedia.org/resource/Tom_Hanks::http://dbpedia.org/resource/Wallace_Shawn,http://dbpedia.org/resource/John_Lasseter
 7,"12 Monkeys is a 1995 American science fiction film directed by Terry Gilliam, inspired by Chris Marker's 1962 short film La Jetée, starring Bruce Willis, Madeleine Stowe, and Brad Pitt, with Christopher Plummer and David Morse in supporting roles. After Universal Studios acquired the rights to remake La Jetée as a full-length film, David and Janet Peoples were hired to write the script. Under Gilliam's direction, Universal granted the filmmakers a $29.5 million budget, and filming lasted from February to May 1995. The film was shot mostly in Philadelphia and Baltimore, where the story was set. The film was released to critical praise and grossed $168.8 million worldwide. Pitt was nominated for the Academy Award for Best Supporting Actor, and he won a Golden Globe Award for his performance. The film also won and was nominated for various categories at the Saturn Awards.",http://dbpedia.org/resource/Charles_Roven,http://dbpedia.org/resource/Universal_Pictures,http://dbpedia.org/resource/David_Peoples::http://dbpedia.org/resource/Janet_Peoples,http://dbpedia.org/resource/Roger_Pratt_(cinematographer),http://dbpedia.org/resource/Category:1990s_American_films::http://dbpedia.org/resource/Category:1990s_English-language_films::http://dbpedia.org/resource/Category:1990s_dystopian_films::http://dbpedia.org/resource/Category:1995_films::http://dbpedia.org/resource/Category:1995_science_fiction_films::http://dbpedia.org/resource/Category:American_dystopian_films::http://dbpedia.org/resource/Category:American_neo-noir_films::http://dbpedia.org/resource/Category:American_nonlinear_narrative_films::http://dbpedia.org/resource/Category:American_post-apocalyptic_films::http://dbpedia.org/resource/Category:American_remakes_of_French_films::http://dbpedia.org/resource/Category:American_science_fiction_films::http://dbpedia.org/resource/Category:Apocalyptic_films::http://dbpedia.org/resource/Category:Atlas_Entertainment_films::http://dbpedia.org/resource/Category:Eco-terrorism_in_fiction::http://dbpedia.org/resource/Category:Environmental_films::http://dbpedia.org/resource/Category:Features_based_on_short_films::http://dbpedia.org/resource/Category:Films_about_animal_rights::http://dbpedia.org/resource/Category:Films_about_time_travel::http://dbpedia.org/resource/Category:Films_about_viral_outbreaks::http://dbpedia.org/resource/Category:Films_directed_by_Terry_Gilliam::http://dbpedia.org/resource/Category:Films_featuring_a_Best_Supporting_Actor_Golden_Globe_winning_performance::http://dbpedia.org/resource/Category:Films_set_in_1990::http://dbpedia.org/resource/Category:Films_set_in_1996::http://dbpedia.org/resource/Category:Films_set_in_2035::http://dbpedia.org/resource/Category:Films_set_in_Baltimore::http://dbpedia.org/resource/Category:Films_set_in_Philadelphia::http://dbpedia.org/resource/Category:Films_set_in_a_movie_theatre::http://dbpedia.org/resource/Category:Films_set_in_psychiatric_hospitals::http://dbpedia.org/resource/Category:Films_shot_in_Baltimore::http://dbpedia.org/resource/Category:Films_shot_in_New_Jersey::http://dbpedia.org/resource/Category:Films_shot_in_Philadelphia::http://dbpedia.org/resource/Category:Films_with_screenplays_by_David_Peoples::http://dbpedia.org/resource/Category:Works_subject_to_a_lawsuit,http://dbpedia.org/resource/Brad_Pitt::http://dbpedia.org/resource/Bruce_Willis::http://dbpedia.org/resource/Christopher_Plummer::http://dbpedia.org/resource/Madeleine_Stowe,http://dbpedia.org/resource/Terry_Gilliam
 9,"Dead Man Walking is a 1995 American crime drama film starring Susan Sarandon and Sean Penn, and co-produced and directed by Tim Robbins, who adapted the screenplay from the 1993 non-fiction book of the same name. Sister Helen Prejean (Sarandon) establishes a special relationship with Matthew Poncelet (Penn), a character based on convicted murderers Elmo Patrick Sonnier and Robert Lee Willie. He is a prisoner on death row in Louisiana, and she visits him as his spiritual adviser after corresponding with him. The film was a critical and commercial success; it grossed $83 million on a budget of $11 million and received praise for the performances of Penn and Sarandon, as well as Robbins' direction. Sarandon's performance won her the Academy Award for Best Actress, while Robbins, Penn and Bruce Springsteen were nominated for Best Director, Best Actor and Best Original Song, respectively, for the single ""Dead Man Walkin'"".",http://dbpedia.org/resource/Jon_Kilik::http://dbpedia.org/resource/Tim_Robbins,http://dbpedia.org/resource/Gramercy_Pictures,http://dbpedia.org/resource/Tim_Robbins,http://dbpedia.org/resource/Roger_Deakins,http://dbpedia.org/resource/Category:1990s_American_films::http://dbpedia.org/resource/Category:1990s_English-language_films::http://dbpedia.org/resource/Category:1990s_legal_films::http://dbpedia.org/resource/Category:1995_crime_drama_films::http://dbpedia.org/resource/Category:1995_films::http://dbpedia.org/resource/Category:American_crime_drama_films::http://dbpedia.org/resource/Category:Films_about_Catholic_nuns::http://dbpedia.org/resource/Category:Films_about_Christianity::http://dbpedia.org/resource/Category:Films_about_capital_punishment::http://dbpedia.org/resource/Category:Films_based_on_non-fiction_books::http://dbpedia.org/resource/Category:Films_directed_by_Tim_Robbins::http://dbpedia.org/resource/Category:Films_featuring_a_Best_Actress_Academy_Award-winning_performance::http://dbpedia.org/resource/Category:Films_produced_by_Jon_Kilik::http://dbpedia.org/resource/Category:Films_set_in_Louisiana::http://dbpedia.org/resource/Category:Films_shot_in_New_Orleans::http://dbpedia.org/resource/Category:Gramercy_Pictures_films::http://dbpedia.org/resource/Category:PolyGram_Filmed_Entertainment_films::http://dbpedia.org/resource/Category:Working_Title_Films_films,http://dbpedia.org/resource/R._Lee_Ermey::http://dbpedia.org/resource/Raymond_J._Barry::http://dbpedia.org/resource/Robert_Prosky::http://dbpedia.org/resource/Scott_Wilson_(actor)::http://dbpedia.org/resource/Sean_Penn::http://dbpedia.org/resource/Susan_Sarandon,http://dbpedia.org/resource/Tim_Robbins
diff --git a/datasets/ml-100k/processed/map.csv b/datasets/ml-100k/processed/map.csv
index 4929440..7150b21 100644
--- a/datasets/ml-100k/processed/map.csv
+++ b/datasets/ml-100k/processed/map.csv
@@ -1,4 +1,4 @@
-item_id,URI
+item_id::string,URI::string
 1,http://dbpedia.org/resource/Toy_Story
 2,http://dbpedia.org/resource/GoldenEye
 3,http://dbpedia.org/resource/Four_Rooms
diff --git a/datasets/ml-1m/processed/enriched.csv b/datasets/ml-1m/processed/enriched.csv
index cf2e2a6..24dd2e2 100644
--- a/datasets/ml-1m/processed/enriched.csv
+++ b/datasets/ml-1m/processed/enriched.csv
@@ -1,4 +1,4 @@
-item_id,abstract,producer,distributor,writer,cinematography,subject,starring,director
+item_id::string,abstract::string,producer::string_list,distributor::string_list,writer::string_list,cinematography::string_list,subject:string_list,starring::string_list,director::string_list
 1,,http://dbpedia.org/resource/Bonnie_Arnold::http://dbpedia.org/resource/Ralph_Guggenheim,http://dbpedia.org/resource/Buena_Vista_Pictures_Distribution,http://dbpedia.org/resource/Alec_Sokolow::http://dbpedia.org/resource/Andrew_Stanton::http://dbpedia.org/resource/Joe_Ranft::http://dbpedia.org/resource/Joel_Cohen_(writer)::http://dbpedia.org/resource/John_Lasseter::http://dbpedia.org/resource/Joss_Whedon::http://dbpedia.org/resource/Pete_Docter,,http://dbpedia.org/resource/Category:1990s_American_animated_films::http://dbpedia.org/resource/Category:1990s_English-language_films::http://dbpedia.org/resource/Category:1990s_buddy_comedy_films::http://dbpedia.org/resource/Category:1990s_children's_animated_films::http://dbpedia.org/resource/Category:1990s_fantasy_comedy_films::http://dbpedia.org/resource/Category:1995_animated_films::http://dbpedia.org/resource/Category:1995_children's_films::http://dbpedia.org/resource/Category:1995_comedy_films::http://dbpedia.org/resource/Category:1995_computer-animated_films::http://dbpedia.org/resource/Category:1995_directorial_debut_films::http://dbpedia.org/resource/Category:1995_films::http://dbpedia.org/resource/Category:3D_re-releases::http://dbpedia.org/resource/Category:American_3D_films::http://dbpedia.org/resource/Category:American_adventure_comedy_films::http://dbpedia.org/resource/Category:American_animated_feature_films::http://dbpedia.org/resource/Category:American_buddy_comedy_films::http://dbpedia.org/resource/Category:American_children's_animated_adventure_films::http://dbpedia.org/resource/Category:American_children's_animated_comedy_films::http://dbpedia.org/resource/Category:American_children's_animated_fantasy_films::http://dbpedia.org/resource/Category:American_computer-animated_films::http://dbpedia.org/resource/Category:Animated_buddy_films::http://dbpedia.org/resource/Category:Animated_films_about_friendship::http://dbpedia.org/resource/Category:Best_Animated_Feature_Annie_Award_winners::http://dbpedia.org/resource/Category:Films_about_dolls::http://dbpedia.org/resource/Category:Films_about_toys::http://dbpedia.org/resource/Category:Films_adapted_into_television_shows::http://dbpedia.org/resource/Category:Films_directed_by_John_Lasseter::http://dbpedia.org/resource/Category:Films_produced_by_Bonnie_Arnold::http://dbpedia.org/resource/Category:Films_scored_by_Randy_Newman::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Alec_Sokolow::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Andrew_Stanton::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Joe_Ranft::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Joel_Cohen::http://dbpedia.org/resource/Category:Films_with_screenplays_by_John_Lasseter::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Joss_Whedon::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Pete_Docter::http://dbpedia.org/resource/Category:Pixar_animated_films::http://dbpedia.org/resource/Category:Sentient_toys_in_fiction::http://dbpedia.org/resource/Category:Toy_Story::http://dbpedia.org/resource/Category:United_States_National_Film_Registry_films::http://dbpedia.org/resource/Category:Walt_Disney_Pictures_films,http://dbpedia.org/resource/Annie_Potts::http://dbpedia.org/resource/Don_Rickles::http://dbpedia.org/resource/Erik_von_Detten::http://dbpedia.org/resource/Jim_Varney::http://dbpedia.org/resource/John_Morris_(actor)::http://dbpedia.org/resource/John_Ratzenberger::http://dbpedia.org/resource/Tim_Allen::http://dbpedia.org/resource/Tom_Hanks::http://dbpedia.org/resource/Wallace_Shawn,http://dbpedia.org/resource/John_Lasseter
 6,"Heat is a 1995 American crime drama film written and directed by Michael Mann. It features an ensemble cast led by Al Pacino and Robert De Niro, with Tom Sizemore, Jon Voight and Val Kilmer in supporting roles. The film follows the conflict between an LAPD detective, played by Pacino, and a career thief, played by De Niro, while also depicting its effect on their professional relationships and personal lives. Mann wrote the original script for Heat in 1979, basing it on Chicago police officer Chuck Adamson's pursuit of criminal Neil McCauley, after whom De Niro's character is named. The script was first used for a television pilot developed by Mann, which became the 1989 television film L.A. Takedown after the pilot did not receive a series order. In 1994, Mann revisited the script to turn it into a feature film, co-producing the project with Art Linson. The film marks De Niro and Pacino's first on-screen appearance together following a period of acclaimed performances from both. Due to their esteemed reputations, promotion centered on their involvement. Heat was released by Warner Bros. Pictures on December 15, 1995, to critical and commercial success. It grossed $187 million on a $60 million budget, while receiving praise for Mann's direction and screenplay, action sequences, sound mixing, and the performances of Pacino and De Niro. Although it did not receive any major award nominations, Heat is regarded as one of the most influential films of its genre and has inspired several other works. A planned sequel was announced in July 2022.",http://dbpedia.org/resource/Art_Linson::http://dbpedia.org/resource/Michael_Mann,http://dbpedia.org/resource/Warner_Bros._Pictures,http://dbpedia.org/resource/Michael_Mann,http://dbpedia.org/resource/Dante_Spinotti,"http://dbpedia.org/resource/Category:1990s_American_films::http://dbpedia.org/resource/Category:1990s_English-language_films::http://dbpedia.org/resource/Category:1990s_crime_action_films::http://dbpedia.org/resource/Category:1990s_heist_films::http://dbpedia.org/resource/Category:1990s_police_films::http://dbpedia.org/resource/Category:1990s_thriller_drama_films::http://dbpedia.org/resource/Category:1995_action_drama_films::http://dbpedia.org/resource/Category:1995_action_films::http://dbpedia.org/resource/Category:1995_crime_drama_films::http://dbpedia.org/resource/Category:1995_crime_thriller_films::http://dbpedia.org/resource/Category:1995_drama_films::http://dbpedia.org/resource/Category:1995_films::http://dbpedia.org/resource/Category:American_crime_drama_films::http://dbpedia.org/resource/Category:American_crime_thriller_films::http://dbpedia.org/resource/Category:American_gangster_films::http://dbpedia.org/resource/Category:American_heist_films::http://dbpedia.org/resource/Category:American_neo-noir_films::http://dbpedia.org/resource/Category:American_police_detective_films::http://dbpedia.org/resource/Category:American_thriller_drama_films::http://dbpedia.org/resource/Category:Films_about_bank_robbery::http://dbpedia.org/resource/Category:Films_about_organized_crime_in_the_United_States::http://dbpedia.org/resource/Category:Films_about_the_Los_Angeles_Police_Department::http://dbpedia.org/resource/Category:Films_directed_by_Michael_Mann::http://dbpedia.org/resource/Category:Films_produced_by_Art_Linson::http://dbpedia.org/resource/Category:Films_produced_by_Michael_Mann::http://dbpedia.org/resource/Category:Films_scored_by_Elliot_Goldenthal::http://dbpedia.org/resource/Category:Films_set_in_Koreatown,_Los_Angeles::http://dbpedia.org/resource/Category:Films_set_in_Los_Angeles::http://dbpedia.org/resource/Category:Films_shot_in_California::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Michael_Mann::http://dbpedia.org/resource/Category:Regency_Enterprises_films::http://dbpedia.org/resource/Category:Warner_Bros._films",http://dbpedia.org/resource/Al_Pacino::http://dbpedia.org/resource/Amy_Brenneman::http://dbpedia.org/resource/Ashley_Judd::http://dbpedia.org/resource/Diane_Venora::http://dbpedia.org/resource/Jon_Voight::http://dbpedia.org/resource/Mykelti_Williamson::http://dbpedia.org/resource/Robert_De_Niro::http://dbpedia.org/resource/Ted_Levine::http://dbpedia.org/resource/Tom_Sizemore::http://dbpedia.org/resource/Val_Kilmer::http://dbpedia.org/resource/Wes_Studi,http://dbpedia.org/resource/Michael_Mann
 10,"GoldenEye is a 1995 spy film, the seventeenth in the James Bond series produced by Eon Productions, and the first to star Pierce Brosnan as the fictional MI6 agent James Bond. Directed by Martin Campbell, it was the first in the series not to utilize any story elements from the works of novelist Ian Fleming. It was also the first James Bond film not produced by Albert R. Broccoli, following his stepping down from Eon Productions and replacement by his daughter, Barbara Broccoli (along with Michael G. Wilson, although Albert was still involved as a consultant producer; it was his final film project before his death in 1996). The story was conceived and written by Michael France, with later collaboration by other writers. In the film, Bond fights to prevent a rogue ex-MI6 agent (Sean Bean) from using a satellite weapon against London to cause a global financial meltdown. The film was released after a six-year hiatus in the series caused by legal disputes, during which Timothy Dalton’s contract for the role of James Bond expired and he was replaced by Brosnan. M was also recast, with actress Judi Dench becoming the first woman to portray the character, replacing Robert Brown. The role of Miss Moneypenny was also recast, with Caroline Bliss being replaced by Samantha Bond; Desmond Llewelyn was the only actor to reprise his role, as Q. It was the first Bond film made after the dissolution of the Soviet Union and the end of the Cold War, which provided a background for the plot. Principal photography for GoldenEye took place in the UK, Russia, Monte Carlo and Puerto Rico; it was the inaugural film production to be shot at Leavesden Studios. The first Bond film to use computer-generated imagery (CGI), GoldenEye was also the final film of special effects supervisor Derek Meddings's career, and was dedicated to his memory. The film accumulated a worldwide gross of over US$350 million, considerably better than Dalton's films, without taking inflation into account. It received positive reviews, with critics viewing Brosnan as a definite improvement over his predecessor. It also received award nominations for ""Best Special Visual Effects"" and ""Best Sound"" from the British Academy of Film and Television Arts.",http://dbpedia.org/resource/Barbara_Broccoli::http://dbpedia.org/resource/Michael_G._Wilson,http://dbpedia.org/resource/Metro-Goldwyn-Mayer::http://dbpedia.org/resource/United_International_Pictures,http://dbpedia.org/resource/Bruce_Feirstein::http://dbpedia.org/resource/Jeffrey_Caine::http://dbpedia.org/resource/Michael_France,http://dbpedia.org/resource/Phil_Méheux,"http://dbpedia.org/resource/Category:1990s_American_films::http://dbpedia.org/resource/Category:1990s_British_films::http://dbpedia.org/resource/Category:1990s_action_adventure_films::http://dbpedia.org/resource/Category:1990s_adventure_thriller_films::http://dbpedia.org/resource/Category:1990s_spy_thriller_films::http://dbpedia.org/resource/Category:1995_action_thriller_films::http://dbpedia.org/resource/Category:1995_films::http://dbpedia.org/resource/Category:American_action_adventure_films::http://dbpedia.org/resource/Category:American_action_thriller_films::http://dbpedia.org/resource/Category:American_adventure_thriller_films::http://dbpedia.org/resource/Category:American_films_about_revenge::http://dbpedia.org/resource/Category:American_sequel_films::http://dbpedia.org/resource/Category:American_spy_thriller_films::http://dbpedia.org/resource/Category:British_films_about_revenge::http://dbpedia.org/resource/Category:British_sequel_films::http://dbpedia.org/resource/Category:Eon_Productions_films::http://dbpedia.org/resource/Category:Films_about_computing::http://dbpedia.org/resource/Category:Films_about_nuclear_war_and_weapons::http://dbpedia.org/resource/Category:Films_about_space_programs::http://dbpedia.org/resource/Category:Films_about_terrorism_in_Europe::http://dbpedia.org/resource/Category:Films_adapted_into_comics::http://dbpedia.org/resource/Category:Films_directed_by_Martin_Campbell::http://dbpedia.org/resource/Category:Films_produced_by_Barbara_Broccoli::http://dbpedia.org/resource/Category:Films_produced_by_Michael_G._Wilson::http://dbpedia.org/resource/Category:Films_scored_by_Éric_Serra::http://dbpedia.org/resource/Category:Films_set_in_1986::http://dbpedia.org/resource/Category:Films_set_in_1995::http://dbpedia.org/resource/Category:Films_set_in_Cuba::http://dbpedia.org/resource/Category:Films_set_in_Monaco::http://dbpedia.org/resource/Category:Films_set_in_Saint_Petersburg::http://dbpedia.org/resource/Category:Films_set_in_Siberia::http://dbpedia.org/resource/Category:Films_set_in_the_Soviet_Union::http://dbpedia.org/resource/Category:Films_shot_at_Warner_Bros._Studios,_Leavesden::http://dbpedia.org/resource/Category:Films_shot_in_Cambridgeshire::http://dbpedia.org/resource/Category:Films_shot_in_France::http://dbpedia.org/resource/Category:Films_shot_in_Hertfordshire::http://dbpedia.org/resource/Category:Films_shot_in_London::http://dbpedia.org/resource/Category:Films_shot_in_Monaco::http://dbpedia.org/resource/Category:Films_shot_in_Puerto_Rico::http://dbpedia.org/resource/Category:Films_shot_in_Saint_Petersburg::http://dbpedia.org/resource/Category:Films_shot_in_Surrey::http://dbpedia.org/resource/Category:Films_shot_in_Switzerland::http://dbpedia.org/resource/Category:Films_shot_in_Wiltshire::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Bruce_Feirstein::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Jeffrey_Caine::http://dbpedia.org/resource/Category:Films_with_screenplays_by_Michael_France::http://dbpedia.org/resource/Category:GoldenEye::http://dbpedia.org/resource/Category:James_Bond_films::http://dbpedia.org/resource/Category:Metro-Goldwyn-Mayer_films::http://dbpedia.org/resource/Category:United_Artists_films",http://dbpedia.org/resource/Famke_Janssen::http://dbpedia.org/resource/Izabella_Scorupco::http://dbpedia.org/resource/Joe_Don_Baker::http://dbpedia.org/resource/Pierce_Brosnan::http://dbpedia.org/resource/Sean_Bean,http://dbpedia.org/resource/Martin_Campbell
diff --git a/datasets/ml-1m/processed/map.csv b/datasets/ml-1m/processed/map.csv
index 42f1e39..996a914 100644
--- a/datasets/ml-1m/processed/map.csv
+++ b/datasets/ml-1m/processed/map.csv
@@ -1,4 +1,4 @@
-item_id,URI
+item_id::string,URI::string
 1,http://dbpedia.org/resource/Toy_Story
 2,http://dbpedia.org/resource/Jumanji
 3,http://dbpedia.org/resource/Grumpier_Old_Men
diff --git a/docs/source/getting_started/config.md b/docs/source/getting_started/config.md
index ca727f5..f123761 100644
--- a/docs/source/getting_started/config.md
+++ b/docs/source/getting_started/config.md
@@ -2,26 +2,27 @@
 
 The directive `experiment` configures the experimetn pipeline that includes:
 
-*  `dataset` configuration
-*  `preprocess` methods
-*  `split` method
-*  `models` to be evaluated
-*  `evaluation` configuration
-*  `report` to summarize experiment results
+- `dataset` configuration
+- `preprocess` methods
+- `split` method
+- `models` to be evaluated
+- `evaluation` configuration
+- `report` to summarize experiment results
 
 Example:
+
 ```yaml
 experiment:
-  dataset: 
+  dataset:
     name: ml-100k
     item:
-      path: datasets/ml-100k/processed/item.csv 
-      extra_features: [movie_year, movie_title] 
-    user: 
-      path: datasets/ml-100k/processed/user.csv 
-      extra_features: [gender, occupation] 
-    ratings: 
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/item.csv
+      extra_features: [movie_year, movie_title]
+    user:
+      path: datasets/ml-100k/processed/user.csv
+      extra_features: [gender, occupation]
+    ratings:
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
@@ -47,8 +48,7 @@ experiment:
     test:
       method: k_fold
       k: 2
-      level: 'user'
-
+      level: "user"
 
   models:
     - name: deepwalk_based
@@ -71,15 +71,16 @@ experiment:
         n_walks: 50
         embedding_size: 64
         epochs: 1
-  
+
   evaluation:
     k: 5
     relevance_threshold: 3
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/run1.csv'
+    file: "experiment_results/ml100k_enriched/run1.csv"
 ```
+
 The next session goes through all details of each directive.
 
 ## dataset
@@ -88,51 +89,40 @@ In this section, we can define the dataset name and provide details about items,
 
 ```yaml
 experiment:
-  dataset: 
+  dataset:
     name: dataset_name
     item:
-      path: /path/to/item.csv 
-      extra_features: [feat1_name, feat2_name, ...] 
-    user: 
-      path: path/to/user.csv 
-      extra_features: [feat1_name, feat2_name, ...] 
-    ratings: 
-      path: path/to/rating.csv 
+      path: /path/to/item.csv
+      extra_features: [feat1_name, feat2_name, ...]
+    user:
+      path: path/to/user.csv
+      extra_features: [feat1_name, feat2_name, ...]
+    ratings:
+      path: path/to/rating.csv
       timestamp: True|False
     enrich:
       map_path: path/to/map.csv
       enrich_path: path/to/enriched.csv
       remove_unmatched: True|False
-      properties: 
-        # list of enriching properties 
-        - type: property1_name
-          grouped: True|False
-          sep: separator
-        - type: property1_name
-          grouped: True|False
-          sep: seprator
+      properties: [property1_name, property2_name]
 ```
 
 Let's break down the main directives for the dataset:
 
 - `item`: specifies the item info to be added to the network. (mandatory)
-  - `path`: filepath of the standardized  **item.csv**. (mandatory)
+  - `path`: filepath of the standardized **item.csv**. (mandatory)
   - `extra_features`: For default, the only column to be added is the `item_id`. With a list of column names the user can specify additional features to be added as property node. (optional)
 - `user`: specifies the user info. (mandatory)
-  - `path`: filepath of the standardized  **user.csv**. (mandatory)
+  - `path`: filepath of the standardized **user.csv**. (mandatory)
   - `extra_features`: For default, the only column to be added is the `item_id`. With a list of column names the user can specify additional features to be added as property node. (optional)
 - `ratings`: specifies the ratings info. (mandatory)
-  - `path`: filepath of the standardized  `ratings.csv`. (mandatory)
+  - `path`: filepath of the standardized `ratings.csv`. (mandatory)
   - `timestamp`: boolean that indicates if the column `timestamp` is present.
 - `enrich`: specifies the enriched info. (mandatory)
-  - `map_path`: filepath of the standardized  `map.csv`. (mandatory)
-  - `enrich_path`: filepath of the standardized  `user.csv`. (mandatory)
+  - `map_path`: filepath of the standardized `map.csv`. (mandatory)
+  - `enrich_path`: filepath of the standardized `user.csv`. (mandatory)
   - `remove_unmatched`: boolean to specify if nodes unmatched with DBpedia should be removed. (mandatory)
   - `properties`: list of properties to enrich the dataset (mandatory)
-    - `type`: column name (type) of the property (mandatory)
-    - `grouped`: boolean that indicates if the property was grouped and concatenated into a single string.  Used for multiples property values of the same property type for a given resource. (mandatory)
-    - `sep`: separator used to concatenate a list of property values. (optional)
-
 
 ## preprocess
 
@@ -142,38 +132,38 @@ In the .yaml file, the directive **preprocess** is used to define the list of pr
 experiment:
   # ...
   preprocess:
-    - method: method1_name  
+    - method: method1_name
       parameters:
-        parameter_1: 3  
-        parameter_2: val 
+        parameter_1: 3
+        parameter_2: val
     - method: method2_name
-      parameters: 
+      parameters:
         parameter_1: 4
 ```
 
 Where,
+
 - `preprocess`: specifies a list of pre-processing methods. (optional)
   - `method`: method name (mandatory)
   - `parameters`: method parameters in the format `parameter_name: parameter_value`
 
 The current supported pre-processing methods can be found in [support documentation](./support.md).
 
-
 ### binarize parameters
 
-In the *.yaml* file, the method name is `binarize` and the only parameter is a `threshold` number. Example:
+In the _.yaml_ file, the method name is `binarize` and the only parameter is a `threshold` number. Example:
 
 ```yaml
 experiment:
   preprocess:
     - method: binarize
-      parameters: 
+      parameters:
         threshold: 4
 ```
 
 ### filter_kcore parameters
 
-In the *.yaml* file, the method name is `filter_kcore`, and the parameters are `k`, the number of `iterations` and the `target` type of node (user or item). Example:
+In the _.yaml_ file, the method name is `filter_kcore`, and the parameters are `k`, the number of `iterations` and the `target` type of node (user or item). Example:
 
 ```yaml
 experiment:
@@ -196,20 +186,21 @@ experiment:
   split:
     seed: 42
     test:
-      method: method1_name 
+      method: method1_name
       parameter1_name: 0.2
       parameter2_name: value
     validation:
-      method: method2_name 
+      method: method2_name
       parameter1_name: value_2
       parameter2_name: 100
 ```
 
 Where,
+
 - `split`: specifies the splitting method used. (mandatory)
-  - `seed`: random seed value for reproducibility 
+  - `seed`: random seed value for reproducibility
   - `test`: directive for the test split
-  - `validation`: directive for the validation split 
+  - `validation`: directive for the validation split
   - `method`: splitting method name (mandatory)
     - Parameters as a dictionary where the key is the splitting method parameter name and the value is the corresponding value of this parameter. Example: `parameter1: value1`
 
@@ -217,7 +208,8 @@ The current supported splitting methods can be found in [support documentation](
 
 ### random_by_ratio parameters
 
-In the *.yaml* file, the method name is `random_by_ratio`, and the parameters are:
+In the _.yaml_ file, the method name is `random_by_ratio`, and the parameters are:
+
 - `p`: test set proportion (mandatory)
 - `level`: **global** or **user** level (mandatory).
 
@@ -229,71 +221,73 @@ experiment:
   split:
     seed: 42
     test:
-      method: random_by_ratio 
+      method: random_by_ratio
       level: global # or user
       p: 0.2
     validation:
-      method: random_by_ratio 
+      method: random_by_ratio
       level: global # or user
       p: 0.2
 ```
 
 ### timestamp_by_ratio parameters
 
-In the *.yaml* file, the method name is `timestamp_by_ratio`, and the parameters are the same as Random by Ratio. Example:
+In the _.yaml_ file, the method name is `timestamp_by_ratio`, and the parameters are the same as Random by Ratio. Example:
 
 ```yaml
 experiment:
   # ...
   split:
     test:
-      method: timestamp_by_ratio 
+      method: timestamp_by_ratio
       level: user # or global
       p: 0.1
     validation:
-      method: timestamp_by_ratio 
+      method: timestamp_by_ratio
       level: user # or global
       p: 0.2
 ```
 
 ### fixed_timestamp parameters
 
-In the *.yaml* file, the method name is `fixed_timestamp`. The only parameter is the `timestamp` number. Example:
+In the _.yaml_ file, the method name is `fixed_timestamp`. The only parameter is the `timestamp` number. Example:
 
 ```yaml
 experiment:
   # ...
   split:
     test:
-      method: fixed_timestamp 
+      method: fixed_timestamp
       timestamp: 890000000
     validation:
-      method: fixed_timestamp 
+      method: fixed_timestamp
       timestamp: 880000000
 ```
 
 ### k_fold parameters
 
-In the *.yaml* file, the method name is `k_fold`. The parameters are:
+In the _.yaml_ file, the method name is `k_fold`. The parameters are:
+
 - `k`: number of folds (mandatory)
 - `level`: **global** or **user** level (mandatory).
 
 **Note:** This method does not support validation splitting.
 
 Example:
+
 ```yaml
 experiment:
   # ...
   split:
     test:
       method: k_fold
-      k: 3 
-      level: 'user'
+      k: 3
+      level: "user"
 ```
 
 ## models
 
-In the *.yaml* file, the **models** directive defines a list of models to be evaluated during the experiment pipeline. Example:
+In the _.yaml_ file, the **models** directive defines a list of models to be evaluated during the experiment pipeline. Example:
 
 ```yaml
 experiment:
@@ -304,21 +298,22 @@ experiment:
       parameters:
         parameter1: 10
         paramater2: value
-    - # Other model to be evaluated...
+    -  # Other model to be evaluated...
 ```
 
 Where,
+
 - `models`: specifies a list of models to be evaluated. (mandatory)
   - `name`: model name (mandatory)
   - `config`: metadata config
-    - *save_weights*: boolean that indicates if the model parameters must be saved after training.
-  - `parameters`: model parameters in the format `parameter_name: parameter_value` 
+    - _save_weights_: boolean that indicates if the model parameters must be saved after training.
+  - `parameters`: model parameters in the format `parameter_name: parameter_value`
 
 The supported models and their parameters can be found at the [support](./support.md) documentation.
 
 ## evaluation
 
-In the *.yaml* file, the **evaluation** directive defines the experiment evaluation. The evaluation metadata is given in the format `metadata1: metadata1_value`.  Example:
+In the _.yaml_ file, the **evaluation** directive defines the experiment evaluation. The evaluation metadata is given in the format `metadata1: metadata1_value`. Example:
 
 ```yaml
 experiment:
@@ -330,28 +325,26 @@ experiment:
 ```
 
 Where,
+
 - `evaluation`: specifies the evaluation metadata (mandatory)
   - `k`: evaluates the first k recommendations (mandatory)
-  - `relevance_threshold`: threshold value to consider a rating relevant.  
+  - `relevance_threshold`: threshold value to consider a rating relevant.
   - `metrics`: list of metric names to be evaluated. (mandatory)
 
-Check out the availables evaluation metric in the [support](./support.md) documentation. 
+Check out the availables evaluation metric in the [support](./support.md) documentation.
 
-## report 
+## report
 
-In the *.yaml* file, the **report** directive defines the experiment summarization. 
+In the _.yaml_ file, the **report** directive defines the experiment summarization.
 
 ```yaml
 experiment:
   # ...
   report:
-    file: 'experiment_results/ml100k_enriched/run1.csv'
+    file: "experiment_results/ml100k_enriched/run1.csv"
 ```
 
 Where,
-- `report`: specifies the report metadata (mandatory)
-  - `file`: ***.csv*** filename of the experiment report. (mandatory)
-
-
-
 
+- `report`: specifies the report metadata (mandatory)
+  - `file`: **_.csv_** filename of the experiment report. (mandatory)
diff --git a/experiment_results/last_fm/run1.csv b/experiment_results/last_fm/run1.csv
index a8411ce..168cea5 100644
--- a/experiment_results/last_fm/run1.csv
+++ b/experiment_results/last_fm/run1.csv
@@ -1,3 +1,2 @@
-,fold-1_MAP@5,fold-1_nDCG@5,fold-2_MAP@5,fold-2_nDCG@5,MAP@5_mean,MAP@5_std,nDCG@5_mean,nDCG@5_std,model
-Node2Vec based model + cosine similarity;q=1.0;p=1.0;embedding_size=64,0.20311906042675273,0.1600232845065004,0.20183629509962303,0.1663234608226964,0.20247767776318787,0.0006413826635648517,0.16317337266459842,0.0031500881580979884,Node2Vec based model + cosine similarity;q=1.0;p=1.0;embedding_size=64
-Node2Vec based model + cosine similarity;q=0.6;p=0.8;embedding_size=64,0.19808857808857808,0.16295033302278059,0.2002441213426674,0.160643994252724,0.19916634971562275,0.0010777716270446575,0.1617971636377523,0.0011531693850282965,Node2Vec based model + cosine similarity;q=0.6;p=0.8;embedding_size=64
+model,fold-1_MAP@5,fold-1_nDCG@5,fold-2_MAP@5,fold-2_nDCG@5,MAP@5_mean,MAP@5_std,nDCG@5_mean,nDCG@5_std
+Node2Vec based model + cosine similarity;q=1.0;p=1.0;embedding_size=64,0.21839250493096649,0.1771163629911136,0.2078632938643703,0.17274914807207017,0.2131278993976684,0.005264605533298092,0.17493275553159188,0.0021836074595217164
diff --git a/experiment_results/ml100k_enriched/runk10.csv b/experiment_results/ml100k_enriched/runk10.csv
index 4f48aff..c29e27a 100644
--- a/experiment_results/ml100k_enriched/runk10.csv
+++ b/experiment_results/ml100k_enriched/runk10.csv
@@ -1,3 +1,3 @@
 model,fold-1_MAP@10,fold-1_nDCG@10,fold-2_MAP@10,fold-2_nDCG@10,fold-3_MAP@10,fold-3_nDCG@10,fold-4_MAP@10,fold-4_nDCG@10,fold-5_MAP@10,fold-5_nDCG@10,MAP@10_mean,MAP@10_std,nDCG@10_mean,nDCG@10_std
-Node2Vec based model + cosine similarity;q=1.0;p=1.0;embedding_size=64,0.13725860973979945,0.22771094301421396,0.14019038488102517,0.2322035176003699,0.134025241051607,0.22292313487507265,0.13970702407470195,0.23274895196340248,0.13326647637964104,0.22428005042717597,0.13688954722535493,0.002839042246085731,0.227973319576047,0.00399786653114636
-Node2Vec based model + cosine similarity;q=0.6;p=0.8;embedding_size=64,0.14327306007827897,0.2362221380746961,0.1359488630002883,0.2259336758713797,0.1363378507799176,0.22843814950681451,0.13285614994200812,0.22322268568129713,0.14050866210747287,0.2347891888459272,0.1377849171815932,0.0036686875447563116,0.22972116759602296,0.005023324650698115
+Node2Vec based model + cosine similarity;q=1.0;p=1.0;embedding_size=64,0.1468935850134406,0.2402859736588019,0.1442547104855846,0.2370812436175602,0.14607495604188028,0.24014987015184636,0.15107832854676767,0.247086866537984,0.14624860798174183,0.24076310851606664,0.146910037613883,0.00226099679511848,0.2410734124964518,0.0032762271231638485
+Node2Vec based model + cosine similarity;q=0.6;p=0.8;embedding_size=64,0.14762345912796604,0.24008355755274188,0.14617894611793897,0.23564371192975384,0.14201889821754868,0.23538775039586132,0.14388291954187907,0.23440765616343803,0.1423197561790515,0.23087948541683637,0.14440479583687685,0.0021826431358835936,0.2352804322917263,0.0029442680961352654
diff --git a/framework/dataloader/graph/graph.py b/framework/dataloader/graph/graph.py
index df381a4..20b7904 100644
--- a/framework/dataloader/graph/graph.py
+++ b/framework/dataloader/graph/graph.py
@@ -8,47 +8,39 @@
 from tqdm import tqdm
 import numpy as np
 
-from typing import Set, Tuple
+from typing import Set, Tuple, Dict
 
 """
     Class wrapper on the top of a nx.Graph
 """
+
+
 class Graph(nx.Graph):
-    # def __init__(self, name, item, user, ratings, enrich = None):
-    #     super().__init__()
-    #     self.name = name 
-    #     self.item_nodes = set()
-    #     self.user_nodes = set()
-    #     self.rating_edges = defaultdict(set)        # { user: Set([items]) }
-    #     self.rating_item2users = defaultdict(set)   # { item: Set([users]) }
-        
-    #     # Adding item, user and properties nodes
-    #     self._add_item_info(item, enrich)
-    #     self._add_user_info(user)
-    #     self._add_ratings(ratings)
 
     def __init__(self):
         super().__init__()
+        self.datatype_separator = "::"
         self.item_nodes = set()
         self.user_nodes = set()
-        self.rating_edges = defaultdict(set)        # { user: Set([items]) }
-        self.rating_item2users = defaultdict(set)   # { item: Set([users]) }
+        self.rating_edges = defaultdict(set)  # { user: Set([items]) }
+        self.rating_item2users = defaultdict(set)  # { item: Set([users]) }
 
-    def build(self, name, item, user, ratings, enrich = None, social=None):        
+    def build(self, name, item, user, ratings, enrich=None, social=None):
         # Adding item, user and properties nodes
-        self.name = name 
+        self.name = name
         self._add_item_info(item, enrich)
         self._add_user_info(user)
         self._add_ratings(ratings)
         if social is not None:
             self._add_social_links(social)
 
-
     def info(self):
         n_nodes = self.number_of_nodes()
         n_edges = self.number_of_edges()
-        message =  f'{self.name} graph with a total of {n_nodes} nodes and {n_edges} edges'
-        message +=  f' ({len(self.user_nodes)} users, {len(self.item_nodes)} items and {len(list(self.get_rating_edges()))} ratings)'
+        message = (
+            f"{self.name} graph with a total of {n_nodes} nodes and {n_edges} edges"
+        )
+        message += f" ({len(self.user_nodes)} users, {len(self.item_nodes)} items and {len(list(self.get_rating_edges()))} ratings)"
         return message
 
     def get_item_nodes(self) -> Set[ItemNode]:
@@ -68,28 +60,28 @@ def get_user_property_edges(self) -> Tuple[UserNode, PropertyNode]:
                 yield (node, edge)
             elif isinstance(node, PropertyNode) and isinstance(edge, UserNode):
                 yield (edge, node)
-    
+
     def get_item_property_edges(self) -> Tuple[ItemNode, PropertyNode]:
         for node, edge in self.edges():
             if isinstance(node, ItemNode) and isinstance(edge, PropertyNode):
                 yield (node, edge)
             elif isinstance(node, PropertyNode) and isinstance(edge, ItemNode):
                 yield (edge, node)
-    
+
     def get_ratings_with_labels(self):
         ratings = defaultdict(list)
-        for (u,v) in list(self.get_rating_edges()):
-            data = self.get_edge_data(u,v)
-            ratings[u].append((v, data['rating']))
+        for u, v in list(self.get_rating_edges()):
+            data = self.get_edge_data(u, v)
+            ratings[u].append((v, data["rating"]))
 
         return ratings
-    
+
     def get_all_triples(self):
         triples_return = {"head": [], "relation": [], "tail": []}
-        
+
         # ratings triples
         ratings = self.get_ratings_with_labels()
-        desc = f'Generating ratings triples'
+        desc = f"Generating ratings triples"
         n_total = len(ratings.keys())
         for user, ratings in tqdm(ratings.items(), total=n_total, desc=desc):
             ratings.sort(key=lambda x: x[1], reverse=True)
@@ -100,7 +92,7 @@ def get_all_triples(self):
 
         # user property triples
         user_properties = self.get_user_property_edges()
-        desc = f'Generating user properties triples'
+        desc = f"Generating user properties triples"
         for user, user_property in tqdm(user_properties, desc=desc):
             triples_return["head"].append(user.__str__())
             triples_return["relation"].append("is")
@@ -108,20 +100,20 @@ def get_all_triples(self):
 
         # item property triples
         item_properties = self.get_item_property_edges()
-        desc = f'Generating item properties triples'
+        desc = f"Generating item properties triples"
         for item, item_property in tqdm(item_properties, desc=desc):
             triples_return["head"].append(item.__str__())
             triples_return["relation"].append("has")
             triples_return["tail"].append(item_property.__str__())
-        
+
         return pd.DataFrame(triples_return)
-    
+
     def get_ratings_triples(self):
         triples_return = {"head": [], "relation": [], "tail": []}
-        
+
         # ratings triples
         ratings = self.get_ratings_with_labels()
-        desc = f'Generating ratings triples'
+        desc = f"Generating ratings triples"
         n_total = len(ratings.keys())
         for user, ratings in tqdm(ratings.items(), total=n_total, desc=desc):
             ratings.sort(key=lambda x: x[1], reverse=True)
@@ -129,36 +121,35 @@ def get_ratings_triples(self):
                 triples_return["head"].append(user.__str__())
                 triples_return["relation"].append(f"rating{rating[1]}")
                 triples_return["tail"].append(rating[0].__str__())
-        
+
         return pd.DataFrame(triples_return)
-    
-    def get_user_rated_items(self, user : UserNode) -> Set[ItemNode]:
+
+    def get_user_rated_items(self, user: UserNode) -> Set[ItemNode]:
         return self.rating_edges[user]
-    
+
     def add_node(self, node_for_adding: Node, **attr):
         super().add_node(node_for_adding, **attr)
         if isinstance(node_for_adding, ItemNode):
             self.item_nodes.add(node_for_adding)
         elif isinstance(node_for_adding, UserNode):
             self.user_nodes.add(node_for_adding)
-    
+
     def add_edge(self, u_of_edge, v_of_edge, **attr):
         super().add_edge(u_of_edge, v_of_edge, **attr)
         data = self.get_edge_data(u_of_edge, v_of_edge, default={})
-        edge_type = data.get('type')
+        edge_type = data.get("type")
 
-        if edge_type == 'rated':
+        if edge_type == "rated":
             self.rating_edges[u_of_edge].add(v_of_edge)
             self.rating_item2users[v_of_edge].add(u_of_edge)
-            
-    
+
     def remove_edge(self, u, v):
         if isinstance(u, UserNode) and isinstance(v, ItemNode):
             self.rating_edges[u].remove(v)
             self.rating_item2users[v].remove(u)
 
         super().remove_edge(u, v)
-    
+
     def remove_node(self, n):
         super().remove_node(n)
         if isinstance(n, ItemNode):
@@ -178,19 +169,19 @@ def convert_node_labels_to_integer(self):
         N = self.number_of_nodes()
         mapping = dict(zip(self.nodes(), range(0, N)))
         G = nx.relabel_nodes(self, mapping, copy=True)
-        nx.set_node_attributes(G, {v: k for k, v in mapping.items()}, 'old_label')
-        
+        nx.set_node_attributes(G, {v: k for k, v in mapping.items()}, "old_label")
+
         G.item_nodes = self.item_nodes.copy()
         G.user_nodes = self.user_nodes.copy()
         G.rating_edges = self.rating_edges.copy()
         G.rating_item2users = self.rating_item2users.copy()
 
         return G
-    
+
     def convert_back(self):
-        mapping = {node: self.nodes[node]['old_label'] for node in self.nodes()}
+        mapping = {node: self.nodes[node]["old_label"] for node in self.nodes()}
         G = nx.relabel_nodes(self, mapping, copy=True)
-        nx.set_node_attributes(G, {v: k for k, v in mapping.items()}, 'old_label')
+        nx.set_node_attributes(G, {v: k for k, v in mapping.items()}, "old_label")
 
         G.item_nodes = self.item_nodes.copy()
         G.user_nodes = self.user_nodes.copy()
@@ -199,110 +190,125 @@ def convert_back(self):
 
         return G
 
+    def __read_csv(self, path: str) -> pd.DataFrame:
+        df = pd.read_csv(path)
+        col_names = []
+        for col in df.columns:
+            try:
+                col_name, data_type = col.split("::")
+                col_names.append(col_name)
+
+                if data_type == "string":
+                    df[col] = df[col].astype(str)
+                elif data_type == "string_list":
+                    df[col] = df[col].apply(lambda x: str(x).split("::"))
+                elif data_type == "number":
+                    df[col] = pd.to_numeric(df[col])
+            except Exception as e:
+                print(
+                    f"Couldn't parse data type of column named {col}. Should be col_name::data_type"
+                )
+                print(f"Supported types are: string, string_list, number")
+                exit()
+        df.columns = col_names
+
+        return df
+
     def _add_item_info(self, item, enrich):
         # Extracting info from .csv
-        df_item = pd.read_csv(item['path'])
-        extra_features = get_optional_argument(item, 'extra_features', [])
-        df_item = df_item[['item_id'] + extra_features]
+        df_item = self.__read_csv(item["path"])
+        extra_features = get_optional_argument(item, "extra_features", [])
+        df_item = df_item[["item_id"] + extra_features]
         if enrich is not None:
-            # Merging info from original dataset and enriched 
-            df_map = pd.read_csv(enrich['map_path'])
-            df_item = pd.merge(df_item, df_map, on="item_id", how="left")  
-            if enrich['remove_unmatched']:
-                df_item = df_item[df_item['URI'].notna()]
-            
+            # Merging info from original dataset and enriched
+            df_map = self.__read_csv(enrich["map_path"])
+            df_item = pd.merge(df_item, df_map, on="item_id", how="left")
+            if enrich["remove_unmatched"]:
+                df_item = df_item[df_item["URI"].notna()]
+
             df_enriched = self._get_enrich_dataframe(**enrich)
-            df_item = pd.merge(df_item, df_enriched, on='item_id', how="left")
+            df_item = pd.merge(df_item, df_enriched, on="item_id", how="left")
 
         # Creating graph from dataframe
-        desc = 'Adding item info into network'
+        desc = "Adding item info into network"
         n_items = df_item.shape[0]
         properties = list(df_item.columns)
-        properties.remove('item_id')
+        properties.remove("item_id")
 
         # Adding item nodes and properties nodes
         for _, row in tqdm(df_item.iterrows(), total=n_items, desc=desc):
-            item_node = ItemNode(row['item_id'])
+            item_node = ItemNode(row["item_id"])
             self.add_node(item_node)
 
             for property_ in properties:
                 self._add_node_property(item_node, row, property_)
-    
+
     def _add_node_property(self, source: Node, row: pd.Series, property_: str):
         prop_value = row[property_]
         if not isinstance(prop_value, list) and pd.notna(prop_value):
             # Single property_
             property_node = PropertyNode(row[property_], property_)
-            self.add_edge(source, property_node, type='has_property')
+            self.add_edge(source, property_node, type="has_property")
         elif isinstance(prop_value, list):
             # Multiples properties
             for value in row[property_]:
                 property_node = PropertyNode(value, property_)
-                self.add_edge(source, property_node, type='has_property')
-
+                self.add_edge(source, property_node, type="has_property")
 
     def _get_optional_argument(self, config, key, default):
         try:
             return config[key]
         except KeyError:
             return default
-    
-    def _get_enrich_dataframe(self, **enrich_config):
-        df = pd.read_csv(enrich_config['enrich_path'])
-        df_enriched = pd.DataFrame(df.copy()['item_id'])
-
-        for property in enrich_config['properties']:
-            prop_type = property['type']
-            grouped = property['grouped']
-            sep = property.get('sep')
 
-            prop_series = df[prop_type]
-            f = lambda x: x.split(sep) if grouped and pd.notna(x) else x
-            df_enriched[prop_type] = prop_series.apply(f)
+    def _get_enrich_dataframe(self, **enrich_config):
+        df_enriched = self.__read_csv(enrich_config["enrich_path"])
+        properties = enrich_config["properties"]
+        df_enriched = df_enriched[["item_id"] + properties]
 
         return df_enriched
 
     def _add_user_info(self, user):
-        df_user = pd.read_csv(user['path'])
-        extra_features = get_optional_argument(user, 'extra_features', [])
-        df_user = df_user[['user_id'] + extra_features]
+        df_user = self.__read_csv(user["path"])
+        extra_features = get_optional_argument(user, "extra_features", [])
+        df_user = df_user[["user_id"] + extra_features]
 
         total_users = df_user.shape[0]
-        desc = 'Adding user info into network'
+        desc = "Adding user info into network"
         properties = list(df_user.columns)
-        properties.remove('user_id')
+        properties.remove("user_id")
 
         for _, row in tqdm(df_user.iterrows(), total=total_users, desc=desc):
-            user_node = UserNode(row['user_id'])
+            user_node = UserNode(row["user_id"])
             self.add_node(user_node)
 
             for property_ in properties:
                 self._add_node_property(user_node, row, property_)
 
     def _add_ratings(self, ratings):
-        df_ratings = pd.read_csv(ratings['path'])
+        df_ratings = self.__read_csv(ratings["path"])
         total_ratings = df_ratings.shape[0]
-        desc = 'Adding ratings into network'
+        desc = "Adding ratings into network"
 
         for _, row in tqdm(df_ratings.iterrows(), total=total_ratings, desc=desc):
-            item_node = ItemNode(row['item_id'])
-            user_node = UserNode(row['user_id'])
+            item_node = ItemNode(row["item_id"])
+            user_node = UserNode(row["user_id"])
 
             if item_node in self and user_node in self:
-                attrs = {'type': 'rated', 'rating': row['rating']}
-                if ratings['timestamp']:
-                    attrs['timestamp'] = row['timestamp']
+                attrs = {"type": "rated", "rating": row["rating"]}
+                if ratings["timestamp"]:
+                    attrs["timestamp"] = row["timestamp"]
 
                 self.add_edge(user_node, item_node, **attrs)
 
     def _add_social_links(self, social):
-        df_social = pd.read_csv(social['path'])
+        df_social = self.__read_csv(social["path"])
         total_links = df_social.shape[0]
-        desc = 'Adding social links between users into network'
+        desc = "Adding social links between users into network"
 
         for _, row in tqdm(df_social.iterrows(), total=total_links, desc=desc):
-            user1_node = UserNode(row['user1'])
-            user2_node = UserNode(row['user2'])
+            user1_node = UserNode(row["user1"])
+            user2_node = UserNode(row["user2"])
 
             if user1_node in self and user2_node in self:
-                self.add_edge(user1_node, user2_node)
\ No newline at end of file
+                self.add_edge(user1_node, user2_node)


Test patch:
diff --git a/config_files/test.yml b/config_files/test.yml
index 9aa551b..d16f462 100644
--- a/config_files/test.yml
+++ b/config_files/test.yml
@@ -1,33 +1,27 @@
 experiment:
-  dataset: 
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,37 +32,35 @@ experiment:
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
-
     test:
       method: k_fold
-      k: 5
-      level: 'user'
-
+      k: 2
+      level: "user"
 
   models:
     - name: deepwalk_based
@@ -91,11 +83,11 @@ experiment:
         n_walks: 50
         embedding_size: 64
         epochs: 1
-  
+
   evaluation:
     k: 10
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/runk10.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/runk10.csv"
diff --git a/config_files/test2.yml b/config_files/test2.yml
index 09135b3..c55431d 100644
--- a/config_files/test2.yml
+++ b/config_files/test2.yml
@@ -1,16 +1,16 @@
 experiment:
-  dataset: 
+  dataset:
     name: lastfm
-    item: 
-      path: datasets/lastfm/processed/item.csv 
-      extra_features: [name] 
-    user: 
-      path: datasets/lastfm/processed/user.csv 
-    ratings: 
-      path: datasets/lastfm/processed/rating.csv 
+    item:
+      path: datasets/lastfm/processed/item.csv
+      extra_features: [name]
+    user:
+      path: datasets/lastfm/processed/user.csv
+    ratings:
+      path: datasets/lastfm/processed/rating.csv
       timestamp: False
     social:
-      path: datasets/lastfm/processed/social.csv 
+      path: datasets/lastfm/processed/social.csv
     # enrich:
     #   map_path: datasets/last_fm/processed/map.csv
     #   enrich_path: datasets/last_fm/processed/enriched.csv
@@ -25,11 +25,11 @@ experiment:
     #       sep: "::"
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -40,36 +40,35 @@ experiment:
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
-
     test:
       method: k_fold
       k: 2
-      level: 'user'
+      level: "user"
 
   models:
     - name: deepwalk_based
@@ -91,11 +90,10 @@ experiment:
         embedding_size: 64
         epochs: 1
 
-
   evaluation:
     k: 5
     relevance_threshold: 3
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/last_fm/run1_with_social_links.csv'
\ No newline at end of file
+    file: "experiment_results/last_fm/run1_with_social_links.csv"
diff --git a/config_files/test3.yml b/config_files/test3.yml
index 17ca0d3..59610e8 100644
--- a/config_files/test3.yml
+++ b/config_files/test3.yml
@@ -1,16 +1,16 @@
 experiment:
-  dataset: 
+  dataset:
     name: lastfm
-    item: 
-      path: datasets/lastfm/processed/item.csv 
-      extra_features: [name] 
-    user: 
-      path: datasets/lastfm/processed/user.csv 
-    ratings: 
-      path: datasets/lastfm/processed/rating.csv 
+    item:
+      path: datasets/lastfm/processed/item.csv
+      extra_features: [name]
+    user:
+      path: datasets/lastfm/processed/user.csv
+    ratings:
+      path: datasets/lastfm/processed/rating.csv
       timestamp: False
-    # social:
-    #   path: datasets/lastfm/processed/social.csv 
+    social:
+      path: datasets/lastfm/processed/social.csv
     # enrich:
     #   map_path: datasets/last_fm/processed/map.csv
     #   enrich_path: datasets/last_fm/processed/enriched.csv
@@ -25,11 +25,11 @@ experiment:
     #       sep: "::"
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -40,36 +40,35 @@ experiment:
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
-
     test:
       method: k_fold
       k: 2
-      level: 'user'
+      level: "user"
 
   models:
     - name: deepwalk_based
@@ -81,15 +80,15 @@ experiment:
         n_walks: 50
         embedding_size: 64
         epochs: 1
-    - name: deepwalk_based
-      config:
-      parameters:
-        walk_len: 10
-        p: 0.8
-        q: 0.6
-        n_walks: 50
-        embedding_size: 64
-        epochs: 1
+    # - name: deepwalk_based
+    #   config:
+    #   parameters:
+    #     walk_len: 10
+    #     p: 0.8
+    #     q: 0.6
+    #     n_walks: 50
+    #     embedding_size: 64
+    #     epochs: 1
 
   evaluation:
     k: 5
@@ -97,4 +96,4 @@ experiment:
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/last_fm/run1.csv'
\ No newline at end of file
+    file: "experiment_results/last_fm/run1.csv"
diff --git a/config_files/test_complEx.yml b/config_files/test_complEx.yml
index b9889ee..2304e37 100644
--- a/config_files/test_complEx.yml
+++ b/config_files/test_complEx.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: complEx
@@ -76,12 +70,12 @@
         embedding_dim: 200
         epochs: 5
         seed: 42
-        triples: ratings # only (ratings) or (all) triples for training       
-  
+        triples: ratings # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/complExratings.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/complExratings.csv"
diff --git a/config_files/test_distMult.yml b/config_files/test_distMult.yml
index 5ad8baf..3ace98d 100644
--- a/config_files/test_distMult.yml
+++ b/config_files/test_distMult.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: distMult
@@ -76,12 +70,12 @@
         embedding_dim: 50
         epochs: 5
         seed: 42
-        triples: all # only (ratings) or (all) triples for training       
-  
+        triples: all # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/distMultall.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/distMultall.csv"
diff --git a/config_files/test_rESCAL.yml b/config_files/test_rESCAL.yml
index 0b92582..222e078 100644
--- a/config_files/test_rESCAL.yml
+++ b/config_files/test_rESCAL.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: rESCAL
@@ -76,12 +70,12 @@
         embedding_dim: 50
         epochs: 5
         seed: 42
-        triples: ratings # only (ratings) or (all) triples for training       
-  
+        triples: ratings # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/rESCALratings.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/rESCALratings.csv"
diff --git a/config_files/test_rotatE.yml b/config_files/test_rotatE.yml
index 7a062a0..bc038f4 100644
--- a/config_files/test_rotatE.yml
+++ b/config_files/test_rotatE.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: rotatE
@@ -76,12 +70,12 @@
         embedding_dim: 200
         epochs: 5
         seed: 42
-        triples: all # only (ratings) or (all) triples for training       
-  
+        triples: all # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/rotatEall.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/rotatEall.csv"
diff --git a/config_files/test_transD.yml b/config_files/test_transD.yml
index 1b4ffae..86f841a 100644
--- a/config_files/test_transD.yml
+++ b/config_files/test_transD.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: transD
@@ -76,12 +70,12 @@
         embedding_dim: 150
         epochs: 5
         seed: 42
-        triples: ratings # only (ratings) or (all) triples for training       
-  
+        triples: ratings # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/transDratings.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/transDratings.csv"
diff --git a/config_files/test_transE.yml b/config_files/test_transE.yml
index 300db1d..e302afa 100644
--- a/config_files/test_transE.yml
+++ b/config_files/test_transE.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: transE
@@ -77,14 +71,14 @@
         scoring_fct_norm: 1
         epochs: 5
         seed: 42
-        triples: ratings # only (ratings) or (all) triples for training       
-  
+        triples: ratings # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/transEratings.csv'
+    file: "experiment_results/ml100k_enriched/transEratings.csv"
     execution_times:
-      file: 'experiment_results/ml100k_enriched/transEtimesratings.csv'
\ No newline at end of file
+      file: "experiment_results/ml100k_enriched/transEtimesratings.csv"
diff --git a/config_files/test_transH.yml b/config_files/test_transH.yml
index 0ffcc44..a193253 100644
--- a/config_files/test_transH.yml
+++ b/config_files/test_transH.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: transH
@@ -77,12 +71,12 @@
         scoring_fct_norm: 2
         epochs: 5
         seed: 42
-        triples: ratings # only (ratings) or (all) triples for training       
-  
+        triples: ratings # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/transHratings.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/transHratings.csv"
diff --git a/config_files/test_transR.yml b/config_files/test_transR.yml
index 97d39b1..82ffe6b 100644
--- a/config_files/test_transR.yml
+++ b/config_files/test_transR.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: transR
@@ -78,12 +72,12 @@
         scoring_fct_norm: 2
         epochs: 5
         seed: 42
-        triples: all # only (ratings) or (all) triples for training       
-  
+        triples: all # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/transR1.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/transR1.csv"
diff --git a/config_files/test_tuckER.yml b/config_files/test_tuckER.yml
index 95bca99..9e7e0ef 100644
--- a/config_files/test_tuckER.yml
+++ b/config_files/test_tuckER.yml
@@ -1,33 +1,27 @@
- experiment:
-  dataset: 
+experiment:
+  dataset:
     name: ml-100k
     item: # infos related to item dataset (mandatory, at least item_id)
-      path: datasets/ml-100k/processed/item.csv 
+      path: datasets/ml-100k/processed/item.csv
       extra_features: [movie_year, movie_title] # features(columns) beside item_id to be used
     user: # mandatory (at least user_id)
-      path: datasets/ml-100k/processed/user.csv 
+      path: datasets/ml-100k/processed/user.csv
       extra_features: [gender, occupation] # features beside user_id
     ratings: # mandatory (at least [user_id, item_id, rating])
-      path: datasets/ml-100k/processed/rating.csv 
+      path: datasets/ml-100k/processed/rating.csv
       timestamp: True
     enrich:
       map_path: datasets/ml-100k/processed/map.csv
       enrich_path: datasets/ml-100k/processed/enriched.csv
       remove_unmatched: False
-      properties:
-        - type: subject
-          grouped: True
-          sep: "::"
-        - type: director
-          grouped: True
-          sep: "::"
+      properties: [subject, director]
 
   preprocess:
-    # - method: filter_by_rating  
+    # - method: filter_by_rating
     #   parameters:
     #     threshold: 20
     # - method: binarize
-    #   parameters: 
+    #   parameters:
     #     threshold: 4
     - method: filter_kcore
       parameters:
@@ -38,35 +32,35 @@
   split:
     seed: 42
     # test:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
     # validation:
-    #   method: random_by_ratio 
-    #   level: global 
+    #   method: random_by_ratio
+    #   level: global
     #   p: 0.2
 
     # test:
-    #   method: timestamp_by_ratio 
-    #   level: user 
+    #   method: timestamp_by_ratio
+    #   level: user
     #   p: 0.1
     # validation:
-    #   level: user 
-    #   method: timestamp_by_ratio 
+    #   level: user
+    #   method: timestamp_by_ratio
     #   p: 0.2
 
     # test:
-    #   method: fixed_timestamp 
-    #   # type: global_level 
+    #   method: fixed_timestamp
+    #   # type: global_level
     #   timestamp: 890000000
     # validation:
-    #   method: fixed_timestamp 
+    #   method: fixed_timestamp
     #   timestamp: 880000000
 
     test:
       method: k_fold
       k: 5
-      level: 'user'
+      level: "user"
 
   models:
     - name: tuckER
@@ -76,12 +70,12 @@
         embedding_dim: 200
         epochs: 5
         seed: 42
-        triples: ratings # only (ratings) or (all) triples for training       
-  
+        triples: ratings # only (ratings) or (all) triples for training
+
   evaluation:
     k: 5
     relevance_threshold: 0
     metrics: [MAP, nDCG]
 
   report:
-    file: 'experiment_results/ml100k_enriched/tuckERratings.csv'
\ No newline at end of file
+    file: "experiment_results/ml100k_enriched/tuckERratings.csv"