logo Netzschleuder network catalog, repository, and centrifuge

History of msd_taste_profile

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 by Tiago de Paula Peixoto 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/msd_taste_profile/__init__.py b/entries/msd_taste_profile/__init__.py
index 4de63bb..c0e8943 100644
--- a/entries/msd_taste_profile/__init__.py
+++ b/entries/msd_taste_profile/__init__.py
@@ -37,7 +37,7 @@ The network has the following properties:
 tags = ['Social', 'Online', 'Preferences', 'Bipartite', 'Weighted', 'Metadata']
 url = ['http://millionsongdataset.com/tasteprofile/']
 citation = [('T. Bertin-Mahieux, D. P. W. Ellis, B. Whitman, P. Lamere, "The Million Song Dataset." Proceedings of the 12th International Society for Music Information Retrieval Conference (ISMIR), 591-596 (2011)', 'https://doi.org/10.7916/D8NZ8J07')]
-icon_hash = None
+date_added = '2026-10-28'
 upstream_license = None
 # the server does not answer over HTTPS
 upstream_prefix = 'http://millionsongdataset.com/sites/default/files'
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) by Tiago de Paula Peixoto 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/msd_taste_profile/__init__.py b/entries/msd_taste_profile/__init__.py
index 85aae5c..4de63bb 100644
--- a/entries/msd_taste_profile/__init__.py
+++ b/entries/msd_taste_profile/__init__.py
@@ -28,12 +28,12 @@ The titles and artists of the songs are those of the tracks of the MSD with the
 
 The network has the following properties:
 
-- `name`: the anonymous identifier of the user (a hash), or the MSD identifier of the song (e.g. `SOAKIMP12A8C130995`).
-- `is_user`: whether the node is a user (otherwise a song).
-- `title`, `artist`: the title and the artist of the song (empty for users).
-- `track`: the MSD identifier of the track that gives the title and the artist (e.g. `TRMMMYQ128F932D901`; empty for users).
-- `mismatch`: whether the song is in the list of song-track pairs that should not be trusted (false for users).
-- Edge `weight`: the number of plays of the song by the user."""
+- (Node) `name`: the anonymous identifier of the user (a hash), or the MSD identifier of the song (e.g. `SOAKIMP12A8C130995`).
+- (Node) `is_user`: whether the node is a user (otherwise a song).
+- (Node) `title`, `artist`: the title and the artist of the song (empty for users).
+- (Node) `track`: the MSD identifier of the track that gives the title and the artist (e.g. `TRMMMYQ128F932D901`; empty for users).
+- (Node) `mismatch`: whether the song is in the list of song-track pairs that should not be trusted (false for users).
+- (Edge) `weight`: the number of plays of the song by the user."""
 tags = ['Social', 'Online', 'Preferences', 'Bipartite', 'Weighted', 'Metadata']
 url = ['http://millionsongdataset.com/tasteprofile/']
 citation = [('T. Bertin-Mahieux, D. P. W. Ellis, B. Whitman, P. Lamere, "The Million Song Dataset." Proceedings of the 12th International Society for Music Information Retrieval Conference (ISMIR), 591-596 (2011)', 'https://doi.org/10.7916/D8NZ8J07')]
[entries] Remove unneeded code from the entries by Tiago de Paula Peixoto 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/msd_taste_profile/__init__.py b/entries/msd_taste_profile/__init__.py
index ab8c397..85aae5c 100644
--- a/entries/msd_taste_profile/__init__.py
+++ b/entries/msd_taste_profile/__init__.py
@@ -53,7 +53,6 @@ def fetch_upstream(force=False):
 @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] New entry: msd_taste_profile by Tiago de Paula Peixoto ddf38cd
