History of movielens_32m
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/movielens_32m/__init__.py b/entries/movielens_32m/__init__.py
index 1f2969f..17bbe49 100644
--- a/entries/movielens_32m/__init__.py
+++ b/entries/movielens_32m/__init__.py
@@ -45,7 +45,7 @@ The networks have the following properties:
tags = ['Social', 'Online', 'Preferences', 'Weighted', 'Timestamps', 'Metadata']
url = 'https://grouplens.org/datasets/movielens/32m/'
citation = [('F. M. Harper, J. A. Konstan, "The MovieLens Datasets: History and Context." ACM Transactions on Interactive Intelligent Systems 5(4), 19 (2015)', 'https://doi.org/10.1145/2827872')]
-icon_hash = None
+date_added = '2026-10-28'
# The README of this release (unlike those of the older ones) allows
# redistribution under the same license conditions
upstream_license = ("MovieLens usage license (research use, no commercial use; redistribution under the same conditions)", "https://files.grouplens.org/datasets/movielens/ml-32m-README.html")
[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/movielens_32m/__init__.py b/entries/movielens_32m/__init__.py
index d0b31c6..1f2969f 100644
--- a/entries/movielens_32m/__init__.py
+++ b/entries/movielens_32m/__init__.py
@@ -31,11 +31,17 @@ The older MovieLens data sets (100K, 1M, 10M, 20M, 25M) are in the entry `moviel
The networks have the following properties:
-- Nodes: `movie_id`, the MovieLens id of the movie (its page is `https://movielens.org/movies/<movie_id>`; -1 for the other nodes); `title`, the title of the movie, including its year of release; `genres`, the list of its genres (empty if none is listed); `imdb_id` and `tmdb_id`, its ids in IMDb and in The Movie Database (-1 if not given).
-- `ratings`, nodes: `is_user`, whether the node is a user (otherwise a movie); `user_id`, the anonymized id of the user (-1 for movies).
-- `ratings`, edges: `rating`, the rating in stars; `time`, the time of the rating, as Unix time.
-- `tags`, nodes: `is_tag`, whether the node is a tag (otherwise a movie); `name`, the tag string (empty for movies).
-- `tags`, edges: `user`, the anonymized id of the user who applied the tag (the same as `user_id` in `ratings`); `time`, the time when the tag was applied, as Unix time."""
+- (Node) `movie_id`: the MovieLens id of the movie (its page is `https://movielens.org/movies/<movie_id>`; -1 for the other nodes).
+- (Node) `title`: the title of the movie, including its year of release.
+- (Node) `genres`: the list of its genres (empty if none is listed).
+- (Node) `imdb_id` and `tmdb_id`: its ids in IMDb and in The Movie Database (-1 if not given).
+- (Node) `is_user` (`ratings`): whether the node is a user (otherwise a movie).
+- (Node) `user_id` (`ratings`): the anonymized id of the user (-1 for movies).
+- (Node) `is_tag` (`tags`): whether the node is a tag (otherwise a movie).
+- (Node) `name` (`tags`): the tag string (empty for movies).
+- (Edge) `rating` (`ratings`): the rating in stars.
+- (Edge) `time`: the time of the rating (`ratings`), or when the tag was applied (`tags`), as Unix time.
+- (Edge) `user` (`tags`): the anonymized id of the user who applied the tag (the same as `user_id` in `ratings`)."""
tags = ['Social', 'Online', 'Preferences', 'Weighted', 'Timestamps', 'Metadata']
url = 'https://grouplens.org/datasets/movielens/32m/'
citation = [('F. M. Harper, J. A. Konstan, "The MovieLens Datasets: History and Context." ACM Transactions on Interactive Intelligent Systems 5(4), 19 (2015)', 'https://doi.org/10.1145/2827872')]
[entries] Remove unneeded code from the entries 2c69f19
Unneeded "global files" statements, unused imports and variables, f-strings without placeholders, duplicate keys in foodweb_little_rock (keeping the order and the values used), and the second definition of get_lines() in twitter_higgs. Nothing changes in the networks or descriptions. Changed: none
diff --git a/entries/movielens_32m/__init__.py b/entries/movielens_32m/__init__.py
index 101da2b..d0b31c6 100644
--- a/entries/movielens_32m/__init__.py
+++ b/entries/movielens_32m/__init__.py
@@ -110,7 +110,6 @@ def set_movie_props(g, vs, movies):
@coerce_props()
@annotate()
def parse(alts=None):
- global files
name = __name__.split(".")[-1]
for fnames, alt, fmt in files:
if alts is not None and alt not in alts:
[entries] Remove the remarks on the distribution of the data from the descriptions of beer_reviews, bluesky_failla, citibike, divvy, facebook100, goodreads, hiv_transmission, hollywood_actors, mind_news, movielens_10m_tags, movielens_32m, movielens_ratings, netflix_prize, opensky_flights, opensky_flights_full, regulondb, statsbomb, yelp_restaurants and yelp_reviews 8be09c8
The descriptions said whether the data may be distributed (access tokens, restrictions, redistribution bans, take-downs); the site shows restricted entries itself, and the descriptions now only describe the data.
diff --git a/entries/movielens_32m/__init__.py b/entries/movielens_32m/__init__.py
index adcb8b3..101da2b 100644
--- a/entries/movielens_32m/__init__.py
+++ b/entries/movielens_32m/__init__.py
@@ -27,7 +27,7 @@ description = """Bipartite networks of the users and movies of the online movie
- `ratings`: users and movies, where each edge is the rating that a user gave to a movie, from 0.5 to 5 stars in half-star steps (a user rates a movie at most once). The network contains all 87,585 movies of the data set (those with at least one rating or tag), of which 84,432 were rated; the 3,153 movies that were only tagged have no edges.
- `tags`: tags and movies, where each edge is one application of a tag (a free-text word or short phrase, case-sensitive) to a movie by a user, so that the same tag applied to the same movie by several users (or several times) gives multiple edges. Only the movies with at least one tag are included.
-The older MovieLens data sets (100K, 1M, 10M, 20M, 25M), whose license forbids redistribution, are in the entry `movielens_ratings`; the tags of the 10M data set are in `movielens_10m_tags`.
+The older MovieLens data sets (100K, 1M, 10M, 20M, 25M) are in the entry `movielens_ratings`; the tags of the 10M data set are in `movielens_10m_tags`.
The networks have the following properties:
[entries] movielens_100k: rename to movielens_10m_tags 9813447
The entry holds the tag assignments of the MovieLens 10M data set, not the 100K data set (which has no tags), so its name was misleading. The cross-references in movielens_32m and movielens_ratings follow.
diff --git a/entries/movielens_32m/__init__.py b/entries/movielens_32m/__init__.py
index cd6655c..adcb8b3 100644
--- a/entries/movielens_32m/__init__.py
+++ b/entries/movielens_32m/__init__.py
@@ -27,7 +27,7 @@ description = """Bipartite networks of the users and movies of the online movie
- `ratings`: users and movies, where each edge is the rating that a user gave to a movie, from 0.5 to 5 stars in half-star steps (a user rates a movie at most once). The network contains all 87,585 movies of the data set (those with at least one rating or tag), of which 84,432 were rated; the 3,153 movies that were only tagged have no edges.
- `tags`: tags and movies, where each edge is one application of a tag (a free-text word or short phrase, case-sensitive) to a movie by a user, so that the same tag applied to the same movie by several users (or several times) gives multiple edges. Only the movies with at least one tag are included.
-The older MovieLens data sets (100K, 1M, 10M, 20M, 25M), whose license forbids redistribution, are in the entry `movielens_ratings`; the tags of the 10M data set are in `movielens_100k`.
+The older MovieLens data sets (100K, 1M, 10M, 20M, 25M), whose license forbids redistribution, are in the entry `movielens_ratings`; the tags of the 10M data set are in `movielens_10m_tags`.
The networks have the following properties:
@@ -50,7 +50,7 @@ files = [(('ml-32m.zip:ml-32m/ratings.csv', 'ml-32m.zip:ml-32m/movies.csv',
'ml-32m.zip:ml-32m/links.csv'), 'tags', None)]
# The server of files.grouplens.org does not send a certificate chain that
-# can be verified against the usual trust stores (see movielens_100k). The
+# can be verified against the usual trust stores (see movielens_10m_tags). The
# file is then obtained from a pinned Internet Archive snapshot instead; in
# both cases its content is checked against the MD5 sum published by
# GroupLens (ml-32m.zip.md5).
[entries] New entry: movielens_32m c068b9f
diff --git a/entries/movielens_32m/__init__.py b/entries/movielens_32m/__init__.py
new file mode 100644
index 0000000..cd6655c
--- /dev/null
+++ b/entries/movielens_32m/__init__.py
@@ -0,0 +1,167 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020-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/>.
+
+from .. import *
+
+import hashlib
+import pandas
+
+title = "MovieLens 32M ratings and tags (1995-2023)"
+description = """Bipartite networks of the users and movies of the online movie recommender service MovieLens, from the MovieLens 32M data set released by GroupLens Research (University of Minnesota) in October 2023. The data set contains 32,000,204 ratings and 2,000,072 tag applications made between January 1995 and October 2023 by 200,948 users, who were selected at random among those who had rated at least 20 movies, and whose ids were anonymized; no demographic information about the users is given.
+
+- `ratings`: users and movies, where each edge is the rating that a user gave to a movie, from 0.5 to 5 stars in half-star steps (a user rates a movie at most once). The network contains all 87,585 movies of the data set (those with at least one rating or tag), of which 84,432 were rated; the 3,153 movies that were only tagged have no edges.
+- `tags`: tags and movies, where each edge is one application of a tag (a free-text word or short phrase, case-sensitive) to a movie by a user, so that the same tag applied to the same movie by several users (or several times) gives multiple edges. Only the movies with at least one tag are included.
+
+The older MovieLens data sets (100K, 1M, 10M, 20M, 25M), whose license forbids redistribution, are in the entry `movielens_ratings`; the tags of the 10M data set are in `movielens_100k`.
+
+The networks have the following properties:
+
+- Nodes: `movie_id`, the MovieLens id of the movie (its page is `https://movielens.org/movies/<movie_id>`; -1 for the other nodes); `title`, the title of the movie, including its year of release; `genres`, the list of its genres (empty if none is listed); `imdb_id` and `tmdb_id`, its ids in IMDb and in The Movie Database (-1 if not given).
+- `ratings`, nodes: `is_user`, whether the node is a user (otherwise a movie); `user_id`, the anonymized id of the user (-1 for movies).
+- `ratings`, edges: `rating`, the rating in stars; `time`, the time of the rating, as Unix time.
+- `tags`, nodes: `is_tag`, whether the node is a tag (otherwise a movie); `name`, the tag string (empty for movies).
+- `tags`, edges: `user`, the anonymized id of the user who applied the tag (the same as `user_id` in `ratings`); `time`, the time when the tag was applied, as Unix time."""
+tags = ['Social', 'Online', 'Preferences', 'Weighted', 'Timestamps', 'Metadata']
+url = 'https://grouplens.org/datasets/movielens/32m/'
+citation = [('F. M. Harper, J. A. Konstan, "The MovieLens Datasets: History and Context." ACM Transactions on Interactive Intelligent Systems 5(4), 19 (2015)', 'https://doi.org/10.1145/2827872')]
+icon_hash = None
+# The README of this release (unlike those of the older ones) allows
+# redistribution under the same license conditions
+upstream_license = ("MovieLens usage license (research use, no commercial use; redistribution under the same conditions)", "https://files.grouplens.org/datasets/movielens/ml-32m-README.html")
+upstream_prefix = 'https://files.grouplens.org/datasets/movielens'
+files = [(('ml-32m.zip:ml-32m/ratings.csv', 'ml-32m.zip:ml-32m/movies.csv',
+ 'ml-32m.zip:ml-32m/links.csv'), 'ratings', None),
+ (('ml-32m.zip:ml-32m/tags.csv', 'ml-32m.zip:ml-32m/movies.csv',
+ 'ml-32m.zip:ml-32m/links.csv'), 'tags', None)]
+
+# The server of files.grouplens.org does not send a certificate chain that
+# can be verified against the usual trust stores (see movielens_100k). The
+# file is then obtained from a pinned Internet Archive snapshot instead; in
+# both cases its content is checked against the MD5 sum published by
+# GroupLens (ml-32m.zip.md5).
+archived_url = 'https://web.archive.org/web/20250304092659id_/https://files.grouplens.org/datasets/movielens/ml-32m.zip'
+md5sum = 'd472be332d4daa821edc399621853b57'
+
+def fetch_upstream(force=False):
+ name = __name__.split(".")[-1]
+ fname = "ml-32m.zip"
+ if not force:
+ try:
+ with open_upstream_file(name, fname, "rb"):
+ return
+ except (FileNotFoundError, IOError):
+ pass
+ def download(url):
+ with open_url(url) as f:
+ return f.read()
+ try:
+ data = download(f"{upstream_prefix}/{fname}")
+ except urllib.error.URLError as e:
+ if not isinstance(e.reason, ssl.SSLCertVerificationError):
+ raise
+ data = retry_download(download, archived_url, tries=3)
+ if hashlib.md5(data).hexdigest() != md5sum:
+ raise RuntimeError(f"unexpected content for {fname}")
+ with open_upstream_file(name, fname, "wb") as output:
+ output.write(data)
+
+def read_movies(name, fmovies, flinks):
+ "Movie ids, titles, genres and IMDb and TMDB ids, as a data frame."
+ with open_upstream_file(name, fmovies, "rb") as f:
+ movies = pandas.read_csv(f, dtype={"movieId": "int64", "title": str,
+ "genres": str},
+ keep_default_na=False)
+ with open_upstream_file(name, flinks, "rb") as f:
+ links = pandas.read_csv(f, dtype={"movieId": "int64"})
+ links = links.fillna(-1).astype("int64")
+ movies = movies.merge(links, on="movieId", how="left").fillna(-1)
+ return movies
+
+def set_movie_props(g, vs, movies):
+ "Set the properties of the movie nodes `vs` (indices) from the frame."
+ g.vp.movie_id = g.new_vp("int64_t", val=-1)
+ g.vp.title = g.new_vp("string")
+ g.vp.genres = g.new_vp("vector<string>")
+ g.vp.imdb_id = g.new_vp("int64_t", val=-1)
+ g.vp.tmdb_id = g.new_vp("int64_t", val=-1)
+ g.vp.movie_id.a[vs] = movies.movieId.values
+ g.vp.imdb_id.a[vs] = movies.imdbId.values.astype("int64")
+ g.vp.tmdb_id.a[vs] = movies.tmdbId.values.astype("int64")
+ for v, t, gs in zip(vs, movies.title.values, movies.genres.values):
+ g.vp.title[v] = t
+ g.vp.genres[v] = [] if gs == "(no genres listed)" else gs.split("|")
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+ global files
+ name = __name__.split(".")[-1]
+ for fnames, alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+ movies = read_movies(name, fnames[1], fnames[2])
+ if alt == "ratings":
+ with open_upstream_file(name, fnames[0], "rb") as f:
+ df = pandas.read_csv(f, dtype={"userId": "int64", "movieId": "int64",
+ "rating": "float64",
+ "timestamp": "int64"})
+ users = numpy.unique(df.userId.values)
+ N_u = len(users)
+ mpos = pandas.Index(movies.movieId.values)
+ assert mpos.is_unique
+ g = Graph(directed=False)
+ g.add_vertex(N_u + len(movies))
+ g.vp.is_user = g.new_vp("bool")
+ g.vp.is_user.a[:N_u] = True
+ g.vp.user_id = g.new_vp("int64_t", val=-1)
+ g.vp.user_id.a[:N_u] = users
+ set_movie_props(g, numpy.arange(N_u, N_u + len(movies)), movies)
+ u = numpy.searchsorted(users, df.userId.values)
+ m = mpos.get_indexer(df.movieId.values)
+ assert (m >= 0).all()
+ g.ep.rating = g.new_ep("double")
+ g.ep.time = g.new_ep("int64_t")
+ g.add_edge_list(numpy.column_stack((u, N_u + m)))
+ g.ep.rating.a = df.rating.values
+ g.ep.time.a = df.timestamp.values
+ del df
+ else:
+ with open_upstream_file(name, fnames[0], "rb") as f:
+ df = pandas.read_csv(f, dtype={"userId": "int64", "movieId": "int64",
+ "tag": str, "timestamp": "int64"},
+ keep_default_na=False)
+ tag_names, t = numpy.unique(df.tag.values.astype(str),
+ return_inverse=True)
+ mids, m = numpy.unique(df.movieId.values, return_inverse=True)
+ N_t = len(tag_names)
+ movies = movies.set_index("movieId").loc[mids].reset_index()
+ g = Graph(directed=False)
+ g.add_vertex(N_t + len(mids))
+ g.vp.is_tag = g.new_vp("bool")
+ g.vp.is_tag.a[:N_t] = True
+ g.vp.name = g.new_vp("string")
+ for v, s in enumerate(tag_names):
+ g.vp.name[v] = s
+ set_movie_props(g, numpy.arange(N_t, N_t + len(mids)), movies)
+ g.ep.user = g.new_ep("int64_t")
+ g.ep.time = g.new_ep("int64_t")
+ g.add_edge_list(numpy.column_stack((t, N_t + m)))
+ g.ep.user.a = df.userId.values
+ g.ep.time.a = df.timestamp.values
+ yield alt, g