FranKGraphBench 0.0.1a0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. FranKGraphBench-0.0.1a0.dist-info/LICENSE +201 -0
  2. FranKGraphBench-0.0.1a0.dist-info/METADATA +189 -0
  3. FranKGraphBench-0.0.1a0.dist-info/RECORD +70 -0
  4. FranKGraphBench-0.0.1a0.dist-info/WHEEL +6 -0
  5. FranKGraphBench-0.0.1a0.dist-info/entry_points.txt +3 -0
  6. FranKGraphBench-0.0.1a0.dist-info/top_level.txt +2 -0
  7. data_integration/__init__.py +47 -0
  8. data_integration/__main__.py +4 -0
  9. data_integration/dataset.py +270 -0
  10. data_integration/dataset2class.py +34 -0
  11. data_integration/datasets/__init__.py +0 -0
  12. data_integration/datasets/amazon.py +140 -0
  13. data_integration/datasets/book-crossing.py +98 -0
  14. data_integration/datasets/douban-movie.py +75 -0
  15. data_integration/datasets/lastfm.py +215 -0
  16. data_integration/datasets/movielens.py +375 -0
  17. data_integration/datasets/steam.py +98 -0
  18. data_integration/datasets/worker.py +31 -0
  19. data_integration/datasets/yelp.py +156 -0
  20. framework/__init__.py +17 -0
  21. framework/__main__.py +4 -0
  22. framework/dataloader/__init__.py +0 -0
  23. framework/dataloader/dataloader.py +52 -0
  24. framework/dataloader/dataset/__init__.py +0 -0
  25. framework/dataloader/dataset/dataset.py +38 -0
  26. framework/dataloader/edge_splitter/__init__.py +0 -0
  27. framework/dataloader/edge_splitter/edge_splitter.py +163 -0
  28. framework/dataloader/graph/__init__.py +0 -0
  29. framework/dataloader/graph/graph.py +314 -0
  30. framework/dataloader/graph/node.py +80 -0
  31. framework/dataloader/preprocess/__init__.py +0 -0
  32. framework/dataloader/preprocess/methods.py +47 -0
  33. framework/evaluator/__init__.py +0 -0
  34. framework/evaluator/metric.py +39 -0
  35. framework/evaluator/metric2class.py +10 -0
  36. framework/evaluator/metrics/__init__.py +0 -0
  37. framework/evaluator/metrics/map.py +33 -0
  38. framework/evaluator/metrics/ndcg.py +49 -0
  39. framework/experiment.py +120 -0
  40. framework/recommender/__init__.py +0 -0
  41. framework/recommender/model2class.py +46 -0
  42. framework/recommender/models/__init__.py +0 -0
  43. framework/recommender/models/complEx/__init__.py +0 -0
  44. framework/recommender/models/complEx/model.py +193 -0
  45. framework/recommender/models/deep_walk_based/__init__.py +0 -0
  46. framework/recommender/models/deep_walk_based/model.py +149 -0
  47. framework/recommender/models/distMult/__init__.py +0 -0
  48. framework/recommender/models/distMult/model.py +187 -0
  49. framework/recommender/models/ePHEN/__init__.py +0 -0
  50. framework/recommender/models/ePHEN/model.py +159 -0
  51. framework/recommender/models/rESCAL/__init__.py +0 -0
  52. framework/recommender/models/rESCAL/model.py +184 -0
  53. framework/recommender/models/rotatE/__init__.py +0 -0
  54. framework/recommender/models/rotatE/model.py +195 -0
  55. framework/recommender/models/transD/__init__.py +0 -0
  56. framework/recommender/models/transD/model.py +190 -0
  57. framework/recommender/models/transE/__init__.py +0 -0
  58. framework/recommender/models/transE/model.py +194 -0
  59. framework/recommender/models/transH/__init__.py +0 -0
  60. framework/recommender/models/transH/model.py +193 -0
  61. framework/recommender/models/transR/__init__.py +0 -0
  62. framework/recommender/models/transR/model.py +190 -0
  63. framework/recommender/models/tuckER/__init__.py +0 -0
  64. framework/recommender/models/tuckER/model.py +199 -0
  65. framework/recommender/recommender.py +32 -0
  66. framework/recommender/utils/__init__.py +0 -0
  67. framework/recommender/utils/walker.py +185 -0
  68. framework/reporter/__init__.py +0 -0
  69. framework/reporter/report.py +101 -0
  70. framework/utils.py +10 -0
