logo Netzschleuder network catalog, repository, and centrifuge

History of liner_shipping

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: liner_shipping by Tiago de Paula Peixoto a6ea4cf
diff --git a/entries/liner_shipping/__init__.py b/entries/liner_shipping/__init__.py
new file mode 100644
index 0000000..f5ef62c
--- /dev/null
+++ b/entries/liner_shipping/__init__.py
@@ -0,0 +1,144 @@
+#! /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 openpyxl
+
+title = "Global liner shipping networks (2015-2018)"
+description = r"""Networks of the world's container ports connected by regular liner shipping services, constructed by M. Xu and collaborators from the database of liner shipping service routes of Alphaliner (a commercial database of the container shipping industry, whose raw data are not public). Nodes are ports, and two ports are connected by an undirected edge if at least one liner service route calls at both of them: each service route, a fixed sequence of ports of call served by container ships, becomes a clique of its ports, and the network is the union of these cliques.
+
+The entry contains the versions of the network published by the authors with their articles:
+
+- `2015`: the network of April 2015 of Xu et al. (Nature Communications 2020), from all service routes, with 977 ports and 16,680 edges (the source data of Fig. 1d of the article). The edges are unweighted.
+- `2015_international` and `2017_international`: the networks of 2015 and 2017 of Xu et al. (Royal Society Open Science 2020), from the international service routes (1,316 routes in 2015), with 777 and 770 ports. The ports are identified only by numbers (which differ between the two years) and their countries. The edges have six weights, computed as the sum over the service routes that call at both ports of either 1, $1/(n-1)$, $1/[n(n-1)/2]$, $C$, $C/(n-1)$ or $C/[n(n-1)/2]$, where $n$ is the number of ports of the route and $C$ its total traffic capacity, in twenty-foot equivalent units (TEU).
+- `2018`: the network of 2018 of Xu et al. (Global Networks 2024), with 887 ports, weighted edges and the great-circle distances between the ports. In the data, each edge is listed in both directions, with the same values; it is included once.
+
+The networks are not comparable in every respect, since they were constructed from different selections of the routes; the port names of the `2015` and `2018` networks are as given (e.g. `Antwerp`, `Port Chalmers`).
+
+The networks have the following properties:
+
+- `name` (`2015`, `2018`): the name of the port.
+- `port_id` (`2015_international`, `2017_international`): the identifier of the port in the data.
+- `country` (`2015_international`, `2017_international`, `2018`): the ISO 3166-1 alpha-3 code of the country of the port.
+- `country_name` (`2018`): the name of the country of the port.
+- Edge `routes`, `weight_1n`, `weight_2nn`, `capacity`, `capacity_1n`, `capacity_2nn` (`2015_international`, `2017_international`): the six edge weights, the sums over the shared routes of 1 (the number of shared routes), $1/(n-1)$, $1/[n(n-1)/2]$, $C$, $C/(n-1)$ and $C/[n(n-1)/2]$, respectively.
+- Edge `weight` (`2018`): the weight of the link as given by the authors (derived from the traffic capacities of the shared routes).
+- Edge `distance` (`2018`): the great-circle distance between the two ports, in kilometers."""
+tags = ['Transportation', 'Economic', 'Trade', 'Weighted', 'Metadata']
+url = ['https://github.com/Network-Maritime-Complexity/Structural-core',
+       'https://doi.org/10.5281/zenodo.4018584',
+       'https://github.com/Network-Maritime-Complexity/GLSN-efficiency-vulnerability']
+citation = [('M. Xu, Q. Pan, A. Muscoloni, H. Xia, C. V. Cannistraci, "Modular gateway-ness connectivity and structural core organization in maritime network science." Nature Communications 11, 2849 (2020)',
+             'https://doi.org/10.1038/s41467-020-16619-5'),
+            ('M. Xu, Q. Pan, H. Xia, N. Masuda, "Estimating international trade status of countries from global liner shipping networks." Royal Society Open Science 7, 200386 (2020)',
+             'https://doi.org/10.1098/rsos.200386'),
+            ('M. Xu, Y. Zhu, W. Deng, Y. Shen, T. Li, "Assessing the efficiency and vulnerability of global liner shipping network." Global Networks 24(1), e12445 (2024)',
+             'https://doi.org/10.1111/glob.12445')]
+icon_hash = None
+# 2015: no license stated in the repository; 2015_international and
+# 2017_international: CC BY 4.0 (repository and Zenodo record); 2018: MIT
+upstream_license = ("CC BY 4.0 (2015_international, 2017_international), MIT (2018)", None)
+upstream_prefix = None
+
+# GitHub repositories at pinned commits, and the Zenodo record of release
+# v1.0.2 of GLSN-and-international-trade
+CORE = 'https://raw.githubusercontent.com/Network-Maritime-Complexity/Structural-core/bf3ebcdd51c01c26eac6eec7e0b7b71702f871f5'
+EFF = 'https://raw.githubusercontent.com/Network-Maritime-Complexity/GLSN-efficiency-vulnerability/721a98d2c430e805ced991d31c81d8df600049a2'
+urls = {"glsn_2015_fig1d.xlsx": f"{CORE}/Source%20Data/Source%20Data-Fig.%201d.xlsx",
+        "GLSN-and-international-trade-v1.0.2.zip": "https://zenodo.org/records/4018584/files/Network-Maritime-Complexity/GLSN-and-international-trade-v1.0.2.zip",
+        "glsn_2018_edges.xlsx": f"{EFF}/data/Edge%202018_with%20great%20circle%20distance%20(GC)%20and%20weight.xlsx",
+        "glsn_2018_ports.xlsx": f"{EFF}/data/port%202018.xlsx"}
+TRADE = "GLSN-and-international-trade-v1.0.2.zip:Network-Maritime-Complexity-GLSN-and-international-trade-f4e22ef/code/data"
+files = [("glsn_2015_fig1d.xlsx", "2015", None),
+         (f"{TRADE}/Edges_GLSN_2015.xlsx", "2015_international", None),
+         (f"{TRADE}/Edges_GLSN_2017.xlsx", "2017_international", None),
+         (("glsn_2018_edges.xlsx", "glsn_2018_ports.xlsx"), "2018", None)]
+
+TRADE_WEIGHTS = {"Edge weight=1": "routes", "Edge weight=1/(n-1)": "weight_1n",
+                 "Edge weight=1/[n(n-1)/2]": "weight_2nn", "Edge weight=C": "capacity",
+                 "Edge weight=C/(n-1)": "capacity_1n",
+                 "Edge weight=C/[n(n-1)/2]": "capacity_2nn"}
+
+def fetch_upstream(force=False):
+    return fetch_archived_files(__name__.split(".")[-1], urls, force=force, tries=3)
+
+def read_rows(name, fname):
+    with open_upstream_file(name, fname, "rb", seekable=True) as f:
+        wb = openpyxl.load_workbook(f, read_only=True, data_only=True)
+        rows = [r for r in wb[wb.sheetnames[0]].iter_rows(values_only=True)]
+        wb.close()
+    return rows
+
+@cache_network()
+@coerce_props(vforce={"name": "string"})
+@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
+        g = Graph(directed=False)
+        if alt == "2015":
+            rows = read_rows(name, fnames)[1:]     # first row: a caption
+            g.vp.name = g.add_edge_list([(str(a).strip(), str(b).strip())
+                                         for a, b, *_ in rows if a is not None],
+                                        hashed=True, hash_type="string")
+        elif alt.endswith("international"):
+            rows = read_rows(name, fnames)
+            header = rows[0]
+            rows = rows[1:]
+            ids = sorted({r[0] for r in rows} | {r[1] for r in rows})
+            idx = {x: i for i, x in enumerate(ids)}
+            country = {}
+            for r in rows:
+                country[r[0]] = r[2]
+                country[r[1]] = r[3]
+            g.add_vertex(len(ids))
+            g.vp.port_id = g.new_vp("int", vals=ids)
+            g.vp.country = g.new_vp("string", vals=[country[x] for x in ids])
+            cols = [header.index(c) for c in TRADE_WEIGHTS]
+            eprops = [g.new_ep("double") for c in TRADE_WEIGHTS]
+            for p, c in zip(TRADE_WEIGHTS.values(), eprops):
+                g.ep[p] = c
+            g.add_edge_list([[idx[r[0]], idx[r[1]]] + [r[c] for c in cols]
+                             for r in rows], eprops=eprops)
+        else:
+            erows = read_rows(name, fnames[0])
+            prows = read_rows(name, fnames[1])
+            ports = [r for r in prows[1:] if r[0] is not None]
+            idx = {str(r[0]).strip(): i for i, r in enumerate(ports)}
+            g.add_vertex(len(ports))
+            g.vp.name = g.new_vp("string", vals=[str(r[0]).strip() for r in ports])
+            g.vp.country = g.new_vp("string", vals=[r[1] for r in ports])
+            g.vp.country_name = g.new_vp("string", vals=[r[2] for r in ports])
+            h = erows[0]
+            i1, i2, igc, iw = (h.index("port1"), h.index("port2"), h.index("GC"),
+                               h.index("weight"))
+            seen = set()
+            edges = []
+            for r in erows[1:]:
+                u, v = idx[str(r[i1]).strip()], idx[str(r[i2]).strip()]
+                if (min(u, v), max(u, v)) in seen:
+                    continue
+                seen.add((min(u, v), max(u, v)))
+                edges.append((u, v, r[iw], r[igc]))
+            g.ep.weight = g.new_ep("double")
+            g.ep.distance = g.new_ep("double")
+            g.add_edge_list(edges, eprops=[g.ep.weight, g.ep.distance])
+        yield alt, g