Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cornac/data/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,20 @@ def build(
extra_pos = ts_pos + 1
extra_data = [data[i][extra_pos] for i in valid_idx] if fmt in ["SITJson", "USITJson"] else None

if timestamps is not None and len(timestamps) > 1:
order = np.argsort(session_indices, kind="stable")
s = session_indices[order]
t = timestamps[order]
decreasing = (t[1:] < t[:-1]) & (s[1:] == s[:-1])
if decreasing.any():
n_bad = int(decreasing.sum())
warnings.warn(
f"{n_bad} interaction(s) are not in chronological order within "
"their session. Sequential models treat input row order as the "
"ground-truth sequence; sort your data by (session, timestamp) "
"before building the dataset."
)

dataset = cls(
num_users=len(global_uid_map),
num_sessions=len(set(session_indices)),
Expand Down
22 changes: 22 additions & 0 deletions cornac/datasets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,25 @@ Session-aware recommendation extends next-item (session-based) recommendation by
| [Cosmetics](./cosmetics.py) | 17,268 | 42,367 | 172,242 | 2,533,262 | 9.97 | 59.79 | 14.71 | 0.346% |

For session-based (next-item) evaluation, [Diginetica](./diginetica.py)'s `load_val()` and `load_test()` default to `mode="session-based"`, returning each user's single held-out session (`val_sbr`/`test_sbr`) with no training transitions repeated — the clean evaluation set used by session-based models such as [FPMC](../models/fpmc/) and [GRU4Rec](../models/gru4rec/). Pass `mode="session-aware"` to load the cumulative files (`val`/`test`) instead, where each user's prior sessions precede their held-out one for cross-session models.

---

## Semantic-ID Datasets
### Amazon Product Review
[Amazon Product Review](./amazon_review.py) 5-core

Each user's reviews form one chronologically-ordered sequence. Interactions are loaded via `amazon_review.load_feedback(category=...)` in `UIRT` format (user, item, rating, timestamp). No preprocessing is needed and the data is kept as-is for comparability with published results (with `leave-last-out` split).

| Dataset | #Users | #Items | #Interactions | Type |
| :----------------------- | -----: | -----: | ------------: | :-------- |
| Amazon Beauty (`beauty`) | 22,363 | 12,101 | 198,502 | INT [1,5] |
| Amazon Sports (`sports`) | 35,598 | 18,357 | 296,337 | INT [1,5] |
| Amazon Toys (`toys`) | 19,412 | 11,924 | 167,597 | INT [1,5] |

```Python
from cornac.datasets import amazon_review
from cornac.eval_methods import NextItemEvaluation

data = amazon_review.load_feedback(category="beauty") # UIRT tuples, chronological per user
eval_method = NextItemEvaluation.leave_last_out(data, fmt="UIRT")
```
1 change: 1 addition & 0 deletions cornac/datasets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from . import amazon_clothing
from . import amazon_digital_music
from . import amazon_office
from . import amazon_review
from . import amazon_toy
from . import citeulike
from . import cosmetics
Expand Down
108 changes: 108 additions & 0 deletions cornac/datasets/amazon_review.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright 2026 The Cornac Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""
Amazon Product Review datasets.

There are three versions: '2014', '2018', and '2023' available.
'2014' is the version used in the Semantic-ID literature (e.g., TIGER).

Source: https://cseweb.ucsd.edu/~jmcauley/datasets/amazon/links.html
"""

import gzip
import json
import os
from typing import List

from ..data import Reader
from ..utils import cache

# category -> reviews_<cat>_5.json.gz
_CATEGORY_FILES = {
"beauty": "Beauty",
"sports": "Sports_and_Outdoors",
"toys": "Toys_and_Games",
}

_BASE_URL = "https://snap.stanford.edu/data/amazon/productGraph/categoryFiles"


def _preprocess(gz_path: str, csv_path: str) -> None:
"""Parse the raw 5-core reviews into ``user,item,rating,timestamp`` rows.

Only mechanical cleaning is applied (drop rows with a missing field);
rows are sorted chronologically per user so downstream sequential builders
receive time-ordered sessions.
"""
rows = []
with gzip.open(gz_path, "rt", encoding="utf-8") as f:
for line in f:
r = json.loads(line)
user = r.get("reviewerID")
item = r.get("asin")
rating = r.get("overall")
timestamp = r.get("unixReviewTime")
if user is None or item is None or rating is None or timestamp is None:
continue
rows.append((user, item, float(rating), int(timestamp)))

rows.sort(key=lambda x: (x[0], x[3])) # (user, timestamp)

with open(csv_path, "w") as f:
for user, item, rating, timestamp in rows:
f.write(f"{user},{item},{rating},{timestamp}\n")


def load_feedback(category: str, version: str = "2014", fmt: str = "UIRT", reader: Reader = None) -> List:
"""Load the user-item review feedback, chronologically ordered per user.

Parameters
----------
category: str, required
One of ``'beauty'``, ``'sports'``, ``'toys'`` -- the three categories
used by TIGER and subsequent Semantic-ID papers.

version: str, default: '2014'
Dataset version. Only ``'2014'`` (McAuley 5-core) is currently supported;
2018 and 2023 are available, but 2014 is the version used throughout the Semantic-ID literature.

fmt: str, default: 'UIRT'
Data format; the underlying file has user, item, rating, and timestamp
columns, so ``'UIR'`` and ``'UI'`` are also valid.

reader: `obj:cornac.data.Reader`, default: None
Reader object used to read the data.

Returns
-------
data: array-like
Data in the form of a list of tuples (user, item, rating, timestamp).
"""
if category not in _CATEGORY_FILES:
raise ValueError(f"category='{category}' not supported; " f"choose one of {sorted(_CATEGORY_FILES)}")
if version != "2014":
raise ValueError(f"version='{version}' not supported; only '2014' (McAuley 5-core) " "is available")

stem = _CATEGORY_FILES[category]
gz_path = cache(
url=f"{_BASE_URL}/reviews_{stem}_5.json.gz",
relative_path=f"amazon_review/{category}_{version}.json.gz",
)
csv_path = f"{gz_path[:-len('.json.gz')]}.csv"
if not os.path.exists(csv_path):
_preprocess(gz_path, csv_path)

reader = Reader() if reader is None else reader
return reader.read(csv_path, fmt=fmt, sep=",")
Loading
Loading