@@ -0,0 +1,31 @@
1
+ from threading import Thread
2
+ import queue
3
+
4
+ class Worker(Thread):
5
+ def __init__(self, request_queue, query_function, pbar=None):
6
+ """
7
+ Parameters:
8
+ request_queue: queue containing requests to be consumed by workers
9
+ query_function: function to make the query to the endpoint
10
+ pbar: tqdm progressbar to be updated after each query
11
+ """
12
+ super().__init__()
13
+ self.queue = request_queue
14
+ self.local_results = []
15
+ self.query = query_function
16
+ self.pbar = pbar
17
+
18
+ def run(self):
19
+ while True:
20
+ try:
21
+ idx, query = self.queue.get(block=False)
22
+ response = self.query(query)
23
+ self.local_results.append((idx, response))
24
+ if self.pbar is not None:
25
+ self.pbar.update(n=1)
26
+ self.queue.task_done()
27
+ except queue.Empty:
28
+ break
29
+ except Exception as e:
30
+ print(f'Exception:')
31
+ print(e)
@@ -0,0 +1,156 @@
1
+ import os
2
+ import json
3
+ import pandas as pd
4
+ from collections import defaultdict
5
+
6
+ from ..dataset import Dataset
7
+
8
+ class Yelp(Dataset):
9
+ def __init__(self, input_path, output_path, n_workers=1):
10
+ super().__init__(input_path, output_path, n_workers)
11
+ self.dataset_name = "Yelp"
12
+
13
+ self.categories_separator = ", "
14
+ self.friends_separator = ", "
15
+ self.elites_separator = ","
16
+
17
+ self.string_list_separator = "::"
18
+
19
+ self.item_fields = {
20
+ "business_id": "item_id::string",
21
+ "name": "name::string",
22
+ "address": "address::string",
23
+ "city": "city::string",
24
+ "state": "state::string",
25
+ "postal_code": "postal_code::string",
26
+ "latitude": "latitute::number",
27
+ "longitude": "longitude::number",
28
+ "stars": "stars::number",
29
+ "review_count": "review_count::number",
30
+ "is_open": "is_open::number",
31
+ "attributes": "attributes::string_list",
32
+ "categories": "categories::string_list",
33
+ }
34
+
35
+ self.user_fields = {
36
+ "user_id": "user_id::string",
37
+ "name": "name::string",
38
+ "review_count": "review_count::number",
39
+ "yelping_since": "yelping_since::string",
40
+ "useful": "useful_count::number",
41
+ "funny": "funny_count::number",
42
+ "cool": "cool_count::number",
43
+ "elite": "elite_years::string_list",
44
+ "fans": "fans_count::number",
45
+ "average_stars": "average_stars::number",
46
+ "compliment_hot": "compliment_hot_count::number",
47
+ "compliment_more": "compliment_more_count::number",
48
+ "compliment_profile": "compliment_cute_count::number",
49
+ "compliment_list": "compliment_list_count::number",
50
+ "compliment_note": "compliment_note_count::number",
51
+ "compliment_plain": "compliment_plain_count::number",
52
+ "compliment_cool": "compliment_cool_count::number",
53
+ "compliment_funny": "compliment_funny_count::number",
54
+ "compliment_writer": "compliment_writer_count::number",
55
+ "compliment_photos": "compliment_photos_count::number"
56
+ }
57
+
58
+ self.rating_fields = {
59
+ "user_id": "user_id::string",
60
+ "business_id": "item_id::string",
61
+ "stars": "rating::number",
62
+ "useful": "useful_count::number",
63
+ "funny": "funny_count::number",
64
+ "cool": "cool_count::number",
65
+ "text": "text::string",
66
+ "date": "date::string"
67
+ }
68
+
69
+ self.social_fields = {"user_id": "user1::string", "friend_id": "user2::string"}
70
+
71
+ def load_item_data(self) -> pd.DataFrame():
72
+ filename = os.path.join(self.input_path, "yelp_academic_dataset_business.json")
73
+
74
+ print(filename)
75
+ file = open(filename, encoding="utf8")
76
+ data = []
77
+ for line in file:
78
+ data.append(json.loads(line))
79
+ df = pd.DataFrame(data)
80
+ file.close()
81
+
82
+ attributes = defaultdict(list)
83
+ for index, row in df['attributes'].dropna().items():
84
+ for key, value in row.items():
85
+ if value == 'True':
86
+ attributes[index].append(key)
87
+ df['attributes'] = pd.Series(attributes).apply(lambda x: self.string_list_separator.join(x))
88
+
89
+ categories = defaultdict(list)
90
+ for index, row in df['categories'].dropna().items():
91
+ for category in row.split(self.categories_separator):
92
+ categories[index].append(category)
93
+ df['categories'] = pd.Series(categories).apply(lambda x: self.string_list_separator.join(x))
94
+
95
+ df = df.rename(self.item_fields, axis=1)
96
+ df = df[self.item_fields.values()]
97
+ return df
98
+
99
+ def load_user_data(self) -> pd.DataFrame:
100
+ filename = os.path.join(self.input_path, "yelp_academic_dataset_user.json")
101
+
102
+ file = open(filename, encoding="utf8")
103
+ data = []
104
+ for line in file:
105
+ data.append(json.loads(line))
106
+ df = pd.DataFrame(data)
107
+ file.close()
108
+
109
+ elites = defaultdict(list)
110
+ for index, row in df['elite'].dropna().items():
111
+ for elite in row.split(self.elites_separator):
112
+ elites[index].append(elite)
113
+ df['elite'] = pd.Series(elites).apply(lambda x: self.string_list_separator.join(x))
114
+
115
+ df = df.rename(self.user_fields, axis=1)
116
+ df = df[self.user_fields.values()]
117
+ return df
118
+
119
+ def load_rating_data(self) -> pd.DataFrame:
120
+ filename = os.path.join(self.input_path, "yelp_academic_dataset_review.json")
121
+
122
+ file = open(filename, encoding="utf8")
123
+ data = []
124
+ for line in file:
125
+ data.append(json.loads(line))
126
+ df = pd.DataFrame(data)
127
+ file.close()
128
+ df = df.rename(self.rating_fields, axis=1)
129
+ df = df[self.rating_fields.values()]
130
+ return df
131
+
132
+ def load_social_data(self) -> pd.DataFrame:
133
+ filename = os.path.join(self.input_path, "yelp_academic_dataset_user.json")
134
+
135
+ file = open(filename, encoding="utf8")
136
+ data = []
137
+ for line in file:
138
+ data.append(json.loads(line))
139
+ user_df = pd.DataFrame(data)
140
+ file.close()
141
+
142
+ friends = defaultdict(list)
143
+ for index, row in user_df['friends'].dropna().items():
144
+ for friend in row.split(self.friends_separator):
145
+ friends[index].append(friend)
146
+
147
+ social = {'user_id': [], 'friend_id': []}
148
+ for index, friends_list in friends.items():
149
+ for friend in friends_list:
150
+ social["user_id"].append(user_df['user_id'].iloc[index])
151
+ social["friend_id"].append(friend)
152
+
153
+ df = pd.DataFrame(social)
154
+ df = df.rename(self.social_fields, axis=1)
155
+ df = df[self.social_fields.values()]
156
+ return df
framework/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ import argparse
2
+ from framework.experiment import run
3
+
4
+ __version__ = "0.0.1-alpha"
5
+ __author__ = "Paulo do Carmo and Alvaro Lopes"
6
+
7
+ def framework():
8
+ parser = argparse.ArgumentParser(
9
+ description="Script to run framework for reproducible experiment."
10
+ )
11
+
12
+ parser.add_argument('-c', '--config', type=str, required=True, help='.yml config filepath to setup experiment')
13
+
14
+ args = parser.parse_args()
15
+ config_path = args.config
16
+ print(f'Running experiment...')
17
+ run(config_path)
framework/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from . import framework
2
+
3
+ if __name__ == '__main__':
4
+ framework()
File without changes
@@ -0,0 +1,52 @@
1
+ from ..utils import get_optional_argument
2
+ from .graph.graph import Graph
3
+ from .preprocess.methods import apply_method
4
+ from .edge_splitter.edge_splitter import EdgeSplitter
5
+ from .dataset.dataset import Dataset
6
+
7
+ """
8
+ Loads, preprocess, filter and split dataset, then return a dataset
9
+ :arguments:
10
+ dataset: dict specifying the dataset configuration
11
+ :returns: Networkx graph (train, val, test)
12
+ """
13
+ def load(**data_config):
14
+ # G = Graph(**data_config)
15
+ G = Graph()
16
+ G.build(**data_config)
17
+ return G
18
+
19
+ def preprocess(G, methods: list):
20
+ for method in methods:
21
+ apply_method(G, **method)
22
+
23
+ def split(G, **split_config):
24
+ seed = get_optional_argument(split_config, 'seed', 42)
25
+ edge_splitter_test = EdgeSplitter(G, seed=seed)
26
+ for (G_train, ratings_test) in edge_splitter_test.split(**split_config['test']):
27
+ dataset = Dataset()
28
+ dataset.set_test_data(ratings_test)
29
+ print(f'\tDisturbed graph info after splitting test data: {G_train.info()}')
30
+ print(f'\tNumber of test instances: {len_instances(ratings_test)}')
31
+
32
+ if get_optional_argument(split_config, 'validation', False):
33
+ if split_config['test']['method'] == 'k_fold' and split_config['validation']['method'] == 'k_fold':
34
+ raise ValueError("Validation split does not support k_fold method.")
35
+
36
+ edge_splitter_val = EdgeSplitter(G_train, seed=seed)
37
+ for G_train, ratings_val in edge_splitter_val.split(**split_config['validation']):
38
+ dataset.set_val_data(ratings_val)
39
+ print(f'\tGraph info after splitting validation data: {G_train.info()}')
40
+ print(f'\tNumber of validation instances: {len_instances(ratings_val)}')
41
+
42
+ ratings_train = G_train.get_ratings_with_labels()
43
+ dataset.set_train_data(G_train, ratings_train)
44
+ print(f'Number of training instances: {len_instances(ratings_train)}')
45
+
46
+ yield dataset
47
+
48
+ def len_instances(data):
49
+ return sum([len(items) for items in data.values()])
50
+
51
+
52
+
File without changes
@@ -0,0 +1,38 @@
1
+ class Dataset():
2
+ def __init__(self):
3
+ self.G_train = None
4
+ self.ratings_train = None # {user: [(item1, rating), ... } Unordered items
5
+ self.ratings_test = None
6
+ self.has_val = False
7
+ self.ratings_val = None
8
+
9
+ def set_train_data(self, G, ratings):
10
+ self.G_train = G
11
+ self._sort_ratings(ratings)
12
+ self.ratings_train = ratings
13
+
14
+ def set_test_data(self, ratings):
15
+ self._sort_ratings(ratings)
16
+ self.ratings_test = ratings
17
+
18
+ def set_val_data(self, ratings):
19
+ self.has_val = True
20
+ self._sort_ratings(ratings)
21
+ self.ratings_val = ratings
22
+
23
+ def get_train_data(self):
24
+ return self.G_train, self.ratings_train
25
+
26
+ def get_test_data(self):
27
+ return self.ratings_test
28
+
29
+ def get_val_data(self):
30
+ return self.ratings_val
31
+
32
+ def has_val_data(self):
33
+ return self.has_val
34
+
35
+ def _sort_ratings(self, ratings):
36
+ # inplace sorting
37
+ for u in ratings.keys():
38
+ ratings[u].sort(reverse=True, key=lambda x: x[1])
File without changes
@@ -0,0 +1,163 @@
1
+ from typing import Tuple, Dict, List
2
+
3
+ import random
4
+ import math
5
+ from copy import deepcopy
6
+ from collections import defaultdict
7
+
8
+ from ..graph.graph import Graph
9
+ from ..graph.node import UserNode, ItemNode
10
+
11
+ import numpy as np
12
+ from sklearn.model_selection import KFold
13
+
14
+
15
+ class EdgeSplitter():
16
+ def __init__(self, G: Graph, seed : int = 42):
17
+ self.G = deepcopy(G)
18
+ self.random_state = seed
19
+ random.seed(seed)
20
+ np.random.seed(seed)
21
+
22
+ self.supported_methods = [
23
+ 'random_by_ratio',
24
+ 'timestamp_by_ratio',
25
+ 'fixed_timestamp',
26
+ 'k_fold'
27
+ ]
28
+
29
+ def split(self, **config) -> Tuple[Graph, Dict[UserNode, List[Tuple[ItemNode, float]]]]:
30
+ method = config['method']
31
+ if method not in self.supported_methods:
32
+ raise ValueError(f'Invalid split method provided. So far, the supported ones are:\n{self.supported_methods}')
33
+
34
+ if method in ['random_by_ratio', 'timestamp_by_ratio']:
35
+ p = config['p']
36
+ level = config["level"]
37
+ if p <= 0 or p >= 1:
38
+ raise ValueError('The parameter p must be in the interval (0,1)')
39
+
40
+ if level not in ['user', 'global']:
41
+ raise ValueError('Invalid level parameter for splitting. Choose between user and global level.')
42
+
43
+ print(f'Splitting data using {method} method at {level} level, where p={p} ...')
44
+
45
+ if method == 'random_by_ratio':
46
+ test = self._random_by_ratio(p, level)
47
+ elif method == 'timestamp_by_ratio':
48
+ test = self._timestamp_by_ratio(p, level)
49
+
50
+ yield self._extract_dataset(test)
51
+
52
+ elif method == 'fixed_timestamp':
53
+ print(f'Splitting data using {method} method, where timestamp={config["timestamp"]} ...')
54
+
55
+ test = []
56
+ for (u,v) in self.G.get_rating_edges():
57
+ data = self.G.get_edge_data(u,v)
58
+ if data['timestamp'] > config['timestamp']:
59
+ test.append((u,v))
60
+ yield self._extract_dataset(test)
61
+
62
+ elif method == 'k_fold':
63
+ k = config['k']
64
+ if k < 2:
65
+ raise ValueError('k parameter for K-fold must be at least 2.')
66
+
67
+ level = config['level']
68
+ if level not in ['user', 'global']:
69
+ raise ValueError('Invalid level parameter for splitting. Choose between user and global level.')
70
+
71
+
72
+ for test in self._kfold(k, level):
73
+ yield self._extract_dataset(test)
74
+
75
+ def _extract_dataset(self, test):
76
+ # Returns Graph, {user: [(item1, rating1), (item2, rating2) ...]}
77
+ ratings = defaultdict(list)
78
+ for (u, v) in test:
79
+ data = self.G.get_edge_data(u,v)
80
+ self.G.remove_edge(u,v)
81
+ ratings[u].append((v, data['rating']))
82
+
83
+ return self.G, ratings
84
+
85
+ def _random_by_ratio(self, p: float, level: str) -> np.array:
86
+ if level == 'global':
87
+ ratings = np.array(list(self.G.get_rating_edges()))
88
+ np.random.shuffle(ratings)
89
+ n_test = math.ceil(ratings.shape[0] * p)
90
+
91
+ return ratings[-n_test:]
92
+ elif level == 'user':
93
+ test = []
94
+ for user, items in self.G.rating_edges.items():
95
+ # Number of test items for user
96
+ items = np.array(list(items))
97
+ np.random.shuffle(items)
98
+ n_test = math.ceil(items.shape[0] * p)
99
+ test += [(user, item) for item in items[-n_test:]]
100
+
101
+ return np.array(test)
102
+
103
+ def _timestamp_by_ratio(self, p: float, level: str) -> np.array:
104
+ if level == 'global':
105
+ ratings = list(self.G.get_rating_edges())
106
+ ratings = self._sort_by_timestamp(ratings)
107
+ ratings = np.array(ratings)
108
+ n_test = math.ceil(ratings.shape[0] * p)
109
+
110
+ return ratings[-n_test:]
111
+ elif level == 'user':
112
+ test = []
113
+ for user, items in self.G.rating_edges.items():
114
+ ratings = [(user, item) for item in items]
115
+ ratings = self._sort_by_timestamp(ratings)
116
+ n_test = math.ceil(len(ratings) * p)
117
+ test += ratings[-n_test:]
118
+
119
+ return np.array(test)
120
+
121
+ def _kfold(self, k : int, level : str):
122
+ G_Copy = deepcopy(self.G)
123
+
124
+ if level == 'global':
125
+ ratings = list(self.G.get_rating_edges())
126
+ ratings = np.array(ratings)
127
+ np.random.shuffle(ratings)
128
+ kfold = KFold(n_splits=k)
129
+ for _, test_index in kfold.split(ratings):
130
+ yield ratings[test_index]
131
+ self.G = deepcopy(G_Copy)
132
+
133
+ elif level == 'user':
134
+ kfold_users, ratings_per_user = [], []
135
+ for user, items in self.G.rating_edges.items():
136
+ items = np.array(list(items))
137
+ np.random.shuffle(items)
138
+ kfold = KFold(n_splits=k).split(items)
139
+ kfold_users.append(kfold)
140
+ edges = np.array([(user, item) for item in items])
141
+ ratings_per_user.append(edges)
142
+
143
+ for _ in range(k):
144
+ test = []
145
+ for idx, kfold in enumerate(kfold_users):
146
+ _, test_index = next(kfold)
147
+ test_ratings = ratings_per_user[idx][test_index].tolist()
148
+ test += test_ratings
149
+ test = np.array(test)
150
+ yield test
151
+ self.G = deepcopy(G_Copy)
152
+
153
+ def _sort_by_timestamp(self, ratings: list) -> list:
154
+ # globally sort the ratings by timestamp
155
+ f = lambda edge: self.G.get_edge_data(edge[0], edge[1])['timestamp']
156
+ return sorted(ratings, key=f)
157
+
158
+
159
+
160
+
161
+
162
+
163
+
File without changes