logo Netzschleuder network catalog, repository, and centrifuge

History of tudor_letters

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/tudor_letters/__init__.py b/entries/tudor_letters/__init__.py
index 5ec5a9b..54393cc 100644
--- a/entries/tudor_letters/__init__.py
+++ b/entries/tudor_letters/__init__.py
@@ -49,7 +49,7 @@ citation = [('R. Ahnert and S. E. Ahnert, "Tudor Networks of Power." Oxford Univ
              'https://doi.org/10.1093/oso/9780198858973.001.0001'),
             ('R. Ahnert, S. E. Ahnert, J. Cree and L. Fikkers, "Tudor Networks of Power - correspondence network dataset." Apollo - University of Cambridge Repository (2023)',
              'https://doi.org/10.17863/CAM.99562')]
-icon_hash = None
+date_added = '2026-10-31'
 upstream_license = ('CC BY-NC-SA 4.0', 'https://creativecommons.org/licenses/by-nc-sa/4.0/')
 
 # (the repository at the commit of the last update, 2023-10-11)
[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/tudor_letters/__init__.py b/entries/tudor_letters/__init__.py
index 01dce4f..5ec5a9b 100644
--- a/entries/tudor_letters/__init__.py
+++ b/entries/tudor_letters/__init__.py
@@ -30,18 +30,18 @@ The coordinates of the places of writing are given in the dataset with the signs
 
 The network has the following properties:
 
-- `label`: the label of the person in the dataset.
-- `person_id`: the person's identifier in the dataset.
-- `is_woman`: whether the person is in the authors' list of women correspondents.
-- `webpage`: the URL of the person's biographical web page (Wikipedia, Kate Emerson's biographies, ...), or empty.
-- `webpage_type`: the kind of the web page: `WP` (Wikipedia), `KEH` (Kate Emerson's "Who Was Who in Tudor England"), `ODNB`, `HOP` (History of Parliament), `VIAF`, `other`, or empty.
-- `keh_context`: the context of the person in Kate Emerson's biographies (e.g. under which person's entry they appear), or empty.
-- `wikidata`, `viaf`: the Wikidata and VIAF identifiers of the person, or empty.
-- Edge `date_from`, `date_to`: the range of possible dates of the letter (YYYY-MM-DD; equal if the date is known exactly), or empty if unknown.
-- Edge `ms_id` and `ms_ref`: the identifier of the manuscript in State Papers Online and its archival reference (e.g. `SP 1/231 f.189`).
-- Edge `place`: the place of writing (as disambiguated by the authors), or empty if not known; 1,111 letters have several places of writing, separated by "; ".
-- Edge `place_id`: the identifiers of the places of writing (empty if not known).
-- Edge `latitude`, `longitude`: the coordinates of the places of writing (NaN for the 105 places without coordinates)."""
+- (Node) `label`: the label of the person in the dataset.
+- (Node) `person_id`: the person's identifier in the dataset.
+- (Node) `is_woman`: whether the person is in the authors' list of women correspondents.
+- (Node) `webpage`: the URL of the person's biographical web page (Wikipedia, Kate Emerson's biographies, ...), or empty.
+- (Node) `webpage_type`: the kind of the web page: `WP` (Wikipedia), `KEH` (Kate Emerson's "Who Was Who in Tudor England"), `ODNB`, `HOP` (History of Parliament), `VIAF`, `other`, or empty.
+- (Node) `keh_context`: the context of the person in Kate Emerson's biographies (e.g. under which person's entry they appear), or empty.
+- (Node) `wikidata`, `viaf`: the Wikidata and VIAF identifiers of the person, or empty.
+- (Edge) `date_from`, `date_to`: the range of possible dates of the letter (YYYY-MM-DD; equal if the date is known exactly), or empty if unknown.
+- (Edge) `ms_id` and `ms_ref`: the identifier of the manuscript in State Papers Online and its archival reference (e.g. `SP 1/231 f.189`).
+- (Edge) `place`: the place of writing (as disambiguated by the authors), or empty if not known; 1,111 letters have several places of writing, separated by "; ".
+- (Edge) `place_id`: the identifiers of the places of writing (empty if not known).
+- (Edge) `latitude`, `longitude`: the coordinates of the places of writing (NaN for the 105 places without coordinates)."""
 tags = ['Social', 'Communication', 'Offline', 'Unweighted', 'Multigraph', 'Timestamps', 'Metadata']
 url = ['https://github.com/tudor-networks-of-power/code/tree/main/TNP_DATA',
        'https://doi.org/10.17863/CAM.99562']
