History of cordis_projects
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/cordis_projects/__init__.py b/entries/cordis_projects/__init__.py
index af0eee5..d8bb070 100644
--- a/entries/cordis_projects/__init__.py
+++ b/entries/cordis_projects/__init__.py
@@ -80,7 +80,7 @@ citation = [('Publications Office of the European Union, "CORDIS - EU funded pro
('Publications Office of the European Union, "CORDIS - EU research projects under FP7 (2007-2013)." data.europa.eu (2026)', _doi + "11"),
('Publications Office of the European Union, "CORDIS - EU research projects under Horizon 2020 (2014-2020)." data.europa.eu (2026)', _doi + "12"),
('Publications Office of the European Union, "CORDIS - EU research projects under HORIZON EUROPE (2021-2027)." data.europa.eu (2026)', _doi + "20")]
-icon_hash = None
+date_added = '2026-10-31'
upstream_license = ("European Commission reuse notice (CC BY 4.0)",
"https://eur-lex.europa.eu/eli/dec/2011/833/oj")
# The files are replaced in place by CORDIS (no versioned copies); those of
[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/cordis_projects/__init__.py b/entries/cordis_projects/__init__.py
index 8e0600f..af0eee5 100644
--- a/entries/cordis_projects/__init__.py
+++ b/entries/cordis_projects/__init__.py
@@ -40,18 +40,33 @@ The data of CORDIS are updated in place (in particular those of Horizon 2020 and
The networks have the following properties:
-- `is_project`: whether the node is a project (otherwise it is an organization).
-- `name`: the name of the organization, or the title of the project.
-- `id`: the identifier of the organization (its PIC; empty if there is none) or of the project (its grant agreement number or reference).
-- `acronym`: the acronym of the project, or the short name of the organization.
-- `program` (network `combined` only): the program of the project (`fp1`, ..., `h2020`, `horizon_europe`; empty for organizations).
-- Organizations: `sme` (whether it is a small or medium-sized enterprise: `true`, `false` or empty if unknown), `activity_type` (`HES`: higher or secondary education, `REC`: research organization, `PRC`: private company, `PUB`: public body, `OTH`: other, and in FP6 also `IND`: industry and `GOV`: government; empty if unknown, as for almost all organizations before FP7), `city`, `post_code`, `country` (two-letter code, e.g. `UK` for the United Kingdom and `EL` for Greece), `nuts_code` (the NUTS region code), `latitude` and `longitude` (`NaN` if unknown), `url` (its web site).
-- Projects: `status` (`CLOSED`, `SIGNED`, `TERMINATED`; mostly empty before FP7), `start_date` and `end_date` (Unix time, -1 if unknown), `ec_signature_date` (Unix time of the signature of the grant, -1 if unknown), `total_cost` and `ec_max_contribution` (in euros, `NaN` if unknown), `legal_basis` (the specific program, e.g. `FP1-BRITE`, `H2020-EU.1.1.`), `topics`, `master_call`, `sub_call`, `funding_scheme` (e.g. `ERC-STG`, `RIA`), `grant_doi` (the DOI of the grant), `keywords` (from Horizon 2020), `euroscivoc` (the fields of science of the European Science Vocabulary assigned to the project, as paths such as `natural sciences/physical sciences/nuclear physics/nuclear fusion`, separated by `|`).
-- Edge `role`: the role of the organization in the project: `coordinator`, `participant`, `partner`, `associatedPartner`, `internationalPartner` or `thirdParty` (a few rows with misplaced fields in the data have other values).
-- Edge `order`: the position of the organization in the list of participants of the project.
-- Edge `ec_contribution` and `net_ec_contribution`: the (net) contribution of the EU to the organization for the project, in euros (`NaN` if unknown, as before FP7).
-- Edge `total_cost`: the total cost of the participation of the organization, in euros (`NaN` if unknown).
-- Edge `end_of_participation`: whether the participation ended early (`true`, `false`, or empty if unknown)."""
+- (Node) `is_project`: whether the node is a project (otherwise it is an organization).
+- (Node) `name`: the name of the organization, or the title of the project.
+- (Node) `id`: the identifier of the organization (its PIC; empty if there is none) or of the project (its grant agreement number or reference).
+- (Node) `acronym`: the acronym of the project, or the short name of the organization.
+- (Node) `program` (network `combined` only): the program of the project (`fp1`, ..., `h2020`, `horizon_europe`; empty for organizations).
+- (Node) `sme` (organizations): whether it is a small or medium-sized enterprise: `true`, `false` or empty if unknown.
+- (Node) `activity_type` (organizations): `HES`: higher or secondary education, `REC`: research organization, `PRC`: private company, `PUB`: public body, `OTH`: other, and in FP6 also `IND`: industry and `GOV`: government; empty if unknown, as for almost all organizations before FP7.
+- (Node) `city`, `post_code` (organizations): the city and post code of the organization.
+- (Node) `country` (organizations): two-letter code, e.g. `UK` for the United Kingdom and `EL` for Greece.
+- (Node) `nuts_code` (organizations): the NUTS region code.
+- (Node) `latitude` and `longitude` (organizations): the coordinates of the organization (`NaN` if unknown).
+- (Node) `url` (organizations): its web site.
+- (Node) `status` (projects): `CLOSED`, `SIGNED`, `TERMINATED`; mostly empty before FP7.
+- (Node) `start_date` and `end_date` (projects): the start and end dates of the project (Unix time, -1 if unknown).
+- (Node) `ec_signature_date` (projects): Unix time of the signature of the grant, -1 if unknown.
+- (Node) `total_cost` and `ec_max_contribution` (projects): the total cost and the maximum contribution of the EU (in euros, `NaN` if unknown).
+- (Node) `legal_basis` (projects): the specific program, e.g. `FP1-BRITE`, `H2020-EU.1.1.`.
+- (Node) `topics`, `master_call`, `sub_call` (projects): the topics, master call and sub call of the project.
+- (Node) `funding_scheme` (projects): the funding scheme (e.g. `ERC-STG`, `RIA`).
+- (Node) `grant_doi` (projects): the DOI of the grant.
+- (Node) `keywords` (projects): the keywords (from Horizon 2020).
+- (Node) `euroscivoc` (projects): the fields of science of the European Science Vocabulary assigned to the project, as paths such as `natural sciences/physical sciences/nuclear physics/nuclear fusion`, separated by `|`.
+- (Edge) `role`: the role of the organization in the project: `coordinator`, `participant`, `partner`, `associatedPartner`, `internationalPartner` or `thirdParty` (a few rows with misplaced fields in the data have other values).
+- (Edge) `order`: the position of the organization in the list of participants of the project.
+- (Edge) `ec_contribution` and `net_ec_contribution`: the (net) contribution of the EU to the organization for the project, in euros (`NaN` if unknown, as before FP7).
+- (Edge) `total_cost`: the total cost of the participation of the organization, in euros (`NaN` if unknown).
+- (Edge) `end_of_participation`: whether the participation ended early (`true`, `false`, or empty if unknown)."""
tags = ['Social', 'Collaboration', 'Affiliation', 'Bipartite', 'Multigraph', 'Unweighted', 'Metadata']
url = ['https://cordis.europa.eu/projects',
'https://data.europa.eu/data/datasets?query=CORDIS%20EU%20research%20projects']
[entries] New entry: cordis_projects 7392ba9
diff --git a/entries/cordis_projects/__init__.py b/entries/cordis_projects/__init__.py
new file mode 100644
index 0000000..8e0600f
--- /dev/null
+++ b/entries/cordis_projects/__init__.py
@@ -0,0 +1,278 @@
+#! /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 math
+from calendar import timegm
+from datetime import datetime
+
+from .. import *
+
+title = "EU research projects and their participants (CORDIS, 1984-2026)"
+description = r"""Participations of organizations in the research projects funded by the European Union under its Framework Programmes for research and innovation, from FP1 (1984-1987) to Horizon Europe (2021-2027), from the project data of CORDIS (Community Research and Development Information Service) of the European Commission. Each network is the undirected bipartite network of the organizations (universities, research centers, companies, public bodies, ...) and the projects of one Framework Programme, where an edge links an organization to a project in which it takes part, as coordinator, participant, partner, associated partner or third party.
+
+The networks are the programs:
+
+- `fp1` (1984-1987), `fp2` (1987-1991), `fp3` (1990-1994), `fp4` (1994-1998), `fp5` (1998-2002), `fp6` (2002-2006), `fp7` (2007-2013): the closed earlier Framework Programmes;
+- `h2020`: Horizon 2020 (2014-2020);
+- `horizon_europe`: Horizon Europe (2021-2027), still ongoing;
+- `combined`: all the programs together, in one network.
+
+Organizations are identified by the participant identification code (PIC) of the European Commission where the data give one (in FP7, Horizon 2020 and Horizon Europe, and for a few organizations of FP4-FP6); otherwise (almost all organizations of FP1-FP6) they are identified by their exact name and country as given in the data, so that an organization whose name is spelled differently in different projects appears as several nodes. In the network `combined`, the organizations of all programs are merged in the same way: by their PIC where they have one, otherwise by their exact name and country. Since the PICs are missing before FP7, an organization that took part in earlier programs is thus usually split into several nodes (one with its PIC, from FP7 on, and one or more without, from the earlier programs, one per spelling of its name), and different organizations of the same name and country before FP7 are merged into one node. The attributes of an organization in `combined` are those of the last program in which it appears; the projects are those of all programs, with their program as a property (the project ids of different programs are kept apart). An organization that takes part in a project in several roles (e.g. as participant and as third party) has several edges to it, one per participation. Every project of the program is a node, including the few without any listed participant.
+
+The data of CORDIS are updated in place (in particular those of Horizon 2020 and Horizon Europe, which change as projects are signed and closed); the networks are of the files of October 2026 (the earlier programs were last updated in October 2024 and January 2025). The descriptions of the projects ("objective"), their reports, publications and deliverables, and the street addresses and contact forms of the organizations are left out.
+
+The networks have the following properties:
+
+- `is_project`: whether the node is a project (otherwise it is an organization).
+- `name`: the name of the organization, or the title of the project.
+- `id`: the identifier of the organization (its PIC; empty if there is none) or of the project (its grant agreement number or reference).
+- `acronym`: the acronym of the project, or the short name of the organization.
+- `program` (network `combined` only): the program of the project (`fp1`, ..., `h2020`, `horizon_europe`; empty for organizations).
+- Organizations: `sme` (whether it is a small or medium-sized enterprise: `true`, `false` or empty if unknown), `activity_type` (`HES`: higher or secondary education, `REC`: research organization, `PRC`: private company, `PUB`: public body, `OTH`: other, and in FP6 also `IND`: industry and `GOV`: government; empty if unknown, as for almost all organizations before FP7), `city`, `post_code`, `country` (two-letter code, e.g. `UK` for the United Kingdom and `EL` for Greece), `nuts_code` (the NUTS region code), `latitude` and `longitude` (`NaN` if unknown), `url` (its web site).
+- Projects: `status` (`CLOSED`, `SIGNED`, `TERMINATED`; mostly empty before FP7), `start_date` and `end_date` (Unix time, -1 if unknown), `ec_signature_date` (Unix time of the signature of the grant, -1 if unknown), `total_cost` and `ec_max_contribution` (in euros, `NaN` if unknown), `legal_basis` (the specific program, e.g. `FP1-BRITE`, `H2020-EU.1.1.`), `topics`, `master_call`, `sub_call`, `funding_scheme` (e.g. `ERC-STG`, `RIA`), `grant_doi` (the DOI of the grant), `keywords` (from Horizon 2020), `euroscivoc` (the fields of science of the European Science Vocabulary assigned to the project, as paths such as `natural sciences/physical sciences/nuclear physics/nuclear fusion`, separated by `|`).
+- Edge `role`: the role of the organization in the project: `coordinator`, `participant`, `partner`, `associatedPartner`, `internationalPartner` or `thirdParty` (a few rows with misplaced fields in the data have other values).
+- Edge `order`: the position of the organization in the list of participants of the project.
+- Edge `ec_contribution` and `net_ec_contribution`: the (net) contribution of the EU to the organization for the project, in euros (`NaN` if unknown, as before FP7).
+- Edge `total_cost`: the total cost of the participation of the organization, in euros (`NaN` if unknown).
+- Edge `end_of_participation`: whether the participation ended early (`true`, `false`, or empty if unknown)."""
+tags = ['Social', 'Collaboration', 'Affiliation', 'Bipartite', 'Multigraph', 'Unweighted', 'Metadata']
+url = ['https://cordis.europa.eu/projects',
+ 'https://data.europa.eu/data/datasets?query=CORDIS%20EU%20research%20projects']
+_doi = "https://doi.org/10.2906/112117098108/"
+citation = [('Publications Office of the European Union, "CORDIS - EU funded projects under FP1 (1984-1987)." data.europa.eu (2026)', _doi + "18"),
+ ('Publications Office of the European Union, "CORDIS - EU funded projects under FP2 (1987-1991)." data.europa.eu (2026)', _doi + "14"),
+ ('Publications Office of the European Union, "CORDIS - EU funded projects under FP3 (1990-1994)." data.europa.eu (2026)', _doi + "16"),
+ ('Publications Office of the European Union, "CORDIS - EU research projects under FP4 (1994-1998)." data.europa.eu (2026)', _doi + "17"),
+ ('Publications Office of the European Union, "CORDIS - EU research projects under FP5 (1998-2002)." data.europa.eu (2026)', _doi + "15"),
+ ('Publications Office of the European Union, "CORDIS - EU research projects under FP6 (2002-2006)." data.europa.eu (2026)', _doi + "13"),
+ ('Publications Office of the European Union, "CORDIS - EU research projects under FP7 (2007-2013)." data.europa.eu (2026)', _doi + "11"),
+ ('Publications Office of the European Union, "CORDIS - EU research projects under Horizon 2020 (2014-2020)." data.europa.eu (2026)', _doi + "12"),
+ ('Publications Office of the European Union, "CORDIS - EU research projects under HORIZON EUROPE (2021-2027)." data.europa.eu (2026)', _doi + "20")]
+icon_hash = None
+upstream_license = ("European Commission reuse notice (CC BY 4.0)",
+ "https://eur-lex.europa.eu/eli/dec/2011/833/oj")
+# The files are replaced in place by CORDIS (no versioned copies); those of
+# h2020 and HORIZON were of 2026-10-08 when the entry was made, the others
+# of 2024-10-22 (fp7: 2025-01-02).
+upstream_prefix = 'https://cordis.europa.eu/data'
+_programmes = [("fp1", "fp1", "csv/"), ("fp2", "fp2", "csv/"),
+ ("fp3", "fp3", "csv/"), ("fp4", "fp4", "csv/"),
+ ("fp5", "fp5", "csv/"), ("fp6", "fp6", "csv/"),
+ ("fp7", "fp7", ""), ("h2020", "h2020", ""),
+ ("HORIZON", "horizon_europe", "")]
+_files = [(tuple(f"cordis-{p}projects-csv.zip:{d}{m}.csv"
+ for m in ("project", "organization", "euroSciVoc")), alt, None)
+ for p, alt, d in _programmes]
+# the network `combined` reads the files of all programs
+files = _files + [(tuple(f for fn, alt, fmt in _files for f in fn),
+ "combined", None)]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+csv.field_size_limit(1 << 30)
+
+def to_time(x):
+ "Dates (YYYY-MM-DD, possibly with a time) as Unix time (-1 if missing)."
+ x = (x or "").strip()
+ if not x:
+ return -1
+ try:
+ return timegm(datetime.fromisoformat(x).timetuple())
+ except ValueError:
+ return -1
+
+def to_float(x):
+ try:
+ return float(x)
+ except (TypeError, ValueError):
+ return math.nan
+
+def to_int(x):
+ try:
+ return int(x)
+ except (TypeError, ValueError):
+ return -1
+
+def geolocation(x):
+ """The coordinates (latitude, longitude) of the field `geolocation`,
+ given as "lat,lon", or as "(lon,lat)" in parentheses."""
+ x = (x or "").strip()
+ try:
+ if x.startswith("("):
+ lon, lat = x.strip("()").split(",")
+ else:
+ lat, lon = x.split(",")
+ return float(lat), float(lon)
+ except ValueError:
+ return math.nan, math.nan
+
+_pprops = [("acronym", "acronym", "string"), ("status", "status", "string"),
+ ("start_date", "startDate", "int64_t"),
+ ("end_date", "endDate", "int64_t"),
+ ("ec_signature_date", "ecSignatureDate", "int64_t"),
+ ("total_cost", "totalCost", "double"),
+ ("ec_max_contribution", "ecMaxContribution", "double"),
+ ("legal_basis", "legalBasis", "string"),
+ ("topics", "topics", "string"),
+ ("master_call", "masterCall", "string"),
+ ("sub_call", "subCall", "string"),
+ ("funding_scheme", "fundingScheme", "string"),
+ ("grant_doi", "grantDoi", "string"),
+ ("keywords", "keywords", "string")]
+_oprops = [("acronym", "shortName", "string"), ("sme", "SME", "string"),
+ ("activity_type", "activityType", "string"),
+ ("city", "city", "string"), ("post_code", "postCode", "string"),
+ ("country", "country", "string"),
+ ("nuts_code", "nutsCode", "string"),
+ ("url", "organizationURL", "string")]
+_eprops = [("role", "role", "string"), ("order", "order", "int"),
+ ("ec_contribution", "ecContribution", "double"),
+ ("net_ec_contribution", "netEcContribution", "double"),
+ ("total_cost", "totalCost", "double"),
+ ("end_of_participation", "endOfParticipation", "string")]
+
+def convert(val, t):
+ if t == "string":
+ return (val or "").strip()
+ if t == "int64_t":
+ return to_time(val)
+ if t == "int":
+ return to_int(val)
+ return to_float(val)
+
+_check_rows = None # for checks only: read the first rows of each file
+
+def read_rows(name, fname):
+ with open_upstream_file(name, fname, "r", encoding="utf-8-sig",
+ newline="") as f:
+ for i, row in enumerate(csv.DictReader(f, delimiter=";")):
+ if _check_rows is not None and i >= _check_rows:
+ break
+ yield row
+
+def build(name, progs):
+ """The bipartite network of the organizations and projects of the
+ programs `progs`, a list of (program, (project file, organization
+ file, euroSciVoc file)). With several programs, the projects are keyed
+ by program and id, and the organizations by their id where they have
+ one, otherwise by their exact name and country; the attributes of an
+ organization are those of the last program in which it appears."""
+ g = Graph(directed=False)
+ vprops = {"is_project": "bool", "name": "string", "id": "string",
+ "latitude": "double", "longitude": "double",
+ "euroscivoc": "string"}
+ if len(progs) > 1:
+ vprops["program"] = "string"
+ for k, s, t in _pprops + _oprops:
+ vprops[k] = t
+ for k, t in vprops.items():
+ g.vp[k] = g.new_vp(t)
+ for k, s, t in _eprops:
+ g.ep[k] = g.new_ep(t)
+
+ orgs = {}
+ edges = []
+ evals = {k: [] for k, s, t in _eprops}
+ for prog, (fproj, forg, fvoc) in progs:
+ projects = {}
+
+ def add_project(pid):
+ v = g.add_vertex()
+ projects[pid] = int(v)
+ g.vp.is_project[v] = True
+ g.vp.id[v] = pid
+ if "program" in g.vp:
+ g.vp.program[v] = prog
+ return v
+
+ for row in read_rows(name, fproj):
+ pid = row["id"].strip()
+ if pid in projects:
+ continue
+ v = add_project(pid)
+ g.vp.name[v] = (row["title"] or "").strip()
+ for k, s, t in _pprops:
+ if s in row:
+ g.vp[k][v] = convert(row[s], t)
+
+ voc = {}
+ for row in read_rows(name, fvoc):
+ path = (row.get("euroSciVocPath") or "").strip().strip("/")
+ if path:
+ voc.setdefault(row["projectID"].strip(), []).append(path)
+ for pid, paths in voc.items():
+ if pid in projects:
+ g.vp.euroscivoc[projects[pid]] = "|".join(dict.fromkeys(paths))
+
+ seen = set() # organizations already updated in this program
+ for row in read_rows(name, forg):
+ pid = (row["projectID"] or "").strip()
+ oid = (row["organisationID"] or "").strip()
+ oname = (row["name"] or "").strip()
+ key = oid if oid else (None, oname, (row["country"] or "").strip())
+ if pid not in projects:
+ add_project(pid)
+ u = orgs.get(key)
+ if u is None:
+ u = orgs[key] = int(g.add_vertex())
+ if key not in seen:
+ seen.add(key)
+ v = g.vertex(u)
+ g.vp.id[v] = oid
+ g.vp.name[v] = oname
+ for k, s, t in _oprops:
+ g.vp[k][v] = convert(row.get(s), t)
+ g.vp.latitude[v], g.vp.longitude[v] = \
+ geolocation(row.get("geolocation"))
+ edges.append((u, projects[pid]))
+ for k, s, t in _eprops:
+ evals[k].append(convert(row.get(s), t))
+ g.add_edge_list(edges)
+ # properties of the other kind of node
+ is_project = g.vp.is_project.a.astype(bool)
+ for k in ("start_date", "end_date", "ec_signature_date"):
+ g.vp[k].a[~is_project] = -1
+ for k in ("total_cost", "ec_max_contribution"):
+ g.vp[k].a[~is_project] = math.nan
+ for k in ("latitude", "longitude"):
+ g.vp[k].a[is_project] = math.nan
+ for k, s, t in _eprops:
+ if t == "string":
+ for e, x in zip(g.edges(), evals[k]):
+ g.ep[k][e] = x
+ else:
+ g.ep[k].a = evals[k]
+ 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
+ if alt == "combined":
+ progs = [(a, fn) for fn, a, f in _files]
+ else:
+ progs = [(alt, fnames)]
+ yield alt, build(name, progs)