diff --git a/entries/msd_taste_profile/__init__.py b/entries/msd_taste_profile/__init__.py
new file mode 100644
index 0000000..ab8c397
--- /dev/null
+++ b/entries/msd_taste_profile/__init__.py
@@ -0,0 +1,117 @@
+#! /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 pyarrow
+import pyarrow.csv
+
+from .. import *
+
+title = "Echo Nest Taste Profile of the Million Song Dataset (2011)"
+description = r"""A bipartite network of users and songs from the Taste Profile subset of the Million Song Dataset (MSD), the user data released by The Echo Nest (a music intelligence company) to accompany the MSD. The data consist of the play counts of songs by anonymous users, collected from undisclosed partners of The Echo Nest, restricted to the songs of the MSD. Each node is a user or a song, and an edge connects a user to a song played by the user, weighted by the number of times the user played it.
+
+The titles and artists of the songs are those of the tracks of the MSD with the same song identifier (the first track, if the song has several). After the release, an error was found in the matching of the songs of the Taste Profile to the tracks of the MSD: the MSD published a list of song-track pairs that should not be trusted, which are marked with the `mismatch` property (the songs are kept).
+
+The network has the following properties:
+
+- `name`: the anonymous identifier of the user (a hash), or the MSD identifier of the song (e.g. `SOAKIMP12A8C130995`).
+- `is_user`: whether the node is a user (otherwise a song).
+- `title`, `artist`: the title and the artist of the song (empty for users).
+- `track`: the MSD identifier of the track that gives the title and the artist (e.g. `TRMMMYQ128F932D901`; empty for users).
+- `mismatch`: whether the song is in the list of song-track pairs that should not be trusted (false for users).
+- Edge `weight`: the number of plays of the song by the user."""
+tags = ['Social', 'Online', 'Preferences', 'Bipartite', 'Weighted', 'Metadata']
+url = ['http://millionsongdataset.com/tasteprofile/']
+citation = [('T. Bertin-Mahieux, D. P. W. Ellis, B. Whitman, P. Lamere, "The Million Song Dataset." Proceedings of the 12th International Society for Music Information Retrieval Conference (ISMIR), 591-596 (2011)', 'https://doi.org/10.7916/D8NZ8J07')]
+icon_hash = None
+upstream_license = None
+# the server does not answer over HTTPS
+upstream_prefix = 'http://millionsongdataset.com/sites/default/files'
+files = [(('challenge/train_triplets.txt.zip:train_triplets.txt',
+           'AdditionalFiles/unique_tracks.txt',
+           'tasteprofile/sid_mismatches.txt'), None, None)]
+
+def fetch_upstream(force=False):
+    return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+                                force)
+
+@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
+        # "<user>\t<song>\t<count>"
+        with open_upstream_file(name, fnames[0], "rb") as f:
+            t = pyarrow.csv.read_csv(
+                f, read_options=pyarrow.csv.ReadOptions(
+                    column_names=["user", "song", "count"],
+                    block_size=1 << 26),
+                parse_options=pyarrow.csv.ParseOptions(delimiter="\t",
+                                                       quote_char=False),
+                convert_options=pyarrow.csv.ConvertOptions(
+                    column_types={"user": pyarrow.string(),
+                                  "song": pyarrow.string(),
+                                  "count": pyarrow.int64()}))
+        users = t.column("user").dictionary_encode().combine_chunks()
+        songs = t.column("song").dictionary_encode().combine_chunks()
+        count = t.column("count").to_numpy()
+        del t
+        nu = len(users.dictionary)
+        g = Graph(directed=False)
+        g.add_vertex(nu + len(songs.dictionary))
+        unames = users.dictionary.to_pylist()
+        snames = songs.dictionary.to_pylist()
+        g.vp.name = g.new_vp("string", vals=unames + snames)
+        g.vp.is_user = g.new_vp("bool")
+        g.vp.is_user.a[:nu] = True
+        g.ep.weight = g.new_ep("int32_t")
+        g.add_edge_list(numpy.column_stack([users.indices.to_numpy(),
+                                            songs.indices.to_numpy() + nu,
+                                            count]),
+                        eprops=[g.ep.weight])
+        del users, songs, count
+
+        # "<track><SEP><song><SEP><artist><SEP><title>"
+        sindex = {s: nu + i for i, s in enumerate(snames)}
+        g.vp.title = g.new_vp("string")
+        g.vp.artist = g.new_vp("string")
+        g.vp.track = g.new_vp("string")
+        seen = set()
+        with open_upstream_file(name, fnames[1], "r", errors="replace") as f:
+            for line in f:
+                track, song, artist, stitle = line.rstrip("\n").split("<SEP>")
+                v = sindex.get(song)
+                if v is None or v in seen:
+                    continue
+                seen.add(v)
+                g.vp.title[v] = stitle
+                g.vp.artist[v] = artist
+                g.vp.track[v] = track
+
+        # "ERROR: <song track> artist  -  title  !=  artist  -  title"
+        g.vp.mismatch = g.new_vp("bool")
+        with open_upstream_file(name, fnames[2], "r", errors="replace") as f:
+            for line in f:
+                song = line.split("<", 1)[1].split()[0]
+                v = sindex.get(song)
+                if v is not None:
+                    g.vp.mismatch[v] = True
+        yield alt, g