[entries] New entry: tudor_letters by Tiago de Paula Peixoto fe4243d
diff --git a/entries/tudor_letters/__init__.py b/entries/tudor_letters/__init__.py
new file mode 100644
index 0000000..01dce4f
--- /dev/null
+++ b/entries/tudor_letters/__init__.py
@@ -0,0 +1,157 @@
+#! /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 re
+
+from .. import *
+
+title = "Tudor State Papers correspondence (1509-1603)"
+description = r"""The correspondence network of the Tudor State Papers, the official government records of England under the Tudors (1509-1603), from the dataset of R. Ahnert, S. E. Ahnert, J. Cree and L. Fikkers that accompanies the book "Tudor Networks of Power". It contains, to the best of the authors' knowledge, all the letters in the State Papers Domestic and Foreign, extracted from the metadata of the State Papers Online resource (Gale Cengage), which follows the State Papers Calendars of the 19th century; the identities of people and places of writing were disambiguated and deduplicated by the authors.
+
+Nodes are people (or, in some cases, collective senders and recipients, e.g. a convent or a council), and a directed edge from $i$ to $j$ is a letter sent by $i$ to $j$; pairs of people exchanged many letters (a multigraph). Many letters are dated only by a range of possible dates (sometimes very wide, when the calendars give no date); letters without any date have empty dates, and a few malformed dates of the dataset (e.g. the end date `5720101`, or months `00` and `91`) are kept as they are. The people's labels are as in the dataset (e.g. "A. Lady Kingston"); for a subset of people, the dataset gives links to biographical resources (Wikipedia, Kate Emerson's "Who Was Who in Tudor England", the Oxford Dictionary of National Biography, the History of Parliament, VIAF and Wikidata), of which the specific web page, and the Wikidata and VIAF identifiers, are included. The list of women among the correspondents used in the book (chapter 5; 857 identifiers, of which 838 are among the people of the dataset), from the authors' code repository, is included as a vertex property. The other derived data of the repository (co-authorship and mention networks, itineraries) are not included.
+
+The coordinates of the places of writing are given in the dataset with the signs of the longitudes reversed (west positive); they are given here with the usual convention (east positive).
+
+The network has the following properties:
+
+- `label`: the label of the person in the dataset.
+- `person_id`: the person's identifier in the dataset.
+- `is_woman`: whether the person is in the authors' list of women correspondents.
+- `webpage`: the URL of the person's biographical web page (Wikipedia, Kate Emerson's biographies, ...), or empty.
+- `webpage_type`: the kind of the web page: `WP` (Wikipedia), `KEH` (Kate Emerson's "Who Was Who in Tudor England"), `ODNB`, `HOP` (History of Parliament), `VIAF`, `other`, or empty.
+- `keh_context`: the context of the person in Kate Emerson's biographies (e.g. under which person's entry they appear), or empty.
+- `wikidata`, `viaf`: the Wikidata and VIAF identifiers of the person, or empty.
+- Edge `date_from`, `date_to`: the range of possible dates of the letter (YYYY-MM-DD; equal if the date is known exactly), or empty if unknown.
+- Edge `ms_id` and `ms_ref`: the identifier of the manuscript in State Papers Online and its archival reference (e.g. `SP 1/231 f.189`).
+- Edge `place`: the place of writing (as disambiguated by the authors), or empty if not known; 1,111 letters have several places of writing, separated by "; ".
+- Edge `place_id`: the identifiers of the places of writing (empty if not known).
+- Edge `latitude`, `longitude`: the coordinates of the places of writing (NaN for the 105 places without coordinates)."""
+tags = ['Social', 'Communication', 'Offline', 'Unweighted', 'Multigraph', 'Timestamps', 'Metadata']
+url = ['https://github.com/tudor-networks-of-power/code/tree/main/TNP_DATA',
+       'https://doi.org/10.17863/CAM.99562']
+citation = [('R. Ahnert and S. E. Ahnert, "Tudor Networks of Power." Oxford University Press (2023)',
+             'https://doi.org/10.1093/oso/9780198858973.001.0001'),
+            ('R. Ahnert, S. E. Ahnert, J. Cree and L. Fikkers, "Tudor Networks of Power - correspondence network dataset." Apollo - University of Cambridge Repository (2023)',
+             'https://doi.org/10.17863/CAM.99562')]
+icon_hash = None
+upstream_license = ('CC BY-NC-SA 4.0', 'https://creativecommons.org/licenses/by-nc-sa/4.0/')
+
+# (the repository at the commit of the last update, 2023-10-11)
+upstream_prefix = 'https://raw.githubusercontent.com/tudor-networks-of-power/code/fb6bba6b51c7bf0e33d3b0c46a6948adda7c3a4b'
+files = [(('TNP_DATA/letter_edgelist.tsv', 'TNP_DATA/people_labels.tsv',
+           'TNP_DATA/people_metadata.tsv', 'TNP_DATA/places_labels.tsv',
+           'TNP_DATA/places_metadata.tsv',
+           'CHAPTER_2/Network_Analysis_Tool/final_women.out'), None, None)]
+
+def fetch_upstream(force=False):
+    return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+                                force)
+
+def _date(x):
+    """A date YYYYMMDD as YYYY-MM-DD (empty if 0); malformed dates (two
+    end dates, e.g. 5720101) are kept as they are."""
+    if x == "0":
+        return ""
+    if not re.fullmatch(r"\d{8}", x):
+        return x
+    return f"{x[:4]}-{x[4:6]}-{x[6:]}"
+
+def _link_id(html):
+    "The identifier in the HTML link of a Wikidata or VIAF record (empty if none)."
+    m = re.search(r'href="https?://www\.(?:wikidata\.org/wiki|viaf\.org(?:/viaf)?)/([^"/]+)"', html)
+    return m.group(1) if m else ""
+
+@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
+        def rows(fname, header):
+            with open_upstream_file(name, fname, "r") as f:
+                r = csv.reader(f, delimiter="\t")
+                if header:
+                    next(r)
+                yield from r
+
+        people = {int(r[0]): r[1] for r in rows(fnames[1], False)}
+        places = {int(r[0]): r[1] for r in rows(fnames[3], True)}
+        # (places without coordinates are given as "-")
+        coords = {int(r[0]): (float(r[1]), -float(r[2]))
+                  for r in rows(fnames[4], False) if r[1] != "-"}
+        women = {int(r[0]) for r in rows(fnames[5], False) if r}
+
+        g = Graph(directed=True)
+        vmap = {}
+        def vertex(i):
+            v = vmap.get(i)
+            if v is None:
+                v = vmap[i] = g.add_vertex()
+            return v
+        # the people, in the order of their identifiers (also those without
+        # letters, if any)
+        for i in sorted(people):
+            vertex(i)
+        eprops = [("date_from", "string"), ("date_to", "string"),
+                  ("ms_id", "string"), ("ms_ref", "string"),
+                  ("place", "string"), ("place_id", "vector<int>"),
+                  ("latitude", "vector<double>"),
+                  ("longitude", "vector<double>")]
+        vals = {"place_id": [], "latitude": [], "longitude": []}
+        for k, t in eprops:
+            g.ep[k] = g.new_ep(t)
+        edges = []
+        for r in rows(fnames[0], True):
+            s, t, d0, d1, ms, p, ref = r
+            # (some letters have several places of writing, e.g. "11,334,846")
+            ps = [int(x) for x in p.split(",") if int(x) != 0]
+            lat = [coords.get(x, (numpy.nan, numpy.nan))[0] for x in ps]
+            lon = [coords.get(x, (numpy.nan, numpy.nan))[1] for x in ps]
+            edges.append((int(vertex(int(s))), int(vertex(int(t))), _date(d0),
+                          _date(d1), ms, ref,
+                          "; ".join(places.get(x, "") for x in ps)))
+            for k, x in zip(["place_id", "latitude", "longitude"], [ps, lat, lon]):
+                vals[k].append(x)
+        g.add_edge_list(edges, eprops=[g.ep[k] for k, t in eprops[:5]])
+        for k, t in eprops[5:]:
+            # (the edge indices are in the order of the edge list)
+            for e in g.edges():
+                g.ep[k][e] = vals[k][g.edge_index[e]]
+
+        g.vp.label = g.new_vp("string")
+        g.vp.person_id = g.new_vp("int")
+        g.vp.is_woman = g.new_vp("bool")
+        for k in ["webpage", "webpage_type", "keh_context", "wikidata", "viaf"]:
+            g.vp[k] = g.new_vp("string")
+        for i, v in vmap.items():
+            g.vp.label[v] = people.get(i, "")
+            g.vp.person_id[v] = i
+            g.vp.is_woman[v] = i in women
+        for r in rows(fnames[2], True):
+            v = vmap.get(int(r[0]))
+            if v is None:
+                continue
+            g.vp.webpage[v] = r[2].strip()
+            g.vp.webpage_type[v] = r[3].strip()
+            g.vp.keh_context[v] = r[4].strip()
+            g.vp.wikidata[v] = _link_id(r[5])
+            g.vp.viaf[v] = _link_id(r[6])
+        yield alt, g