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.
- FranKGraphBench-0.0.1a0.dist-info/LICENSE +201 -0
- FranKGraphBench-0.0.1a0.dist-info/METADATA +189 -0
- FranKGraphBench-0.0.1a0.dist-info/RECORD +70 -0
- FranKGraphBench-0.0.1a0.dist-info/WHEEL +6 -0
- FranKGraphBench-0.0.1a0.dist-info/entry_points.txt +3 -0
- FranKGraphBench-0.0.1a0.dist-info/top_level.txt +2 -0
- data_integration/__init__.py +47 -0
- data_integration/__main__.py +4 -0
- data_integration/dataset.py +270 -0
- data_integration/dataset2class.py +34 -0
- data_integration/datasets/__init__.py +0 -0
- data_integration/datasets/amazon.py +140 -0
- data_integration/datasets/book-crossing.py +98 -0
- data_integration/datasets/douban-movie.py +75 -0
- data_integration/datasets/lastfm.py +215 -0
- data_integration/datasets/movielens.py +375 -0
- data_integration/datasets/steam.py +98 -0
- data_integration/datasets/worker.py +31 -0
- data_integration/datasets/yelp.py +156 -0
- framework/__init__.py +17 -0
- framework/__main__.py +4 -0
- framework/dataloader/__init__.py +0 -0
- framework/dataloader/dataloader.py +52 -0
- framework/dataloader/dataset/__init__.py +0 -0
- framework/dataloader/dataset/dataset.py +38 -0
- framework/dataloader/edge_splitter/__init__.py +0 -0
- framework/dataloader/edge_splitter/edge_splitter.py +163 -0
- framework/dataloader/graph/__init__.py +0 -0
- framework/dataloader/graph/graph.py +314 -0
- framework/dataloader/graph/node.py +80 -0
- framework/dataloader/preprocess/__init__.py +0 -0
- framework/dataloader/preprocess/methods.py +47 -0
- framework/evaluator/__init__.py +0 -0
- framework/evaluator/metric.py +39 -0
- framework/evaluator/metric2class.py +10 -0
- framework/evaluator/metrics/__init__.py +0 -0
- framework/evaluator/metrics/map.py +33 -0
- framework/evaluator/metrics/ndcg.py +49 -0
- framework/experiment.py +120 -0
- framework/recommender/__init__.py +0 -0
- framework/recommender/model2class.py +46 -0
- framework/recommender/models/__init__.py +0 -0
- framework/recommender/models/complEx/__init__.py +0 -0
- framework/recommender/models/complEx/model.py +193 -0
- framework/recommender/models/deep_walk_based/__init__.py +0 -0
- framework/recommender/models/deep_walk_based/model.py +149 -0
- framework/recommender/models/distMult/__init__.py +0 -0
- framework/recommender/models/distMult/model.py +187 -0
- framework/recommender/models/ePHEN/__init__.py +0 -0
- framework/recommender/models/ePHEN/model.py +159 -0
- framework/recommender/models/rESCAL/__init__.py +0 -0
- framework/recommender/models/rESCAL/model.py +184 -0
- framework/recommender/models/rotatE/__init__.py +0 -0
- framework/recommender/models/rotatE/model.py +195 -0
- framework/recommender/models/transD/__init__.py +0 -0
- framework/recommender/models/transD/model.py +190 -0
- framework/recommender/models/transE/__init__.py +0 -0
- framework/recommender/models/transE/model.py +194 -0
- framework/recommender/models/transH/__init__.py +0 -0
- framework/recommender/models/transH/model.py +193 -0
- framework/recommender/models/transR/__init__.py +0 -0
- framework/recommender/models/transR/model.py +190 -0
- framework/recommender/models/tuckER/__init__.py +0 -0
- framework/recommender/models/tuckER/model.py +199 -0
- framework/recommender/recommender.py +32 -0
- framework/recommender/utils/__init__.py +0 -0
- framework/recommender/utils/walker.py +185 -0
- framework/reporter/__init__.py +0 -0
- framework/reporter/report.py +101 -0
- framework/utils.py +10 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import queue
|
|
3
|
+
import string
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from string import Template
|
|
6
|
+
from .datasets.worker import Worker
|
|
7
|
+
|
|
8
|
+
from SPARQLWrapper import SPARQLWrapper, JSON
|
|
9
|
+
from tqdm import tqdm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Dataset:
|
|
13
|
+
def __init__(self, input_path, output_path, n_workers=1):
|
|
14
|
+
r"""
|
|
15
|
+
Base class for Recommender System's datasets
|
|
16
|
+
"""
|
|
17
|
+
self.input_path = input_path
|
|
18
|
+
self.output_path = output_path
|
|
19
|
+
self.n_workers = n_workers
|
|
20
|
+
|
|
21
|
+
self.sparql_endpoint = "http://dbpedia.org/sparql"
|
|
22
|
+
self.timeout = 180
|
|
23
|
+
|
|
24
|
+
# Output files
|
|
25
|
+
self.item_filename = os.path.join(self.output_path, "item.csv")
|
|
26
|
+
self.user_filename = os.path.join(self.output_path, "user.csv")
|
|
27
|
+
self.rating_filename = os.path.join(self.output_path, "rating.csv")
|
|
28
|
+
self.social_filename = os.path.join(self.output_path, "social.csv")
|
|
29
|
+
self.map_filename = os.path.join(self.output_path, "map.csv")
|
|
30
|
+
self.enriched_filename = os.path.join(self.output_path, "enriched.csv")
|
|
31
|
+
|
|
32
|
+
# create output path if doesn't exist
|
|
33
|
+
self.has_output_path()
|
|
34
|
+
# Mapping regex special chars
|
|
35
|
+
self._special_chars_map = str.maketrans({x: "" for x in string.punctuation})
|
|
36
|
+
|
|
37
|
+
# The following variables need to be overwritten by the subclasses of Dataset()
|
|
38
|
+
self.dataset_name = ""
|
|
39
|
+
self.item_separator = ""
|
|
40
|
+
self.user_separator = ""
|
|
41
|
+
self.rating_separator = ""
|
|
42
|
+
self.social_separator = ""
|
|
43
|
+
# Chosen fields to be extracted from dataset and mapping between the original column name and
|
|
44
|
+
# their standardized name
|
|
45
|
+
# Example: {"movie_title": "movie_title::string"}
|
|
46
|
+
self.reverse_dict = lambda x: {v: k for k, v in x.items()}
|
|
47
|
+
self.item_fields = {}
|
|
48
|
+
self.user_fields = {}
|
|
49
|
+
self.rating_fields = {}
|
|
50
|
+
self.map_query_template = Template(None)
|
|
51
|
+
self.enrich_query_template = Template(None)
|
|
52
|
+
|
|
53
|
+
def has_output_path(self):
|
|
54
|
+
if not os.path.isdir(self.output_path):
|
|
55
|
+
os.mkdir(self.output_path)
|
|
56
|
+
|
|
57
|
+
def load_item_data(self) -> pd.DataFrame:
|
|
58
|
+
"""
|
|
59
|
+
Loads item info of Dataset
|
|
60
|
+
:return: returns pd.Dataframe containing each item info.
|
|
61
|
+
"""
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
|
|
64
|
+
def load_user_data(self) -> pd.DataFrame:
|
|
65
|
+
"""
|
|
66
|
+
Loads user info of Dataset
|
|
67
|
+
:return: returns pd.Dataframe containing each user info.
|
|
68
|
+
"""
|
|
69
|
+
raise NotImplementedError
|
|
70
|
+
|
|
71
|
+
def load_rating_data(self) -> pd.DataFrame:
|
|
72
|
+
"""
|
|
73
|
+
Loads rating interactions of Dataset
|
|
74
|
+
:return: returns pd.Dataframe containing each rating interaction.
|
|
75
|
+
"""
|
|
76
|
+
raise NotImplementedError
|
|
77
|
+
|
|
78
|
+
def load_social_data(self) -> pd.DataFrame:
|
|
79
|
+
"""
|
|
80
|
+
Loads social links of Dataset
|
|
81
|
+
:return: returns pd.Dataframe containing each social link between users.
|
|
82
|
+
"""
|
|
83
|
+
raise NotImplementedError
|
|
84
|
+
|
|
85
|
+
def entity_linking(self, df_item) -> pd.DataFrame:
|
|
86
|
+
"""
|
|
87
|
+
Entity link each item to their corresponding DBpedia's URI
|
|
88
|
+
:return: returns pd.Dataframe containing each item_id and their mapped URI
|
|
89
|
+
"""
|
|
90
|
+
raise NotImplementedError
|
|
91
|
+
|
|
92
|
+
def enrich(self, df_map):
|
|
93
|
+
raise NotImplementedError
|
|
94
|
+
|
|
95
|
+
def get_map_query(self, *args, **kwargs) -> str:
|
|
96
|
+
"""
|
|
97
|
+
Returns query parameters to be substituted in the map query template.
|
|
98
|
+
:returns: returns string query
|
|
99
|
+
"""
|
|
100
|
+
raise NotImplementedError
|
|
101
|
+
|
|
102
|
+
def get_enrich_query(self, *args, **kwargs) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Returns query parameters to be substituted in the enrich query template.
|
|
105
|
+
:returns: returns string query
|
|
106
|
+
"""
|
|
107
|
+
raise NotImplementedError
|
|
108
|
+
|
|
109
|
+
def parallel_queries(self, queue, return_type=JSON):
|
|
110
|
+
"""
|
|
111
|
+
Parallel query SPARQL endpoint using Threads
|
|
112
|
+
:arguments:
|
|
113
|
+
queue: queue of tuples indicating the item_id and SPARQL query string
|
|
114
|
+
:returns: list containing tuples with item_id and response result in JSON
|
|
115
|
+
"""
|
|
116
|
+
n_iters = queue.qsize()
|
|
117
|
+
pbar = tqdm(total=n_iters)
|
|
118
|
+
pbar.set_description("Requesting SPARQL endpoint for each item")
|
|
119
|
+
|
|
120
|
+
# instantiating each Worker
|
|
121
|
+
workers = []
|
|
122
|
+
for _ in range(self.n_workers):
|
|
123
|
+
worker = Worker(queue, lambda q: self._query(q, return_type), pbar)
|
|
124
|
+
worker.start()
|
|
125
|
+
workers.append(worker)
|
|
126
|
+
|
|
127
|
+
for worker in workers:
|
|
128
|
+
worker.join()
|
|
129
|
+
pbar.close()
|
|
130
|
+
|
|
131
|
+
# combining results
|
|
132
|
+
responses = []
|
|
133
|
+
for worker in workers:
|
|
134
|
+
responses.extend(worker.local_results)
|
|
135
|
+
|
|
136
|
+
return responses
|
|
137
|
+
|
|
138
|
+
def sequential_queries(self, q, return_type=JSON):
|
|
139
|
+
"""
|
|
140
|
+
Sequential query SPARQL endpoint
|
|
141
|
+
:arguments:
|
|
142
|
+
queue: queue of tuples indicating the item_id and SPARQL query string
|
|
143
|
+
:returns: list containing tuples with item_id and response result in JSON
|
|
144
|
+
"""
|
|
145
|
+
pbar = tqdm(total=q.qsize())
|
|
146
|
+
pbar.set_description("Requesting SPARQL endpoint for each item")
|
|
147
|
+
responses = []
|
|
148
|
+
|
|
149
|
+
while True:
|
|
150
|
+
try:
|
|
151
|
+
idx, query = q.get(block=False)
|
|
152
|
+
response = self._query(query, return_type)
|
|
153
|
+
responses.append((idx, response))
|
|
154
|
+
pbar.update(n=1)
|
|
155
|
+
except queue.Empty:
|
|
156
|
+
break
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(f"Exception:")
|
|
159
|
+
print(e)
|
|
160
|
+
|
|
161
|
+
return responses
|
|
162
|
+
|
|
163
|
+
def _query(self, query, return_type=JSON) -> dict:
|
|
164
|
+
sparql = SPARQLWrapper(self.sparql_endpoint)
|
|
165
|
+
sparql.setTimeout(self.timeout)
|
|
166
|
+
sparql.setQuery(query)
|
|
167
|
+
sparql.setReturnFormat(return_type)
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
return sparql.query().convert()
|
|
171
|
+
except Exception as e:
|
|
172
|
+
raise e
|
|
173
|
+
|
|
174
|
+
def convert_item_data(self):
|
|
175
|
+
"""
|
|
176
|
+
Converts loaded item data to a processed csv (item.csv).
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
print(f"Creating file: {self.item_filename}.")
|
|
180
|
+
df_item = self.load_item_data()
|
|
181
|
+
print(f"{df_item.shape[0]} items with {df_item.shape[1]} Fields")
|
|
182
|
+
print("Fields: " + ", ".join(df_item.columns))
|
|
183
|
+
df_item.to_csv(self.item_filename, index=False)
|
|
184
|
+
except NotImplementedError:
|
|
185
|
+
print("Override load_item_data() method of your Dataset subclass.")
|
|
186
|
+
|
|
187
|
+
def convert_user_data(self):
|
|
188
|
+
"""
|
|
189
|
+
Converts loaded user data to a processed csv (user.csv).
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
print(f"Creating file: {self.user_filename}")
|
|
193
|
+
df_user = self.load_user_data()
|
|
194
|
+
print(f"{df_user.shape[0]} users with {df_user.shape[1]} Fields")
|
|
195
|
+
print("Fields: " + ", ".join(df_user.columns))
|
|
196
|
+
df_user.to_csv(self.user_filename, index=False)
|
|
197
|
+
except NotImplementedError:
|
|
198
|
+
print(f"Override load_user_data() of your Dataset subclass.")
|
|
199
|
+
|
|
200
|
+
def convert_rating_data(self):
|
|
201
|
+
"""
|
|
202
|
+
Converts loaded rating data to a processed csv (rating.csv)
|
|
203
|
+
"""
|
|
204
|
+
try:
|
|
205
|
+
print(f"Creating file: {self.rating_filename}")
|
|
206
|
+
df_rating = self.load_rating_data()
|
|
207
|
+
print(f"{df_rating.shape[0]} ratings with {df_rating.shape[1]} Fields")
|
|
208
|
+
print("Fields: " + ", ".join(df_rating.columns))
|
|
209
|
+
df_rating.to_csv(self.rating_filename, index=False)
|
|
210
|
+
except NotImplementedError:
|
|
211
|
+
print(f"Override load_rating_data() of your Dataset subclass.")
|
|
212
|
+
|
|
213
|
+
def convert_social_data(self):
|
|
214
|
+
"""
|
|
215
|
+
Converts loaded social data to a processed csv (social.csv)
|
|
216
|
+
"""
|
|
217
|
+
try:
|
|
218
|
+
print(f"Creating file: {self.social_filename}")
|
|
219
|
+
df_social = self.load_social_data()
|
|
220
|
+
print(f"{df_social.shape[0]} social link between users")
|
|
221
|
+
df_social.to_csv(self.social_filename, index=False)
|
|
222
|
+
except NotImplementedError:
|
|
223
|
+
print(f"Override load_rating_data() of your Dataset subclass.")
|
|
224
|
+
|
|
225
|
+
def map_URIs(self):
|
|
226
|
+
"""
|
|
227
|
+
Maps each item to their corresponding DBpedia's URI.
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
df_item = pd.read_csv(self.item_filename)
|
|
231
|
+
df_item.columns = [col.split("::")[0] for col in df_item.columns]
|
|
232
|
+
print(f"Mapping each dataset item with DBpedia: {self.map_filename}")
|
|
233
|
+
df_map = self.entity_linking(df_item)
|
|
234
|
+
df_map.to_csv(self.map_filename, index=False)
|
|
235
|
+
|
|
236
|
+
# print mapping statistics
|
|
237
|
+
n_items = df_map.shape[0]
|
|
238
|
+
n_unmatched = df_map[self.map_fields["URI"]].isna().sum()
|
|
239
|
+
print(
|
|
240
|
+
f"{n_unmatched} items weren't matched, corresponding to {n_unmatched/n_items*100:.2f}% of a total of {n_items}."
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
except NotImplementedError:
|
|
244
|
+
print("Override entity_linking() method of your Dataset subclass.")
|
|
245
|
+
|
|
246
|
+
def enrich_data(self):
|
|
247
|
+
"""
|
|
248
|
+
Enrich each mapped item using DBpedia's resources.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
df_map = pd.read_csv(self.map_filename)
|
|
253
|
+
print(
|
|
254
|
+
f"Enriching each item with DBpedia resources: {self.enriched_filename}"
|
|
255
|
+
)
|
|
256
|
+
df_enrich = self.enrich(df_map)
|
|
257
|
+
df_enrich.to_csv(self.enriched_filename)
|
|
258
|
+
|
|
259
|
+
self.report_enriching(df_enrich)
|
|
260
|
+
|
|
261
|
+
except NotImplementedError:
|
|
262
|
+
print("Override enrich() method of your Dataset subclass.")
|
|
263
|
+
|
|
264
|
+
def report_enriching(self, df):
|
|
265
|
+
n_rows = df.shape[0]
|
|
266
|
+
for col in df.columns:
|
|
267
|
+
n_rows_with = df[col].notna().sum()
|
|
268
|
+
print(
|
|
269
|
+
f"number of entities with the property {col}: {n_rows_with} ({n_rows_with/n_rows*100:.2f}%)"
|
|
270
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
dataset2class = {
|
|
2
|
+
'ml-100k': {
|
|
3
|
+
'submodule': 'datasets.movielens',
|
|
4
|
+
'class': 'MovieLens100k'
|
|
5
|
+
},
|
|
6
|
+
'ml-1m': {
|
|
7
|
+
'submodule': 'datasets.movielens',
|
|
8
|
+
'class': 'MovieLens1M'
|
|
9
|
+
},
|
|
10
|
+
'lastfm': {
|
|
11
|
+
'submodule': 'datasets.lastfm',
|
|
12
|
+
'class': 'LastFM'
|
|
13
|
+
},
|
|
14
|
+
'book-crossing': {
|
|
15
|
+
'submodule': 'datasets.book-crossing',
|
|
16
|
+
'class': 'BookCrossing'
|
|
17
|
+
},
|
|
18
|
+
'steam': {
|
|
19
|
+
'submodule': 'datasets.steam',
|
|
20
|
+
'class': 'Steam'
|
|
21
|
+
},
|
|
22
|
+
'yelp': {
|
|
23
|
+
'submodule': 'datasets.yelp',
|
|
24
|
+
'class': 'Yelp'
|
|
25
|
+
},
|
|
26
|
+
'douban-movie': {
|
|
27
|
+
'submodule': 'datasets.douban-movie',
|
|
28
|
+
'class': 'DoubanMovie'
|
|
29
|
+
},
|
|
30
|
+
'amazon-video_games-5': {
|
|
31
|
+
'submodule': 'datasets.amazon',
|
|
32
|
+
'class': 'AmazonVideoGames5'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import gzip
|
|
3
|
+
import json
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import re
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from bs4 import BeautifulSoup
|
|
8
|
+
|
|
9
|
+
from ..dataset import Dataset
|
|
10
|
+
|
|
11
|
+
class Amazon(Dataset):
|
|
12
|
+
"""
|
|
13
|
+
General Amazon(Dataset) class for data loading all different Amazon datasets
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, input_path, output_path, n_workers=1):
|
|
16
|
+
super().__init__(input_path, output_path, n_workers)
|
|
17
|
+
|
|
18
|
+
def _parse(self, path):
|
|
19
|
+
g = gzip.open(path, 'rb')
|
|
20
|
+
for l in g:
|
|
21
|
+
yield json.loads(l)
|
|
22
|
+
|
|
23
|
+
def _getDF(self, path):
|
|
24
|
+
i = 0
|
|
25
|
+
df = {}
|
|
26
|
+
for d in self._parse(path):
|
|
27
|
+
df[i] = d
|
|
28
|
+
i += 1
|
|
29
|
+
return pd.DataFrame.from_dict(df, orient='index')
|
|
30
|
+
|
|
31
|
+
class AmazonVideoGames5(Amazon):
|
|
32
|
+
def __init__(self, input_path, output_path, n_workers=1):
|
|
33
|
+
super().__init__(input_path, output_path, n_workers)
|
|
34
|
+
self.dataset_name = "Amazon Video Games 5-core"
|
|
35
|
+
|
|
36
|
+
self.string_list_separator = "::"
|
|
37
|
+
|
|
38
|
+
self.item_fields = {
|
|
39
|
+
"asin": "item_id::string",
|
|
40
|
+
"title": "name::string",
|
|
41
|
+
"feature": "features::string_list",
|
|
42
|
+
"description": "description::string",
|
|
43
|
+
"price": "price::number",
|
|
44
|
+
"also_buy": "also_buy::string_list",
|
|
45
|
+
"also_view": "also_view::string_list",
|
|
46
|
+
"brand": "brand::string",
|
|
47
|
+
"category": "categories::string_list"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
self.user_fields = {
|
|
51
|
+
"reviewerID": "user_id::string",
|
|
52
|
+
"reviewerName": "name::string",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
self.rating_fields = {
|
|
56
|
+
"reviewerID": "user_id::string",
|
|
57
|
+
"asin": "item_id::string",
|
|
58
|
+
"reviewText": "text::string",
|
|
59
|
+
"overall": "rating::number",
|
|
60
|
+
"summary": "summary::string",
|
|
61
|
+
"unixReviewTime": "timestamp::number"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def load_item_data(self) -> pd.DataFrame():
|
|
65
|
+
filename = os.path.join(self.input_path, "Video_Games_5.json.gz")
|
|
66
|
+
print(filename)
|
|
67
|
+
df = self._getDF(filename)
|
|
68
|
+
|
|
69
|
+
filename = os.path.join(self.input_path, "meta_Video_Games.json.gz")
|
|
70
|
+
print(filename)
|
|
71
|
+
meta_df = self._getDF(filename)
|
|
72
|
+
meta_df = meta_df[meta_df['asin'].isin(df['asin'].unique())].reset_index(drop=True)
|
|
73
|
+
|
|
74
|
+
features = defaultdict(list)
|
|
75
|
+
for index, row in meta_df['feature'].dropna().items():
|
|
76
|
+
for feature in row:
|
|
77
|
+
soup = BeautifulSoup(feature, 'html.parser')
|
|
78
|
+
feature = soup.text
|
|
79
|
+
if "" != feature:
|
|
80
|
+
features[index].append(feature)
|
|
81
|
+
meta_df['feature'] = pd.Series(features).apply(lambda x: self.string_list_separator.join(x))
|
|
82
|
+
|
|
83
|
+
descriptions = defaultdict(list)
|
|
84
|
+
for index, row in meta_df['description'].dropna().items():
|
|
85
|
+
for description in row:
|
|
86
|
+
soup = BeautifulSoup(description, 'html.parser')
|
|
87
|
+
description = soup.text
|
|
88
|
+
if "" != description:
|
|
89
|
+
descriptions[index].append(description)
|
|
90
|
+
meta_df['description'] = pd.Series(descriptions).apply(lambda x: " ".join(x))
|
|
91
|
+
|
|
92
|
+
numeric_const_pattern = '[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?'
|
|
93
|
+
rx = re.compile(numeric_const_pattern, re.VERBOSE)
|
|
94
|
+
meta_df['price'] = meta_df['price'].apply(lambda x: rx.findall(x))
|
|
95
|
+
meta_df['price'] = meta_df['price'].apply(lambda x: x[0] if len(x) > 0 else None)
|
|
96
|
+
meta_df['price'] = meta_df['price'].astype('float64')
|
|
97
|
+
|
|
98
|
+
also_buys = defaultdict(list)
|
|
99
|
+
for index, row in meta_df['also_buy'].dropna().items():
|
|
100
|
+
for also_buy in row:
|
|
101
|
+
also_buys[index].append(also_buy)
|
|
102
|
+
meta_df['also_buy'] = pd.Series(also_buys).apply(lambda x: self.string_list_separator.join(x))
|
|
103
|
+
|
|
104
|
+
also_views = defaultdict(list)
|
|
105
|
+
for index, row in meta_df['also_view'].dropna().items():
|
|
106
|
+
for also_view in row:
|
|
107
|
+
also_views[index].append(also_view)
|
|
108
|
+
meta_df['also_view'] = pd.Series(also_views).apply(lambda x: self.string_list_separator.join(x))
|
|
109
|
+
|
|
110
|
+
categories = defaultdict(list)
|
|
111
|
+
for index, row in meta_df['category'].dropna().items():
|
|
112
|
+
for category in row:
|
|
113
|
+
soup = BeautifulSoup(category, 'html.parser')
|
|
114
|
+
category = soup.text
|
|
115
|
+
if "" != category:
|
|
116
|
+
categories[index].append(category)
|
|
117
|
+
meta_df['category'] = pd.Series(categories).apply(lambda x: self.string_list_separator.join(x))
|
|
118
|
+
|
|
119
|
+
meta_df = meta_df.rename(self.item_fields, axis=1)
|
|
120
|
+
meta_df = meta_df[self.item_fields.values()]
|
|
121
|
+
return meta_df
|
|
122
|
+
|
|
123
|
+
def load_user_data(self) -> pd.DataFrame():
|
|
124
|
+
filename = os.path.join(self.input_path, "Video_Games_5.json.gz")
|
|
125
|
+
print(filename)
|
|
126
|
+
df = self._getDF(filename)
|
|
127
|
+
|
|
128
|
+
df = df.rename(self.user_fields, axis=1)
|
|
129
|
+
df = df[self.user_fields.values()]
|
|
130
|
+
return df
|
|
131
|
+
|
|
132
|
+
def load_rating_data(self) -> pd.DataFrame():
|
|
133
|
+
filename = os.path.join(self.input_path, "Video_Games_5.json.gz")
|
|
134
|
+
print(filename)
|
|
135
|
+
df = self._getDF(filename)
|
|
136
|
+
|
|
137
|
+
df = df.rename(self.rating_fields, axis=1)
|
|
138
|
+
df = df[self.rating_fields.values()]
|
|
139
|
+
return df
|
|
140
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import queue
|
|
4
|
+
from string import Template
|
|
5
|
+
from ..dataset import Dataset
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from tqdm import tqdm
|
|
9
|
+
from thefuzz import process
|
|
10
|
+
|
|
11
|
+
class BookCrossing(Dataset):
|
|
12
|
+
def __init__(self, input_path, output_path, n_workers=1):
|
|
13
|
+
super().__init__(input_path, output_path, n_workers)
|
|
14
|
+
self.dataset_name = 'Book-Crossing'
|
|
15
|
+
|
|
16
|
+
self.item_separator = ';'
|
|
17
|
+
self.user_separator = ';'
|
|
18
|
+
self.rating_separator = ';'
|
|
19
|
+
|
|
20
|
+
self.item_fields = ['item_id', 'title', 'author', 'year', 'publisher']
|
|
21
|
+
# self.user_fields = ['user_id', 'age', 'gender', 'occupation']
|
|
22
|
+
self.rating_fields = ['user_id', 'item_id', 'rating']
|
|
23
|
+
# self.map_fields = ['item_id', 'URI']
|
|
24
|
+
|
|
25
|
+
self.map_query_template = Template('''
|
|
26
|
+
PREFIX dct: <http://purl.org/dc/terms/>
|
|
27
|
+
PREFIX dbo: <http://dbpedia.org/ontology/>
|
|
28
|
+
PREFIX dbr: <http://dbpedia.org/resource/>
|
|
29
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
30
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
31
|
+
SELECT DISTINCT ?book WHERE {
|
|
32
|
+
{
|
|
33
|
+
?book rdf:type dbo:WrittenWork .
|
|
34
|
+
?book rdfs:label ?label .
|
|
35
|
+
FILTER regex(?label, "$name_regex", "i")
|
|
36
|
+
}
|
|
37
|
+
UNION
|
|
38
|
+
{
|
|
39
|
+
?book rdf:type dbo:WrittenWork .
|
|
40
|
+
?tmp dbo:wikiPageRedirects ?book .
|
|
41
|
+
?tmp rdfs:label ?label .
|
|
42
|
+
FILTER regex(?label, "$name_regex", "i") .
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
''')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_item_data(self) -> pd.DataFrame():
|
|
49
|
+
filename = os.path.join(self.input_path, 'BX-Books.csv')
|
|
50
|
+
|
|
51
|
+
df = pd.read_csv(filename, sep=self.item_separator, encoding='CP1252', escapechar='\\')
|
|
52
|
+
df = df.drop(df.columns[-3:], axis=1) # Will not use picture URL related fields
|
|
53
|
+
df.columns = self.item_fields
|
|
54
|
+
return df
|
|
55
|
+
|
|
56
|
+
def entity_linking(self, df_item) -> pd.DataFrame():
|
|
57
|
+
q = queue.Queue()
|
|
58
|
+
for idx, row in df_item[['title', 'item_id']].iterrows():
|
|
59
|
+
query = self.get_map_query(row['title'])
|
|
60
|
+
q.put((row['item_id'], query))
|
|
61
|
+
|
|
62
|
+
if self.n_workers > 1:
|
|
63
|
+
responses = self.parallel_queries(q)
|
|
64
|
+
else:
|
|
65
|
+
responses = self.sequential_queries(q)
|
|
66
|
+
|
|
67
|
+
URI_mapping = {}
|
|
68
|
+
for response in tqdm(responses, desc='Disambiguating query return'):
|
|
69
|
+
candidate_URIs = []
|
|
70
|
+
idx, result = response
|
|
71
|
+
for binding in result['results']['bindings']:
|
|
72
|
+
URI = binding['book']['value']
|
|
73
|
+
candidate_URIs.append(URI)
|
|
74
|
+
|
|
75
|
+
expected_URI = f'http://dbpedia.org/resource/{df_item.loc[df_item.item_id == idx]["title"]}'
|
|
76
|
+
str_matching_result = process.extractOne(expected_URI, candidate_URIs)
|
|
77
|
+
|
|
78
|
+
if str_matching_result is not None:
|
|
79
|
+
URI, _ = str_matching_result
|
|
80
|
+
URI_mapping[idx] = URI
|
|
81
|
+
|
|
82
|
+
df_map = pd.DataFrame({'item_id': df_item['item_id']})
|
|
83
|
+
df_map.set_index('item_id')
|
|
84
|
+
df_map['URI'] = df_map['item_id'].apply(lambda id: URI_mapping.get(id))
|
|
85
|
+
|
|
86
|
+
return df_map
|
|
87
|
+
|
|
88
|
+
def get_map_query(self, book_title) -> str:
|
|
89
|
+
book_title = book_title.translate(self._special_chars_map)
|
|
90
|
+
book_title = book_title.replace(' ', '.*')
|
|
91
|
+
book_title = '^' + book_title
|
|
92
|
+
|
|
93
|
+
params = {'name_regex': book_title}
|
|
94
|
+
query = self.map_query_template.substitute(**params)
|
|
95
|
+
return query
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
from ..dataset import Dataset
|
|
5
|
+
|
|
6
|
+
class DoubanMovie(Dataset):
|
|
7
|
+
def __init__(self, input_path, output_path, n_workers=1):
|
|
8
|
+
super().__init__(input_path, output_path, n_workers)
|
|
9
|
+
self.dataset_name = "Douban Movie Short"
|
|
10
|
+
|
|
11
|
+
self.item_fields_extract = ["Movie_Name_EN", "Movie_Name_CN"]
|
|
12
|
+
self.user_fields_extract = ["Username"]
|
|
13
|
+
self.rating_fields_extract = ["ID", "Date", "Star", "Comment", "Like", "Movie_Name_EN", "Username"]
|
|
14
|
+
|
|
15
|
+
self.item_fields = {
|
|
16
|
+
"movie_id": "item_id::string",
|
|
17
|
+
"Movie_Name_EN": "name_EN::string",
|
|
18
|
+
"Movie_Name_CN": "name_CN::string"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
self.user_fields = {
|
|
22
|
+
"user_id": "user_id::string",
|
|
23
|
+
"Username": "name::string"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
self.rating_fields = {
|
|
27
|
+
"user_id": "user_id::string",
|
|
28
|
+
"movie_id": "item_id::string",
|
|
29
|
+
"Star": "rating::number",
|
|
30
|
+
"Date": "date::string",
|
|
31
|
+
"Comment": "comment::string",
|
|
32
|
+
"Like": "like_count::number"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def load_item_data(self) -> pd.DataFrame():
|
|
36
|
+
filename = os.path.join(self.input_path, "DMSC.csv")
|
|
37
|
+
df = pd.read_csv(filename)
|
|
38
|
+
|
|
39
|
+
df = df[self.item_fields_extract].drop_duplicates().reset_index(drop=True)
|
|
40
|
+
df['movie_id'] = df.index
|
|
41
|
+
|
|
42
|
+
df = df.rename(self.item_fields, axis=1)
|
|
43
|
+
df = df[self.item_fields.values()]
|
|
44
|
+
return df
|
|
45
|
+
|
|
46
|
+
def load_user_data(self) -> pd.DataFrame():
|
|
47
|
+
filename = os.path.join(self.input_path, "DMSC.csv")
|
|
48
|
+
df = pd.read_csv(filename)
|
|
49
|
+
|
|
50
|
+
df = df[self.user_fields_extract].drop_duplicates().reset_index(drop=True)
|
|
51
|
+
df['user_id'] = df.index
|
|
52
|
+
|
|
53
|
+
df = df.rename(self.user_fields, axis=1)
|
|
54
|
+
df = df[self.user_fields.values()]
|
|
55
|
+
return df
|
|
56
|
+
|
|
57
|
+
def load_rating_data(self) -> pd.DataFrame():
|
|
58
|
+
filename = os.path.join(self.input_path, "DMSC.csv")
|
|
59
|
+
df = pd.read_csv(filename)
|
|
60
|
+
|
|
61
|
+
item_df = df[self.item_fields_extract].drop_duplicates().reset_index(drop=True)
|
|
62
|
+
item_df['movie_id'] = item_df.index
|
|
63
|
+
|
|
64
|
+
user_df = df[self.user_fields_extract].drop_duplicates().reset_index(drop=True)
|
|
65
|
+
user_df['user_id'] = user_df.index
|
|
66
|
+
|
|
67
|
+
df = df[self.rating_fields_extract].drop_duplicates().reset_index(drop=True)
|
|
68
|
+
|
|
69
|
+
df = df.join(item_df.set_index('Movie_Name_EN'), on='Movie_Name_EN')
|
|
70
|
+
df = df.join(user_df.set_index('Username'), on='Username')
|
|
71
|
+
|
|
72
|
+
df = df.rename(self.rating_fields, axis=1)
|
|
73
|
+
df = df[self.rating_fields.values()]
|
|
74
|
+
return df
|
|
75
|
+
|