History of steam
The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.
[entries] Add the date when each entry was added, and remove icon_hash beff7c9
The dates are those of the commits that first added the entries' files (2020-08-11 for the entries of the initial commit); renamed entries keep the date of their first name. Changed: none
diff --git a/entries/steam/__init__.py b/entries/steam/__init__.py
index e36b2b0..5422d0d 100644
--- a/entries/steam/__init__.py
+++ b/entries/steam/__init__.py
@@ -62,7 +62,7 @@ url = ['https://cseweb.ucsd.edu/~jmcauley/datasets.html#steam_data']
citation = [('W.-C. Kang, J. McAuley, "Self-Attentive Sequential Recommendation." 2018 IEEE International Conference on Data Mining (ICDM), 197-206 (2018)', 'https://doi.org/10.1109/ICDM.2018.00035'),
('M. Wan, J. McAuley, "Item recommendation on monotonic behavior chains." Proceedings of the 12th ACM Conference on Recommender Systems (RecSys), 86-94 (2018)', 'https://doi.org/10.1145/3240323.3240369'),
('A. Pathak, K. Gupta, J. McAuley, "Generating and Personalizing Bundle Recommendations on Steam." Proceedings of the 40th International ACM SIGIR Conference on Research and Development in Information Retrieval, 1073-1076 (2017)', 'https://doi.org/10.1145/3077136.3080724')]
-icon_hash = None
+date_added = '2026-10-28'
upstream_license = None
upstream_prefix = 'https://mcauleylab.ucsd.edu/public_datasets/data/steam'
GAMES = 'steam_games.json.gz'
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) bc3dbbd
The lists of properties at the end of the descriptions marked edge properties with "Edge" but left node properties unmarked, or grouped them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property was often unclear. Every item now starts with (Node), (Edge) or (Graph), followed by the property names, a colon and the explanation, one item per property or group of properties; qualifications (kinds of nodes, networks) follow the names in parentheses. Properties that were only named got a short explanation, and a few items that named the wrong kind of property were corrected.
diff --git a/entries/steam/__init__.py b/entries/steam/__init__.py
index 29053fd..e36b2b0 100644
--- a/entries/steam/__init__.py
+++ b/entries/steam/__init__.py
@@ -34,29 +34,29 @@ All networks are bipartite. The games have the metadata of the Steam store (as c
The networks have the following properties:
-- `is_user` (`reviews`, `ownership`, `australian_reviews`): whether the node is a user (otherwise it is a game).
-- `is_bundle` (`bundles`): whether the node is a bundle (otherwise it is a game).
-- `id`: the Steam user id or user name of a user, the Steam application id of a game, or the id of a bundle.
-- `name` (games and bundles): the name of the game or bundle.
-- `products` (users of `reviews`): the number of products of the user, as shown with the review (the largest value over the user's reviews).
-- `genres`, `tags`, `specs` (games): the genres, user-defined tags and features of the game in the store, comma-separated.
-- `developer`, `publisher`, `release_date`, `sentiment` (games): as given in the store (e.g. `Mostly Positive`).
-- `price` (games): the price of the game in US dollars (NaN if not given or not a number, e.g. free to play).
-- `metascore` (games): the Metacritic score of the game (-1 if not given).
-- `early_access` (games): whether the game was in early access.
-- `bundle_price`, `bundle_final_price` (bundles): the price of the bundle without and with its discount, in US dollars.
-- `genre` (games of `bundles`): the genres of the game, as given with the bundle.
-- Edge `time` (`reviews`): the date of the review, as Unix time.
-- Edge `hours` (`reviews`): the hours played by the user at the time of the review (NaN if not given).
-- Edge `recommended` (`reviews`): whether the user recommends the game.
-- Edge `early_access` (`reviews`): whether the review was written while the game was in early access.
-- Edge `found_funny` (`reviews`): the number of users who found the review funny (0 if not given).
-- Edge `compensation` (`reviews`): the compensation of the reviewer, e.g. `Product received for free` (empty if none).
-- Edge `playtime_forever`, `playtime_2weeks` (`ownership`): the time played by the user, in total and in the last two weeks, in minutes.
-- Edge `posted`, `last_edited` (`australian_reviews`): the dates of the review, as shown on Steam (e.g. `Posted November 5, 2011.`; without the year for reviews of the year of collection).
-- Edge `recommend` (`australian_reviews`): whether the user recommends the game.
-- Edge `funny`, `helpful` (`australian_reviews`): the ratings of the review by other users, as shown on Steam (e.g. `15 of 20 people (75%) found this review helpful`).
-- Edge `discounted_price` (`bundles`): the price of the game in the bundle, in US dollars."""
+- (Node) `is_user` (`reviews`, `ownership`, `australian_reviews`): whether the node is a user (otherwise it is a game).
+- (Node) `is_bundle` (`bundles`): whether the node is a bundle (otherwise it is a game).
+- (Node) `id`: the Steam user id or user name of a user, the Steam application id of a game, or the id of a bundle.
+- (Node) `name` (games and bundles): the name of the game or bundle.
+- (Node) `products` (users of `reviews`): the number of products of the user, as shown with the review (the largest value over the user's reviews).
+- (Node) `genres`, `tags`, `specs` (games): the genres, user-defined tags and features of the game in the store, comma-separated.
+- (Node) `developer`, `publisher`, `release_date`, `sentiment` (games): as given in the store (e.g. `Mostly Positive`).
+- (Node) `price` (games): the price of the game in US dollars (NaN if not given or not a number, e.g. free to play).
+- (Node) `metascore` (games): the Metacritic score of the game (-1 if not given).
+- (Node) `early_access` (games): whether the game was in early access.
+- (Node) `bundle_price`, `bundle_final_price` (bundles): the price of the bundle without and with its discount, in US dollars.
+- (Node) `genre` (games of `bundles`): the genres of the game, as given with the bundle.
+- (Edge) `time` (`reviews`): the date of the review, as Unix time.
+- (Edge) `hours` (`reviews`): the hours played by the user at the time of the review (NaN if not given).
+- (Edge) `recommended` (`reviews`): whether the user recommends the game.
+- (Edge) `early_access` (`reviews`): whether the review was written while the game was in early access.
+- (Edge) `found_funny` (`reviews`): the number of users who found the review funny (0 if not given).
+- (Edge) `compensation` (`reviews`): the compensation of the reviewer, e.g. `Product received for free` (empty if none).
+- (Edge) `playtime_forever`, `playtime_2weeks` (`ownership`): the time played by the user, in total and in the last two weeks, in minutes.
+- (Edge) `posted`, `last_edited` (`australian_reviews`): the dates of the review, as shown on Steam (e.g. `Posted November 5, 2011.`; without the year for reviews of the year of collection).
+- (Edge) `recommend` (`australian_reviews`): whether the user recommends the game.
+- (Edge) `funny`, `helpful` (`australian_reviews`): the ratings of the review by other users, as shown on Steam (e.g. `15 of 20 people (75%) found this review helpful`).
+- (Edge) `discounted_price` (`bundles`): the price of the game in the bundle, in US dollars."""
tags = ['Social', 'Online', 'Preferences', 'Bipartite', 'Multigraph', 'Timestamps', 'Metadata']
url = ['https://cseweb.ucsd.edu/~jmcauley/datasets.html#steam_data']
citation = [('W.-C. Kang, J. McAuley, "Self-Attentive Sequential Recommendation." 2018 IEEE International Conference on Data Mining (ICDM), 197-206 (2018)', 'https://doi.org/10.1109/ICDM.2018.00035'),
[entries] New entry: steam aca7874
diff --git a/entries/steam/__init__.py b/entries/steam/__init__.py
new file mode 100644
index 0000000..29053fd
--- /dev/null
+++ b/entries/steam/__init__.py
@@ -0,0 +1,219 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import ast
+
+from .. import *
+
+title = "Steam video game reviews, ownership and bundles (2010-2018)"
+description = r"""Networks of the users of Steam, the video game distribution platform of Valve, and the games (and other products, such as soundtracks and add-ons) they reviewed or own, collected by J. McAuley and colleagues (University of California, San Diego) from the public pages of the Steam store and community.
+
+There are four networks:
+
+- `reviews`: the reviews of games on Steam (version 2 of the data, collected for Kang and McAuley 2018), 7.8 million reviews by 2.6 million users of 15 thousand games, from 2010 to January 2018. A directed edge from a user to a game is a review. Users are identified by their Steam user id where it is given with the review (for about 40% of the reviews), otherwise by their user name; since user names need not be unique, and a user may appear with and without id, user nodes do not correspond exactly to users.
+- `ownership`: the games owned by 88,310 users of Australia (version 1 of the data, collected for Wan and McAuley 2018 and Pathak et al. 2017), with the time played. A directed edge from a user to a game means that the user owns the game. the 673 users listed twice are included once (with their first listing), which leaves 5.09 of the 5.15 million ownership records.
+- `australian_reviews`: the reviews written by 25,799 of these Australian users (59,305 reviews), with their recommendation and the ratings by other users.
+- `bundles`: the 615 bundles of games sold together on Steam (as listed by SteamDB), and the 3,525 items in them. A directed edge from a bundle to a game means that the game is part of the bundle.
+
+All networks are bipartite. The games have the metadata of the Steam store (as collected in early 2018) where available; the texts of the reviews are not included.
+
+The networks have the following properties:
+
+- `is_user` (`reviews`, `ownership`, `australian_reviews`): whether the node is a user (otherwise it is a game).
+- `is_bundle` (`bundles`): whether the node is a bundle (otherwise it is a game).
+- `id`: the Steam user id or user name of a user, the Steam application id of a game, or the id of a bundle.
+- `name` (games and bundles): the name of the game or bundle.
+- `products` (users of `reviews`): the number of products of the user, as shown with the review (the largest value over the user's reviews).
+- `genres`, `tags`, `specs` (games): the genres, user-defined tags and features of the game in the store, comma-separated.
+- `developer`, `publisher`, `release_date`, `sentiment` (games): as given in the store (e.g. `Mostly Positive`).
+- `price` (games): the price of the game in US dollars (NaN if not given or not a number, e.g. free to play).
+- `metascore` (games): the Metacritic score of the game (-1 if not given).
+- `early_access` (games): whether the game was in early access.
+- `bundle_price`, `bundle_final_price` (bundles): the price of the bundle without and with its discount, in US dollars.
+- `genre` (games of `bundles`): the genres of the game, as given with the bundle.
+- Edge `time` (`reviews`): the date of the review, as Unix time.
+- Edge `hours` (`reviews`): the hours played by the user at the time of the review (NaN if not given).
+- Edge `recommended` (`reviews`): whether the user recommends the game.
+- Edge `early_access` (`reviews`): whether the review was written while the game was in early access.
+- Edge `found_funny` (`reviews`): the number of users who found the review funny (0 if not given).
+- Edge `compensation` (`reviews`): the compensation of the reviewer, e.g. `Product received for free` (empty if none).
+- Edge `playtime_forever`, `playtime_2weeks` (`ownership`): the time played by the user, in total and in the last two weeks, in minutes.
+- Edge `posted`, `last_edited` (`australian_reviews`): the dates of the review, as shown on Steam (e.g. `Posted November 5, 2011.`; without the year for reviews of the year of collection).
+- Edge `recommend` (`australian_reviews`): whether the user recommends the game.
+- Edge `funny`, `helpful` (`australian_reviews`): the ratings of the review by other users, as shown on Steam (e.g. `15 of 20 people (75%) found this review helpful`).
+- Edge `discounted_price` (`bundles`): the price of the game in the bundle, in US dollars."""
+tags = ['Social', 'Online', 'Preferences', 'Bipartite', 'Multigraph', 'Timestamps', 'Metadata']
+url = ['https://cseweb.ucsd.edu/~jmcauley/datasets.html#steam_data']
+citation = [('W.-C. Kang, J. McAuley, "Self-Attentive Sequential Recommendation." 2018 IEEE International Conference on Data Mining (ICDM), 197-206 (2018)', 'https://doi.org/10.1109/ICDM.2018.00035'),
+ ('M. Wan, J. McAuley, "Item recommendation on monotonic behavior chains." Proceedings of the 12th ACM Conference on Recommender Systems (RecSys), 86-94 (2018)', 'https://doi.org/10.1145/3240323.3240369'),
+ ('A. Pathak, K. Gupta, J. McAuley, "Generating and Personalizing Bundle Recommendations on Steam." Proceedings of the 40th International ACM SIGIR Conference on Research and Development in Information Retrieval, 1073-1076 (2017)', 'https://doi.org/10.1145/3077136.3080724')]
+icon_hash = None
+upstream_license = None
+upstream_prefix = 'https://mcauleylab.ucsd.edu/public_datasets/data/steam'
+GAMES = 'steam_games.json.gz'
+files = [(('steam_reviews.json.gz', GAMES), 'reviews', None),
+ (('australian_users_items.json.gz', GAMES), 'ownership', None),
+ (('australian_user_reviews.json.gz', GAMES), 'australian_reviews', None),
+ (('bundle_data.json.gz', GAMES), 'bundles', None)]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force, check=False)
+
+def records(name, fname):
+ "The records of the file `fname`: one Python literal (a dict) per line."
+ with open_upstream_file(name, fname, "r") as f:
+ for line in f:
+ if line.strip():
+ yield ast.literal_eval(line)
+
+def to_float(x):
+ try:
+ return float(str(x).replace("$", "").replace(",", ""))
+ except (TypeError, ValueError):
+ return numpy.nan
+
+class Builder:
+ "Bipartite graph built incrementally: sources (users, bundles) and games."
+ def __init__(self):
+ self.src = {}
+ self.games = {}
+ self.edges = []
+ self.eprops = {}
+ self.sprops = {}
+
+ def source(self, key):
+ return self.src.setdefault(key, len(self.src))
+
+ def game(self, key):
+ return self.games.setdefault(key, len(self.games))
+
+ def add(self, s, t, **props):
+ self.edges.append((s, t))
+ for k, v in props.items():
+ self.eprops.setdefault(k, []).append(v)
+
+ def graph(self, side, name, game_meta, eptypes):
+ S = len(self.src)
+ g = Graph(directed=True)
+ g.add_vertex(S + len(self.games))
+ e = numpy.array(self.edges, dtype="int64").reshape(-1, 2)
+ e[:, 1] += S
+ g.add_edge_list(e)
+ p = g.new_vp("bool")
+ p.a[:S] = 1
+ g.vp[side] = p
+ ids = list(self.src) + list(self.games)
+ g.vp.id = g.new_vp("string", vals=[str(i) for i in ids])
+ N = g.num_vertices()
+ for k, (t, v) in self.sprops.items():
+ pad = {"int": -1, "double": numpy.nan, "string": ""}[t]
+ g.vp[k] = g.new_vp(t, vals=list(v) + [pad] * (N - len(v)))
+ # Game metadata
+ gids = list(self.games)
+ def vals(f, default):
+ return [default] * S + [f(game_meta.get(i, {})) for i in gids]
+ lst = lambda d, k: ", ".join(d.get(k) or [])
+ g.vp.name = g.new_vp("string", vals=list(name) + [game_meta.get(i, {}).get("app_name") or game_meta.get(i, {}).get("title") or "" for i in gids])
+ for k in ["genres", "tags", "specs"]:
+ g.vp[k] = g.new_vp("string", vals=vals(lambda d: lst(d, k), ""))
+ for k in ["developer", "publisher", "release_date", "sentiment"]:
+ g.vp[k] = g.new_vp("string", vals=vals(lambda d: str(d.get(k) or ""), ""))
+ g.vp.price = g.new_vp("double", vals=vals(lambda d: to_float(d.get("price")), numpy.nan))
+ g.vp.metascore = g.new_vp("int", vals=vals(lambda d: int(d["metascore"]) if str(d.get("metascore", "")).isdigit() else -1, -1))
+ g.vp.early_access = g.new_vp("bool", vals=vals(lambda d: bool(d.get("early_access")), False))
+ for k, v in self.eprops.items():
+ g.ep[k] = g.new_ep(eptypes[k], vals=v)
+ return g
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+ name = __name__.split(".")[-1]
+ for fnames, alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+ meta = {}
+ for d in records(name, fnames[1]):
+ if "id" in d:
+ meta.setdefault(d["id"], d)
+ b = Builder()
+ if alt == "reviews":
+ # {'username', 'hours', 'products', 'product_id', 'page_order',
+ # 'date', 'text', 'recommended', 'early_access', 'page', and
+ # sometimes 'found_funny', 'compensation', 'user_id'}
+ products = {}
+ for d in records(name, fnames[0]):
+ u = b.source(d.get("user_id") or d["username"])
+ products[u] = max(products.get(u, -1), int(d.get("products") or -1))
+ b.add(u, b.game(d["product_id"]),
+ time=int(numpy.datetime64(d["date"], "s").astype("int64")),
+ hours=to_float(d.get("hours")),
+ recommended=bool(d.get("recommended")),
+ early_access=bool(d.get("early_access")),
+ found_funny=int(d.get("found_funny") or 0),
+ compensation=str(d.get("compensation") or ""))
+ b.sprops["products"] = ("int", [products[i] for i in range(len(b.src))])
+ g = b.graph("is_user", [""] * len(b.src), meta,
+ dict(time="int64_t", hours="double", recommended="bool",
+ early_access="bool",
+ found_funny="int", compensation="string"))
+ elif alt == "ownership":
+ for d in records(name, fnames[0]):
+ if d["user_id"] in b.src:
+ continue
+ u = b.source(d["user_id"])
+ for it in d["items"]:
+ meta.setdefault(it["item_id"], {"app_name": it["item_name"]})
+ b.add(u, b.game(it["item_id"]),
+ playtime_forever=int(it["playtime_forever"]),
+ playtime_2weeks=int(it["playtime_2weeks"]))
+ g = b.graph("is_user", [""] * len(b.src), meta,
+ dict(playtime_forever="int", playtime_2weeks="int"))
+ elif alt == "australian_reviews":
+ for d in records(name, fnames[0]):
+ u = b.source(d["user_id"])
+ for r in d["reviews"]:
+ b.add(u, b.game(r["item_id"]), posted=r["posted"],
+ last_edited=r["last_edited"],
+ recommend=bool(r["recommend"]), funny=r["funny"],
+ helpful=r["helpful"])
+ g = b.graph("is_user", [""] * len(b.src), meta,
+ dict(posted="string", last_edited="string",
+ recommend="bool", funny="string", helpful="string"))
+ else:
+ bnames, bprice, bfinal, genre = [], [], [], {}
+ for d in records(name, fnames[0]):
+ s = b.source(d["bundle_id"])
+ if s < len(bnames):
+ continue
+ bnames.append(d["bundle_name"])
+ bprice.append(to_float(d["bundle_price"]))
+ bfinal.append(to_float(d["bundle_final_price"]))
+ for it in d["items"]:
+ meta.setdefault(it["item_id"], {"app_name": it["item_name"]})
+ t = b.game(it["item_id"])
+ genre.setdefault(t, it.get("genre") or "")
+ b.add(s, t, discounted_price=to_float(it["discounted_price"]))
+ S = len(b.src)
+ b.sprops["bundle_price"] = ("double", bprice + [numpy.nan] * len(b.games))
+ b.sprops["bundle_final_price"] = ("double", bfinal + [numpy.nan] * len(b.games))
+ b.sprops["genre"] = ("string", [""] * S + [genre[i] for i in range(len(b.games))])
+ g = b.graph("is_bundle", bnames, meta, dict(discounted_price="double"))
+ yield alt, g