History of jester
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] Move KONECT-derived entries to their original sources 45943ea
The entries now download the data from where KONECT obtained them (live or via the Wayback Machine), gaining the metadata KONECT dropped (names, identifiers, timestamps, attributes), with descriptions and citations rewritten from the original sources: - Ma'ayan lab collection: faa_routes, foodweb_little_rock (taxon names were assigned to the wrong nodes), interactome_pdz, interactome_figeys, interactome_stelzl, interactome_vidal - Pajek collection and primary sources: word_assoc (from the Edinburgh Associative Thesaurus; KONECT dropped all counts >= 10), foldoc, new_guinea_tribes - bitriad: elite, ceo_club (adds the CEO KONECT's copy lacked) - Lovro Subelj: cora, euroroad (adds isolated cities and parallel roads) - SNAP: elec (all votes, with elections and nominators) - Zenodo: wiki_talk (user ids and roles) - MPI-SWS: flickr_growth, facebook_wall (edges now point from poster to wall owner, as documented), wikipedia_growth (full timestamps) - Other sources: prosper, jester, reactome (the 2014 release KONECT used), unicodelang (CLDR 27), wordnet (WordNet 3.1), chicago_road (TNTP ChicagoRegional)
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index 358a811..c01a82e 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -18,19 +18,46 @@
from .. import *
-title = "Jester joke ratings (2001)"
-description = """Two bipartite networks of users and jokes, extracted from the online joke recommender system Jester. A user connects to all jokes for which that user entered a rating. Edge weights give the rating score, scaled from -10 to +10. The two files differ by how many joke nodes are included, 100 or 150.[^icon]
-[^icon]: Description obtained from the [ICON](https://icon.colorado.edu) project."""
-tags = ['Economic', 'Preferences', 'Weighted']
-url = 'http://konect.cc/networks/jester1'
-citation = [('K. Goldberg et al. "Eigentaste: A constant time collaborative filtering algorithm." Information Retrieval 4(2), 133-151 (2001)', 'https://doi.org/10.1023/A:1011419012209')]
+import html
+
+title = "Jester joke ratings (1999-2003)"
+description = """A bipartite network of users and the jokes they rated in Jester, an online joke recommender system developed by K. Goldberg and collaborators at the University of California, Berkeley. This is Jester's Dataset 1, with over 4.1 million ratings of 100 jokes by 73,421 anonymous users, collected between April 1999 and May 2003. Users rated jokes with a continuous slider, and each rating is a real value between -10 and +10. Each edge connects a user to a joke they rated. The data were released in three parts: two of users who rated 36 or more jokes (24,983 and 23,500 users), and one of users who rated between 15 and 35 jokes (24,938 users). Almost all users rated the ten jokes 5, 7, 8, 13, 15, 16, 17, 18, 19 and 20, which were used as the "gauge set" of the Eigentaste recommendation algorithm.
+
+The network has the following properties:
+
+- Nodes: `is_joke`, whether the node is a joke (otherwise it is a user); `joke_id`, the number of the joke (1 to 100; -1 for users); `text`, the text of the joke (with the HTML markup removed); `part`, the part of the data set the user comes from (1, 2 or 3; -1 for jokes).
+- Edges: `weight`, the rating, between -10 and +10 (higher values for funnier jokes), with two decimal places."""
+tags = ['Economic', 'Preferences', 'Weighted', 'Metadata']
+url = 'https://eigentaste.berkeley.edu/dataset/'
+citation = [('K. Goldberg, T. Roeder, D. Gupta, C. Perkins, "Eigentaste: A Constant Time Collaborative Filtering Algorithm." Information Retrieval 4(2), 133-151 (2001)', 'https://doi.org/10.1023/A:1011419012209')]
icon_hash = '5818e8828cf714f319c3eae7'
-upstream_prefix = 'http://konect.cc/files'
-files = [('download.tsv.jester1.tar.bz2', None, 'konect')]
+upstream_license = ("Freely available for research use when acknowledged", "https://eigentaste.berkeley.edu/dataset/")
+# The Jester site does not answer anymore; the files are obtained from the
+# Internet Archive.
+upstream_prefix = 'https://web.archive.org/web/2026id_/https://eigentaste.berkeley.edu/dataset/archive'
+files = [(('jester_dataset_1_1.zip:jester-data-1.xls',
+ 'jester_dataset_1_2.zip:jester-data-2.xls',
+ 'jester_dataset_1_3.zip:jester-data-3.xls',
+ 'jester_dataset_1_joke_texts.zip'), None, None)]
+archived_urls = {
+ 'jester_dataset_1_1.zip':
+ 'https://web.archive.org/web/20260403205916id_/https://eigentaste.berkeley.edu/dataset/archive/jester_dataset_1_1.zip',
+ 'jester_dataset_1_2.zip':
+ 'https://web.archive.org/web/20250407210107id_/https://eigentaste.berkeley.edu/dataset/archive/jester_dataset_1_2.zip',
+ 'jester_dataset_1_3.zip':
+ 'https://web.archive.org/web/20260403205912id_/https://eigentaste.berkeley.edu/dataset/archive/jester_dataset_1_3.zip',
+ 'jester_dataset_1_joke_texts.zip':
+ 'https://web.archive.org/web/20250409195739id_/https://eigentaste.berkeley.edu/dataset/archive/jester_dataset_1_joke_texts.zip'}
def fetch_upstream(force=False):
- return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
- force)
+ return fetch_archived_files(__name__.split(".")[-1], archived_urls, force,
+ tries=3)
+
+def joke_text(s):
+ "The text of a joke from its HTML page."
+ s = s.split("<!--begin of joke -->")[1].split("<!--end of joke -->")[0]
+ s = re.sub(r"<[^>]*>", " ", s)
+ return " ".join(html.unescape(s).split())
@cache_network()
@coerce_props()
@@ -41,9 +68,38 @@ def parse(alts=None):
for fnames, alt, fmt in files:
if alts is not None and alt not in alts:
continue
- if isinstance(fnames, str):
- fnames = [fnames]
- with ExitStack() as stack:
- fs = [stack.enter_context(open_upstream_file(name, fn, "rb")) for fn in fnames]
- g = parse_graph(fs, fmt, directed=True)
+ users = []
+ for part, fname in enumerate(fnames[:3]):
+ with open_upstream_file(name, fname, "rb") as f:
+ rows, = read_xls(f).values()
+ # first column: number of rated jokes, then the ratings of the
+ # 100 jokes, with 99 meaning "not rated"
+ users.extend((part + 1, row[1:]) for row in rows)
+ texts = {}
+ with open_upstream_file(name, fnames[3], "rb") as f:
+ with zipfile.ZipFile(f) as zf:
+ for i in range(1, 101):
+ with zf.open(f"jokes/init{i}.html") as jf:
+ texts[i] = joke_text(jf.read().decode("latin1"))
+ N = len(users)
+ g = Graph(directed=False)
+ g.add_vertex(N + 100)
+ g.vp.is_joke = g.new_vp("bool")
+ g.vp.is_joke.a[N:] = True
+ g.vp.joke_id = g.new_vp("int", val=-1)
+ g.vp.joke_id.a[N:] = numpy.arange(1, 101)
+ g.vp.text = g.new_vp("string")
+ for i, t in texts.items():
+ g.vp.text[N + i - 1] = t
+ g.vp.part = g.new_vp("int", val=-1)
+ g.ep.weight = g.new_ep("double")
+ elist = []
+ for u, (part, ratings) in enumerate(users):
+ g.vp.part[u] = part
+ assert len(ratings) == 100
+ for j, x in enumerate(ratings):
+ if x is None or x == 99:
+ continue
+ elist.append((u, N + j, round(float(x), 2)))
+ g.add_edge_list(elist, eprops=[g.ep.weight])
yield alt, g
Update copyright information 73a4f0d
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index 971cbc8..358a811 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -1,7 +1,7 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2020-2024 Tiago de Paula Peixoto <tiago@skewed.de>
+# 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
Update copyright information 3d288ea
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index 9504ddf..971cbc8 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -1,7 +1,7 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2020-2023 Tiago de Paula Peixoto <tiago@skewed.de>
+# Copyright (C) 2020-2024 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
Update copyright information 8d005ae
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index 853d209..9504ddf 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -1,7 +1,7 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2021 Tiago de Paula Peixoto <tiago@skewed.de>
+# Copyright (C) 2020-2023 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
Update copyright info db2efcc
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index 7c10860..853d209 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -1,7 +1,7 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2020 Tiago de Paula Peixoto <tiago@skewed.de>
+# Copyright (C) 2021 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
Un-orphan KONECT datasets 894eba5
KONECT has been brought back from the dead for the time being.
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index 861decd..7c10860 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -26,7 +26,6 @@ url = 'http://konect.cc/networks/jester1'
citation = [('K. Goldberg et al. "Eigentaste: A constant time collaborative filtering algorithm." Information Retrieval 4(2), 133-151 (2001)', 'https://doi.org/10.1023/A:1011419012209')]
icon_hash = '5818e8828cf714f319c3eae7'
upstream_prefix = 'http://konect.cc/files'
-orphan = True
files = [('download.tsv.jester1.tar.bz2', None, 'konect')]
def fetch_upstream(force=False):
Update citations to use DOIs e878874
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index 75aea3c..861decd 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -23,7 +23,7 @@ description = """Two bipartite networks of users and jokes, extracted from the o
[^icon]: Description obtained from the [ICON](https://icon.colorado.edu) project."""
tags = ['Economic', 'Preferences', 'Weighted']
url = 'http://konect.cc/networks/jester1'
-citation = [('K. Goldberg et al. "Eigentaste: A constant time collaborative filtering algorithm." Information Retrieval 4(2), 133-151 (2001)', 'http://link.springer.com/article/10.1023/A:1011419012209')]
+citation = [('K. Goldberg et al. "Eigentaste: A constant time collaborative filtering algorithm." Information Retrieval 4(2), 133-151 (2001)', 'https://doi.org/10.1023/A:1011419012209')]
icon_hash = '5818e8828cf714f319c3eae7'
upstream_prefix = 'http://konect.cc/files'
orphan = True
Add support for 'orphan' datasets and tag konect entries e330b80
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index a5e567a..75aea3c 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -26,6 +26,7 @@ url = 'http://konect.cc/networks/jester1'
citation = [('K. Goldberg et al. "Eigentaste: A constant time collaborative filtering algorithm." Information Retrieval 4(2), 133-151 (2001)', 'http://link.springer.com/article/10.1023/A:1011419012209')]
icon_hash = '5818e8828cf714f319c3eae7'
upstream_prefix = 'http://konect.cc/files'
+orphan = True
files = [('download.tsv.jester1.tar.bz2', None, 'konect')]
def fetch_upstream(force=False):
Change license from GPLv3 to AGPLv3 d754be5
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index 364200b..a5e567a 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -3,18 +3,18 @@
#
# Copyright (C) 2020 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 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 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 General Public License for more details.
+# 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 General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# 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 *
Add explicit attribution to descriptions obtained from ICON 1645314
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
index c430dde..364200b 100644
--- a/entries/jester/__init__.py
+++ b/entries/jester/__init__.py
@@ -19,7 +19,8 @@
from .. import *
title = "Jester joke ratings (2001)"
-description = "Two bipartite networks of users and jokes, extracted from the online joke recommender system Jester. A user connects to all jokes for which that user entered a rating. Edge weights give the rating score, scaled from -10 to +10. The two files differ by how many joke nodes are included, 100 or 150."
+description = """Two bipartite networks of users and jokes, extracted from the online joke recommender system Jester. A user connects to all jokes for which that user entered a rating. Edge weights give the rating score, scaled from -10 to +10. The two files differ by how many joke nodes are included, 100 or 150.[^icon]
+[^icon]: Description obtained from the [ICON](https://icon.colorado.edu) project."""
tags = ['Economic', 'Preferences', 'Weighted']
url = 'http://konect.cc/networks/jester1'
citation = [('K. Goldberg et al. "Eigentaste: A constant time collaborative filtering algorithm." Information Retrieval 4(2), 133-151 (2001)', 'http://link.springer.com/article/10.1023/A:1011419012209')]
Initial commit cc85872
diff --git a/entries/jester/__init__.py b/entries/jester/__init__.py
new file mode 100644
index 0000000..c430dde
--- /dev/null
+++ b/entries/jester/__init__.py
@@ -0,0 +1,48 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from .. import *
+
+title = "Jester joke ratings (2001)"
+description = "Two bipartite networks of users and jokes, extracted from the online joke recommender system Jester. A user connects to all jokes for which that user entered a rating. Edge weights give the rating score, scaled from -10 to +10. The two files differ by how many joke nodes are included, 100 or 150."
+tags = ['Economic', 'Preferences', 'Weighted']
+url = 'http://konect.cc/networks/jester1'
+citation = [('K. Goldberg et al. "Eigentaste: A constant time collaborative filtering algorithm." Information Retrieval 4(2), 133-151 (2001)', 'http://link.springer.com/article/10.1023/A:1011419012209')]
+icon_hash = '5818e8828cf714f319c3eae7'
+upstream_prefix = 'http://konect.cc/files'
+files = [('download.tsv.jester1.tar.bz2', None, 'konect')]
+
+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
+ if isinstance(fnames, str):
+ fnames = [fnames]
+ with ExitStack() as stack:
+ fs = [stack.enter_context(open_upstream_file(name, fn, "rb")) for fn in fnames]
+ g = parse_graph(fs, fmt, directed=True)
+ yield alt, g