logo Netzschleuder network catalog, repository, and centrifuge

History of web_of_life

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] New entry: web_of_life by Tiago de Paula Peixoto 75cc21d
diff --git a/entries/web_of_life/__init__.py b/entries/web_of_life/__init__.py
new file mode 100644
index 0000000..863d28b
--- /dev/null
+++ b/entries/web_of_life/__init__.py
@@ -0,0 +1,224 @@
+#! /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 csv
+import json
+import math
+
+title = "Web of Life ecological networks (1923-2021)"
+description = """The ecological networks of the Web of Life database, developed and maintained by the lab of J. Bascompte (University of Zurich), which compiles species interaction networks from the literature, from its own fieldwork and from its collaborators. There are 321 networks of seven kinds: pollination (174), host-parasite (51), seed dispersal (34), food webs (33), anemone-fish (17), plant-herbivore (7) and plant-ant (5). Each network is named after its Web of Life identifier (e.g. `M_PL_015`), whose prefix gives the kind of interaction: `M_PL` pollination, `M_SD` seed dispersal, `M_PA` plant-ant, `M_AF` anemone-fish (mutualistic); `A_HP` host-parasite, `A_PH` plant-herbivore (antagonistic); `FW` food webs. The data were retrieved from the database's API (all interactions, the species of each network, and the network metadata) in October 2026; the database is updated in place. The metadata list seven further identifiers (`FW_012` to `FW_016`, `M_AF_002` and `M_PL_072`) that have no interactions of their own: they group the networks numbered after them (e.g. `M_AF_002_01` to `M_AF_002_16`), which are included. Conversely, four networks with interactions (`A_PH_001` to `A_PH_003`, plant-herbivore, and `FW_008`, a food web) are missing from the metadata, so that their graph properties other than `network_type` are empty.
+
+Nodes are species (or higher taxa, morphospecies, or groups such as "Detritus" or "Zooplankton"), as listed by the database for each network. In `M_PA_004` (Blüthgen et al. 2004) two plants are listed twice (for their extrafloral and floral nectaries), with interactions that cannot be told apart; each is a single node here, with the sum of the abundances. Except for the food webs, the networks are bipartite and undirected, and an edge connects a plant (or host, or anemone) to an animal (or parasite, or fish) interacting with it: a flower visitor (pollination), a frugivore eating its fruits (seed dispersal), an ant (plant-ant), a herbivore (plant-herbivore), a parasite (host-parasite), or an anemonefish living in it (anemone-fish). The side of each node is given by the property `is_pollinator`, `is_seed_disperser`, `is_ant`, `is_herbivore`, `is_parasite` or `is_fish`, respectively. The food webs are directed: an edge (i, j) indicates that taxon j feeds on taxon i. The networks marked as weighted by the database have the edge property `weight` (whose meaning is given by the graph property `cell_values_description`, e.g. the number of visits, when available); the others record only the presence of the interaction.
+
+Many of these networks are also included in other entries, obtained from other sources: `M_PL_015` is the entry `plant_pol_petanidou` (from the Web of Life), and the following studies are also part of the entry `iwdb` (Interaction Web Database), sometimes with a different subdivision into networks: Arroyo et al. 1982 (`M_PL_001` to `M_PL_003`), Barrett and Helenurm 1987 (`M_PL_004`), Bartomeus et al. 2008 (`M_PL_058`, one network instead of twelve plots), Bezerra et al. 2009 (`M_PL_059`), Clements and Long 1923 (`M_PL_005`), Dupont et al. 2003 (`M_PL_008`), Elberling and Olesen 1999 (`M_PL_009`), Hocking 1968 (`M_PL_014`), Inouye and Pyke 1988 (`M_PL_019`), Kaiser-Bunbury et al. 2010 (`M_PL_060_*`, 24 networks instead of two), Kato et al. 1990 (`M_PL_021`), Kevan 1970 (`M_PL_020`), McMullen 1993 (`M_PL_026`), Medan et al. 2002 (`M_PL_022`, `M_PL_023`), Memmott 1999 (`M_PL_017`), Mosquin and Martin 1967 (`M_PL_024`), Motten 1982 (`M_PL_025`), Olesen et al. 2002 (`M_PL_011`), Ollerton et al. 2003 (`M_PL_013`), Ramírez and Brito 1992 (`M_PL_030`), Robertson 1929 (`M_PL_062`), Schemske et al. 1978 (`M_PL_032`), Small 1976 (`M_PL_033`), Vázquez 2002 (`M_PL_051`, one network instead of eight sites), Blüthgen et al. 2004 (`M_PA_004`), Davidson et al. 1989 (`M_PA_001`), Davidson and Fisher 1991 (`M_PA_002`), Fonseca and Ganade 1996 (`M_PA_003`), Joern 1979 (`A_PH_004`, `A_PH_005`), Leather 1991 (`A_PH_006`, `A_PH_007`), Beehler 1983 (`M_SD_002`), Poulin et al. 1999 (`M_SD_032`, `M_SD_033`), Schleuning et al. 2010 (`M_SD_034`), Snow and Snow 1988 (`M_SD_011`), Sorensen 1981 (`M_SD_025`), Ollerton et al. 2007 (`M_AF_001`), Ricciardi et al. 2010 (`M_AF_002*`), and the stream food webs of Thompson, Townsend and collaborators (`FW_012` to `FW_015*`). They are kept here, since the collection is the dataset.
+
+The networks have the following properties:
+
+- `name`: the name of the taxon, as given by the database.
+- `is_pollinator`, `is_seed_disperser`, `is_ant`, `is_herbivore`, `is_parasite`, `is_fish` (bipartite networks, one of them depending on the kind): whether the node is on the animal (or parasite, or fish) side of the network.
+- `role`: the role of the species in the network as given by the database (e.g. "Plant", "Pollinator", "Host", "Parasite", "Predator", "Prey").
+- `is_resource`: whether the species is a resource (e.g. the host or the prey), as given by the database.
+- `abundance`: the abundance of the species (NaN if not given; its meaning is given by the graph property `abundance_description` when available); only in the 59 networks in which the database gives abundances (not all missing or zero).
+- `resolution`: the taxonomic level to which the taxon was identified ("Species", "Genus", "Family", "Order", "Group", ...; empty if not given).
+- `kingdom`, `order`, `family`: the taxonomy of the taxon (empty if not given).
+- Edge `weight` (weighted networks only): the strength of the interaction.
+- Graph properties: `network_type` (the kind of interaction), `author` and `reference` (the source of the data), `location`, `location_address`, `country`, `region`, `latitude` and `longitude` (the location of the study), `cell_values_description` (the meaning of the weights) and `abundance_description` (the meaning of the abundances)."""
+tags = ['Biological', 'Food web', 'Weighted', 'Unweighted', 'Metadata']
+url = 'https://www.web-of-life.es/'
+citation = [('Bascompte Lab, "Web of Life: ecological networks database." University of Zurich (2026)',
+             'https://www.web-of-life.es/')]
+icon_hash = None
+upstream_license = ('Free use, may be shared with collaborators; users must mention "This work has used the Web of Life database (www.web-of-life.es)"',
+                    'https://www.web-of-life.es/help/help.php')
+upstream_prefix = 'https://www.web-of-life.es'
+
+ids = [
+    'A_HP_001', 'A_HP_002', 'A_HP_003', 'A_HP_004', 'A_HP_005', 'A_HP_006',
+    'A_HP_007', 'A_HP_008', 'A_HP_009', 'A_HP_010', 'A_HP_011', 'A_HP_012',
+    'A_HP_013', 'A_HP_014', 'A_HP_015', 'A_HP_016', 'A_HP_017', 'A_HP_018',
+    'A_HP_019', 'A_HP_020', 'A_HP_021', 'A_HP_022', 'A_HP_023', 'A_HP_024',
+    'A_HP_025', 'A_HP_026', 'A_HP_027', 'A_HP_028', 'A_HP_029', 'A_HP_030',
+    'A_HP_031', 'A_HP_032', 'A_HP_033', 'A_HP_034', 'A_HP_035', 'A_HP_036',
+    'A_HP_037', 'A_HP_038', 'A_HP_039', 'A_HP_040', 'A_HP_041', 'A_HP_042',
+    'A_HP_043', 'A_HP_044', 'A_HP_045', 'A_HP_046', 'A_HP_047', 'A_HP_048',
+    'A_HP_049', 'A_HP_050', 'A_HP_051', 'A_PH_001', 'A_PH_002', 'A_PH_003',
+    'A_PH_004', 'A_PH_005', 'A_PH_006', 'A_PH_007', 'FW_001', 'FW_002',
+    'FW_003', 'FW_004', 'FW_005', 'FW_006', 'FW_007', 'FW_008', 'FW_009',
+    'FW_010', 'FW_011', 'FW_012_01', 'FW_012_02', 'FW_013_01', 'FW_013_02',
+    'FW_013_03', 'FW_013_04', 'FW_013_05', 'FW_014_01', 'FW_014_02',
+    'FW_014_03', 'FW_014_04', 'FW_015_01', 'FW_015_02', 'FW_015_03',
+    'FW_015_04', 'FW_016_01', 'FW_017_01', 'FW_017_02', 'FW_017_03',
+    'FW_017_04', 'FW_017_05', 'FW_017_06', 'M_AF_001', 'M_AF_002_01',
+    'M_AF_002_02', 'M_AF_002_03', 'M_AF_002_04', 'M_AF_002_05', 'M_AF_002_06',
+    'M_AF_002_07', 'M_AF_002_08', 'M_AF_002_09', 'M_AF_002_10', 'M_AF_002_11',
+    'M_AF_002_12', 'M_AF_002_13', 'M_AF_002_14', 'M_AF_002_15', 'M_AF_002_16',
+    'M_PA_001', 'M_PA_002', 'M_PA_003', 'M_PA_004', 'M_PA_005', 'M_PL_001',
+    'M_PL_002', 'M_PL_003', 'M_PL_004', 'M_PL_005', 'M_PL_006', 'M_PL_007',
+    'M_PL_008', 'M_PL_009', 'M_PL_010', 'M_PL_011', 'M_PL_012', 'M_PL_013',
+    'M_PL_014', 'M_PL_015', 'M_PL_016', 'M_PL_017', 'M_PL_018', 'M_PL_019',
+    'M_PL_020', 'M_PL_021', 'M_PL_022', 'M_PL_023', 'M_PL_024', 'M_PL_025',
+    'M_PL_026', 'M_PL_027', 'M_PL_028', 'M_PL_029', 'M_PL_030', 'M_PL_031',
+    'M_PL_032', 'M_PL_033', 'M_PL_034', 'M_PL_035', 'M_PL_036', 'M_PL_037',
+    'M_PL_038', 'M_PL_039', 'M_PL_040', 'M_PL_041', 'M_PL_042', 'M_PL_043',
+    'M_PL_044', 'M_PL_045', 'M_PL_046', 'M_PL_047', 'M_PL_048', 'M_PL_049',
+    'M_PL_050', 'M_PL_051', 'M_PL_052', 'M_PL_053', 'M_PL_054', 'M_PL_055',
+    'M_PL_056', 'M_PL_057', 'M_PL_058', 'M_PL_059', 'M_PL_060_01',
+    'M_PL_060_02', 'M_PL_060_03', 'M_PL_060_04', 'M_PL_060_05', 'M_PL_060_06',
+    'M_PL_060_07', 'M_PL_060_08', 'M_PL_060_09', 'M_PL_060_10', 'M_PL_060_11',
+    'M_PL_060_12', 'M_PL_060_13', 'M_PL_060_14', 'M_PL_060_15', 'M_PL_060_16',
+    'M_PL_060_17', 'M_PL_060_18', 'M_PL_060_19', 'M_PL_060_20', 'M_PL_060_21',
+    'M_PL_060_22', 'M_PL_060_23', 'M_PL_060_24', 'M_PL_061_01', 'M_PL_061_02',
+    'M_PL_061_03', 'M_PL_061_04', 'M_PL_061_05', 'M_PL_061_06', 'M_PL_061_07',
+    'M_PL_061_08', 'M_PL_061_09', 'M_PL_061_10', 'M_PL_061_11', 'M_PL_061_12',
+    'M_PL_061_13', 'M_PL_061_14', 'M_PL_061_15', 'M_PL_061_16', 'M_PL_061_17',
+    'M_PL_061_18', 'M_PL_061_19', 'M_PL_061_20', 'M_PL_061_21', 'M_PL_061_22',
+    'M_PL_061_23', 'M_PL_061_24', 'M_PL_061_25', 'M_PL_061_26', 'M_PL_061_27',
+    'M_PL_061_28', 'M_PL_061_29', 'M_PL_061_30', 'M_PL_061_31', 'M_PL_061_32',
+    'M_PL_061_33', 'M_PL_061_34', 'M_PL_061_35', 'M_PL_061_36', 'M_PL_061_37',
+    'M_PL_061_38', 'M_PL_061_39', 'M_PL_061_40', 'M_PL_061_41', 'M_PL_061_42',
+    'M_PL_061_43', 'M_PL_061_44', 'M_PL_061_45', 'M_PL_061_46', 'M_PL_061_47',
+    'M_PL_061_48', 'M_PL_062', 'M_PL_063', 'M_PL_064', 'M_PL_065', 'M_PL_066',
+    'M_PL_067', 'M_PL_068', 'M_PL_069_01', 'M_PL_069_02', 'M_PL_069_03',
+    'M_PL_070', 'M_PL_071', 'M_PL_072_01', 'M_PL_072_02', 'M_PL_072_03',
+    'M_PL_072_04', 'M_PL_072_05', 'M_PL_072_06', 'M_PL_072_07', 'M_PL_072_08',
+    'M_PL_072_09', 'M_PL_072_10', 'M_PL_072_11', 'M_PL_072_12', 'M_PL_073',
+    'M_PL_074_01', 'M_PL_074_02', 'M_PL_074_03', 'M_PL_074_04', 'M_PL_074_05',
+    'M_PL_074_06', 'M_PL_074_07', 'M_PL_074_08', 'M_PL_074_09', 'M_PL_074_10',
+    'M_PL_074_11', 'M_PL_074_12', 'M_PL_074_13', 'M_PL_074_14', 'M_PL_074_15',
+    'M_PL_074_16', 'M_PL_074_17', 'M_PL_074_18', 'M_SD_001', 'M_SD_002',
+    'M_SD_003', 'M_SD_004', 'M_SD_005', 'M_SD_006', 'M_SD_007', 'M_SD_008',
+    'M_SD_009', 'M_SD_010', 'M_SD_011', 'M_SD_012', 'M_SD_013', 'M_SD_014',
+    'M_SD_015', 'M_SD_016', 'M_SD_017', 'M_SD_018', 'M_SD_019', 'M_SD_020',
+    'M_SD_021', 'M_SD_022', 'M_SD_023', 'M_SD_024', 'M_SD_025', 'M_SD_026',
+    'M_SD_027', 'M_SD_028', 'M_SD_029', 'M_SD_030', 'M_SD_031', 'M_SD_032',
+    'M_SD_033', 'M_SD_034',
+]
+
+info_file = 'get_network_info.php'
+edges_file = 'get_networks.php'
+files = [((info_file, edges_file, f'get_species_info.php?network_name={i}'), i, None)
+         for i in ids]
+
+# The side of the bipartite networks: role of the animal (or parasite, or
+# fish) side -> name of the property marking it
+kinds = {"Pollinator": "is_pollinator", "Seed disperser": "is_seed_disperser",
+         "Ant": "is_ant", "Herbivore": "is_herbivore", "Parasite": "is_parasite",
+         "Fish": "is_fish"}
+resources = {"Plant", "Host", "Anemone"}
+
+# The kinds of the networks that are not listed in the network metadata
+prefix_types = {"A_PH": "Plant-Herbivore", "FW": "Food Webs"}
+
+info_cols = ["network_type", "author", "reference", "location",
+             "location_address", "country", "region", "latitude", "longitude",
+             "cell_values_description", "abundance_description"]
+
+def fetch_upstream(force=False):
+    return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+                                force)
+
+def to_float(x):
+    try:
+        return float(x)
+    except ValueError:
+        return math.nan
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+    name = __name__.split(".")[-1]
+    with open_upstream_file(name, info_file, "r") as f:
+        info = {r["network_name"]: r for r in csv.DictReader(f)}
+    with open_upstream_file(name, edges_file, "r") as f:
+        all_edges = {}
+        for e in json.load(f):
+            all_edges.setdefault(e["network_name"], []).append(e)
+    for (_, _, sfile), alt, fmt in files:
+        if alts is not None and alt not in alts:
+            continue
+        # A few networks are not listed in the network metadata
+        # A few networks (A_PH_001 to A_PH_003 and FW_008) are not listed in
+        # the network metadata: their kind is given by their prefix, and they
+        # are weighted if not all weights are 1
+        meta = info.get(alt)
+        if meta is None:
+            meta = {"network_type": prefix_types[alt[:alt.rindex("_")]],
+                    "is_weighted": str(int(any(e["connection_strength"] != "1"
+                                               for e in all_edges[alt])))}
+        with open_upstream_file(name, sfile, "r") as f:
+            species = list(csv.DictReader(f))
+        foodweb = meta["network_type"] == "Food Webs"
+        g = Graph(directed=foodweb)
+        roles = {r["role"] for r in species}
+        kind = None
+        if not foodweb:
+            kind, = [kinds[k] for k in roles if k in kinds]
+            assert len(roles) == 2 and len(roles & resources) == 1, (alt, roles)
+            g.vp[kind] = g.new_vp("bool")
+        g.vp.name = g.new_vp("string")
+        g.vp.role = g.new_vp("string")
+        g.vp.is_resource = g.new_vp("bool")
+        g.vp.abundance = g.new_vp("double")
+        g.vp.resolution = g.new_vp("string")
+        g.vp.kingdom = g.new_vp("string")
+        g.vp.order = g.new_vp("string")
+        g.vp.family = g.new_vp("string")
+        vs = {}
+        for r in species:
+            if r["species"] in vs:
+                # M_PA_004 lists two plants twice (for their extrafloral
+                # and floral nectaries); they are merged, adding the
+                # abundances
+                v = vs[r["species"]]
+                assert g.vp.role[v] == r["role"], (alt, r["species"])
+                g.vp.abundance[v] += to_float(r["abundance"])
+                continue
+            v = g.add_vertex()
+            vs[r["species"]] = v
+            g.vp.name[v] = r["species"].strip()
+            g.vp.role[v] = r["role"]
+            g.vp.is_resource[v] = r["is resource"] == "1"
+            g.vp.abundance[v] = to_float(r["abundance"])
+            g.vp.resolution[v] = r["taxonomic resolution"]
+            g.vp.kingdom[v] = r["kingdom"]
+            g.vp.order[v] = r["order"]
+            g.vp.family[v] = r["family"]
+            if kind is not None:
+                g.vp[kind][v] = r["role"] in kinds
+        if all(math.isnan(x) or x == 0 for x in g.vp.abundance.a):
+            # no abundances given (all missing or zero)
+            del g.vp["abundance"]
+        weighted = meta["is_weighted"] == "1"
+        if weighted:
+            g.ep.weight = g.new_ep("double")
+        for e in all_edges[alt]:
+            # species1 is the plant, host, anemone or prey
+            s, t = vs[e["species1"]], vs[e["species2"]]
+            if kind is not None:
+                assert not g.vp[kind][s] and g.vp[kind][t], (alt, e)
+            ne = g.add_edge(s, t)
+            if weighted:
+                g.ep.weight[ne] = float(e["connection_strength"])
+        for c in info_cols:
+            if c in ("latitude", "longitude"):
+                g.gp[c] = g.new_gp("double", val=to_float(meta.get(c, "")))
+            else:
+                g.gp[c] = g.new_gp("string", val=meta.get(c, "").strip())
+        yield alt, g