logo Netzschleuder network catalog, repository, and centrifuge

History of data_citation_corpus

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/data_citation_corpus/__init__.py b/entries/data_citation_corpus/__init__.py
index 1492257..37b99cd 100644
--- a/entries/data_citation_corpus/__init__.py
+++ b/entries/data_citation_corpus/__init__.py
@@ -49,7 +49,7 @@ url = ['https://makedatacount.org/data-citation/',
        'https://doi.org/10.5281/zenodo.11196858']
 citation = [('DataCite, Make Data Count, "Data Citation Corpus Data File." Zenodo, v4.1 (2025)',
              'https://doi.org/10.5281/zenodo.16901115')]
-icon_hash = None
+date_added = '2026-10-31'
 upstream_license = ("CC0", "https://creativecommons.org/publicdomain/zero/1.0/")
 # Zenodo records (versions) of the data file
 upstream_prefix = 'https://zenodo.org/records'
[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/data_citation_corpus/__init__.py b/entries/data_citation_corpus/__init__.py
index 13b8588..1492257 100644
--- a/entries/data_citation_corpus/__init__.py
+++ b/entries/data_citation_corpus/__init__.py
@@ -34,12 +34,16 @@ Publications are identified by their DOIs (in lower case, without the `https://d
 
 The networks have the following properties:
 
-- `is_dataset`: whether the node is a dataset (otherwise it is a publication).
-- `name`: the DOI of the publication, or the DOI or accession number of the dataset.
-- Datasets: `repository` (the repository in which the dataset is stored, e.g. `European Nucleotide Archive`), `title` (the title of the dataset, if known), `subjects` (the fields of science of the dataset, separated by `; `), `affiliations` and `funders` (the affiliations of the creators and the funders of the dataset, if known, separated by `; `).
-- Publications: `publisher`, `journal`, `published_date` (the publication date, as Unix time; -1 if unknown).
-- Edge `source`: the source of the citation: `datacite` (DataCite Event Data), `czi` (CZI Science Knowledge Graph), `asap` (Aligning Science Across Parkinson's) or `eupmc` (Europe PMC); in `v1_0`, an internal identifier of the source in the corpus.
-- Edge `created`: the time at which the citation was added to the corpus (Unix time)."""
+- (Node) `is_dataset`: whether the node is a dataset (otherwise it is a publication).
+- (Node) `name`: the DOI of the publication, or the DOI or accession number of the dataset.
+- (Node) `repository` (datasets): the repository in which the dataset is stored, e.g. `European Nucleotide Archive`.
+- (Node) `title` (datasets): the title of the dataset, if known.
+- (Node) `subjects` (datasets): the fields of science of the dataset, separated by `; `.
+- (Node) `affiliations` and `funders` (datasets): the affiliations of the creators and the funders of the dataset, if known, separated by `; `.
+- (Node) `publisher`, `journal` (publications): the publisher and journal of the publication.
+- (Node) `published_date` (publications): the publication date, as Unix time; -1 if unknown.
+- (Edge) `source`: the source of the citation: `datacite` (DataCite Event Data), `czi` (CZI Science Knowledge Graph), `asap` (Aligning Science Across Parkinson's) or `eupmc` (Europe PMC); in `v1_0`, an internal identifier of the source in the corpus.
+- (Edge) `created`: the time at which the citation was added to the corpus (Unix time)."""
 tags = ['Informational', 'Citation', 'Bipartite', 'Multigraph', 'Unweighted', 'Metadata']
 url = ['https://makedatacount.org/data-citation/',
        'https://doi.org/10.5281/zenodo.11196858']
[entries] New entry: data_citation_corpus by Tiago de Paula Peixoto 75cb232
diff --git a/entries/data_citation_corpus/__init__.py b/entries/data_citation_corpus/__init__.py
new file mode 100644
index 0000000..13b8588
--- /dev/null
+++ b/entries/data_citation_corpus/__init__.py
@@ -0,0 +1,215 @@
+#! /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 csv
+import io
+import json
+import zipfile
+from datetime import datetime
+
+from .. import *
+
+title = "Data citations of scholarly publications (Data Citation Corpus, 2024-2025)"
+description = r"""Citations of research datasets by scholarly publications (journal articles and preprints), from the Data Citation Corpus of DataCite and Make Data Count. The citations were collected from several sources: the links between publications and datasets registered with DataCite (DataCite Event Data), the mentions of datasets (DOIs and accession numbers, e.g. of GenBank, the European Nucleotide Archive or the Protein Data Bank) found in the full texts of articles with machine learning by the Chan Zuckerberg Initiative (CZI Science Knowledge Graph), the data citations of the Aligning Science Across Parkinson's (ASAP) initiative, and, from version 4.0, the data citations mined from the literature by Europe PMC.
+
+Each network is a release of the corpus: `v1_0` (January 2024), `v1_1` (May 2024), `v2_0` (August 2024), `v3_0` (February 2025), `v4_0` (July 2025) and `v4_1` (August 2025, a correction of v4.0). In each network, a directed edge goes from a publication to a dataset that it cites; the networks are bipartite, with publications and datasets as the two kinds of nodes. Each citation record of the corpus is an edge, so that a pair cited according to several sources (or several times by the same source) has several (parallel) edges.
+
+Publications are identified by their DOIs (in lower case, without the `https://doi.org/` prefix), and datasets by their DOIs (likewise) or accession numbers, as given in the corpus. The metadata of the datasets and publications (repository, publisher, journal, subjects, affiliations, funders) are given per citation record; the nodes get those of their first record. In version 1.0, the repositories, publishers and journals are given only by internal identifiers of the corpus (left out).
+
+The networks have the following properties:
+
+- `is_dataset`: whether the node is a dataset (otherwise it is a publication).
+- `name`: the DOI of the publication, or the DOI or accession number of the dataset.
+- Datasets: `repository` (the repository in which the dataset is stored, e.g. `European Nucleotide Archive`), `title` (the title of the dataset, if known), `subjects` (the fields of science of the dataset, separated by `; `), `affiliations` and `funders` (the affiliations of the creators and the funders of the dataset, if known, separated by `; `).
+- Publications: `publisher`, `journal`, `published_date` (the publication date, as Unix time; -1 if unknown).
+- Edge `source`: the source of the citation: `datacite` (DataCite Event Data), `czi` (CZI Science Knowledge Graph), `asap` (Aligning Science Across Parkinson's) or `eupmc` (Europe PMC); in `v1_0`, an internal identifier of the source in the corpus.
+- Edge `created`: the time at which the citation was added to the corpus (Unix time)."""
+tags = ['Informational', 'Citation', 'Bipartite', 'Multigraph', 'Unweighted', 'Metadata']
+url = ['https://makedatacount.org/data-citation/',
+       'https://doi.org/10.5281/zenodo.11196858']
+citation = [('DataCite, Make Data Count, "Data Citation Corpus Data File." Zenodo, v4.1 (2025)',
+             'https://doi.org/10.5281/zenodo.16901115')]
+icon_hash = None
+upstream_license = ("CC0", "https://creativecommons.org/publicdomain/zero/1.0/")
+# Zenodo records (versions) of the data file
+upstream_prefix = 'https://zenodo.org/records'
+files = [("11196859/files/2024-01-30-data-citation-corpus-v1.0.zip", "v1_0", None),
+         ("11216814/files/2024-05-10-data-citation-corpus-v1.1.zip", "v1_1", None),
+         ("13376773/files/2024-08-23-data-citation-corpus-v2.0.zip", "v2_0", None),
+         ("14897662/files/2025-02-01-data-citation-corpus-v3.0-csv.zip", "v3_0", None),
+         ("16546069/files/2025-07-27-data-citation-corpus-v4.0-csv.zip", "v4_0", None),
+         ("16901115/files/2025-08-15-data-citation-corpus-v4.1-csv.zip", "v4_1", None)]
+
+def fetch_upstream(force=False):
+    return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+                                force)
+
+# For checks only: if set, only (about) this many records are read
+_check_rows = None
+
+csv.field_size_limit(1 << 30)
+
+def to_time(x):
+    if not x:
+        return -1
+    try:
+        return int(datetime.fromisoformat(x.replace("Z", "+00:00")).timestamp())
+    except ValueError:
+        return -1
+
+def norm_doi(x):
+    x = (x or "").strip()
+    for p in ("https://doi.org/", "http://doi.org/", "https://dx.doi.org/",
+              "doi:"):
+        if x.lower().startswith(p):
+            x = x[len(p):]
+            break
+    return x.lower() if x.startswith("10.") else x
+
+def title_of(x):
+    "A field that is either a string or a {'title': ...} object."
+    if isinstance(x, dict):
+        return x.get("title") or ""
+    return x or ""
+
+def join(x):
+    "A list field (JSON list, or a string) as a string."
+    if isinstance(x, list):
+        out = []
+        for y in x:
+            if isinstance(y, dict):
+                y = y.get("title") or y.get("name") or ""
+            if y:
+                out.append(str(y))
+        return "; ".join(out)
+    x = x or ""
+    if x.startswith("["):
+        try:
+            return join(json.loads(x))
+        except ValueError:
+            pass
+    return x
+
+def record(r):
+    """A citation record (of any of the formats of the releases) as
+    (publication, dataset, dataset properties, publication properties,
+    source, created)."""
+    if "subjId" in r:
+        # versions 1.0 and 1.1: objId is the publication, subjId the dataset
+        pub, ds = r.get("objId"), r.get("subjId")
+        repo = title_of(r.get("repository"))
+        publisher = title_of(r.get("publisher"))
+        journal = title_of(r.get("journal"))
+        source = r.get("source") or r.get("sourceId") or ""
+    else:
+        pub, ds = r.get("publication"), r.get("dataset")
+        repo, publisher, journal = (r.get("repository"), r.get("publisher"),
+                                    r.get("journal"))
+        source = r.get("source") or ""
+    return (norm_doi(pub), norm_doi(ds),
+            (repo or "", r.get("title") or "", join(r.get("subjects")),
+             join(r.get("affiliations")), join(r.get("funders"))),
+            (publisher or "", journal or "", to_time(r.get("publishedDate"))),
+            source, to_time(r.get("created")))
+
+def read_json_records(f):
+    """The records of a JSON file of version 1.1: an array with one record
+    per line."""
+    for line in io.TextIOWrapper(f, "utf-8"):
+        line = line.strip().lstrip("[").rstrip("]").rstrip(",").strip()
+        if not line:
+            continue
+        yield json.loads(line)
+
+def read_records(z):
+    names = sorted(n for n in z.namelist() if n.endswith(".csv"))
+    if not names:
+        for n in sorted(n for n in z.namelist() if n.endswith(".json")):
+            with z.open(n) as f:
+                yield from read_json_records(f)
+        return
+    for n in names:
+        with z.open(n) as f:
+            yield from csv.DictReader(io.TextIOWrapper(f, "utf-8", newline=""))
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+    name = __name__.split(".")[-1]
+    for fname, alt, fmt in files:
+        if alts is not None and alt not in alts:
+            continue
+        pubs, dss = {}, {}
+        pprops, dprops = [], []
+        edges, sources, created = [], [], []
+        with open_upstream_file(name, fname, "rb", seekable=True) as f:
+            z = zipfile.ZipFile(f)
+            for i, r in enumerate(read_records(z)):
+                if _check_rows is not None and i >= _check_rows:
+                    break
+                pub, ds, dp, pp, source, t = record(r)
+                if not pub or not ds:
+                    continue
+                u = pubs.get(pub)
+                if u is None:
+                    u = pubs[pub] = len(pubs)
+                    pprops.append(pp)
+                v = dss.get(ds)
+                if v is None:
+                    v = dss[ds] = len(dss)
+                    dprops.append(dp)
+                edges.append((u, v))
+                sources.append(source)
+                created.append(t)
+        Np = len(pubs)
+        g = Graph(directed=True)
+        g.add_vertex(Np + len(dss))
+        g.add_edge_list([(u, Np + v) for u, v in edges])
+        del edges
+        g.vp.is_dataset = g.new_vp("bool")
+        g.vp.is_dataset.a[Np:] = 1
+        g.vp.name = g.new_vp("string")
+        for k, i in pubs.items():
+            g.vp.name[i] = k
+        for k, i in dss.items():
+            g.vp.name[Np + i] = k
+        del pubs, dss
+        for j, k in enumerate(("repository", "title", "subjects",
+                               "affiliations", "funders")):
+            p = g.new_vp("string")
+            for i, x in enumerate(dprops):
+                if x[j]:
+                    p[Np + i] = x[j]
+            g.vp[k] = p
+        for j, k in enumerate(("publisher", "journal")):
+            p = g.new_vp("string")
+            for i, x in enumerate(pprops):
+                if x[j]:
+                    p[i] = x[j]
+            g.vp[k] = p
+        g.vp.published_date = g.new_vp("int64_t")
+        g.vp.published_date.a = -1
+        g.vp.published_date.a[:Np] = [x[2] for x in pprops]
+        p = g.new_ep("string")
+        for e, x in zip(g.edges(), sources):
+            p[e] = x
+        g.ep.source = p
+        g.ep.created = g.new_ep("int64_t", vals=created)
+        yield alt, g