History of parlnet
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: parlnet 766766d
diff --git a/entries/parlnet/__init__.py b/entries/parlnet/__init__.py
new file mode 100644
index 0000000..7dd4128
--- /dev/null
+++ b/entries/parlnet/__init__.py
@@ -0,0 +1,151 @@
+#! /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 *
+
+title = "Bill cosponsorship in European parliaments (parlnet, 1983-2015)"
+description = """Networks of legislative cosponsorship among the members of 27 parliamentary chambers of 19 European countries and Israel, from the parlnet project of F. Briatte, one network per chamber and legislature (150 networks, covering legislatures that started between 1983 and 2015). The bills (mostly private bills, and in some chambers also government bills, motions or resolutions) and their sponsors were scraped from the websites and open data portals of the parliaments. Only bills with at least two sponsors are used. Nodes are members of parliament (MPs) who sponsored at least one such bill, and a directed edge $(i,j)$ means that MP $i$ cosponsored at least one bill whose first author was MP $j$ (the first author of each bill is connected to every other sponsor of the bill, and not the cosponsors among themselves). In the Austrian Nationalrat, only the MPs who filed the bills are known, not the further supporters. Party affiliations that changed during a legislature are recorded as the longest one, and some parties are recoded as parliamentary groups (see the appendix of the paper). MPs with the same name are distinguished by a suffix (e.g. "-1").
+
+The networks are named after the country (ISO code), the chamber when the country has two (e.g. `fr_an` for the French Assemblée nationale, `fr_se` for the French Sénat, `it_ca` and `it_se`, `cz_po` and `cz_se`, `ch_cn` and `ch_cs`, `be_ch` and `be_se`, `ie_da` and `ie_se`, `ro_ca` and `ro_se`), and the first year of the legislature, e.g. `at1995`, `fr_an2012`, `se2014`.
+
+The data come from the file `parlnet.rda` of the project's repository (commit `7c2cf94`, April 2017), which also contains, for 123 of the networks, "committee co-membership" networks (objects `conet_*`): these contain exactly the same ties as the corresponding cosponsorship networks (each directed tie as an undirected edge), with the number of committees shared by the two MPs as their only attribute, which is included here as the edge property `committee` of the cosponsorship networks, so they are not included separately. The local paths of the MPs' photographs, and two bookkeeping vertex attributes of the Finnish networks (`party_length`, `constituency_length`), are also left out.
+
+The networks have the following properties:
+
+- `country`, `chamber` (the chamber's name as a Wikipedia page title, in the language `lang`), `chamber_type` (`Lower`, `Upper` or `Unicameral`), `legislature` (its number or name), `years` (the period of the legislature), `lang` (the language of the Wikipedia titles of the chamber and constituencies), `ipu` (the identifier of the chamber in the IPU PARLINE database), `seats` (the statutory number of seats), `n_cosponsored` (the number of cosponsored bills): graph properties.
+- `name`: the name of the MP.
+- `sex`: `F` or `M`.
+- `born`: the year of birth (-1 if unknown).
+- `party`: the abbreviation of the MP's party.
+- `partyname`: the name of the party.
+- `lr`: the Left/Right score of the party, from 0 (left) to 10 (right), from the ParlGov database (NaN if not available).
+- `constituency`: the MP's constituency, generally as a Wikipedia page title (empty if not available, e.g. in the Belgian Senate).
+- `nyears`: the seniority of the MP, in years in the same chamber before the legislature (-1 if unknown).
+- `url`: the address of the MP's profile on the website of the parliament.
+- `n_au`, `n_co`, `n_bills`: the numbers of cosponsored bills on which the MP is the first author, a cosponsor, and either.
+- Edge `weight`: the number of bills first-authored by $j$ and cosponsored by $i$.
+- Edge `nfw`: the "Newman-Fowler" weight, $\\sum_k Y_{ij}(k)/c_j(k)$, where $c_j(k)$ is the number of cosponsors of the $k$-th bill of $j$, and $Y_{ij}(k)=1$ if $i$ is one of them.
+- Edge `gsw`: the "Gross-Shalizi" weight, the Newman-Fowler weight divided by its maximum, $\\sum_k 1/c_j(k)$ (between 0 and 1).
+- Edge `committee`: the number of parliamentary committees of which both MPs are members (only in the 123 networks where committee memberships are available)."""
+tags = ['Political', 'Social', 'Governance', 'Weighted', 'Metadata']
+url = ['https://github.com/briatte/parlnet', 'http://f.briatte.org/parlviz']
+citation = [('F. Briatte, "Network patterns of legislative collaboration in twenty parliaments." Network Science 4(2), 266-271 (2016)',
+ 'https://doi.org/10.1017/nws.2015.31')]
+icon_hash = None
+upstream_license = ("Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)",
+ "https://creativecommons.org/licenses/by-sa/4.0/")
+upstream_prefix = 'https://raw.githubusercontent.com/briatte/parlnet/7c2cf94af675679b913f84e076a9449a37294561'
+
+# The cosponsorship networks of parlnet.rda, in the order of the file
+alts = ['at1994', 'at1995', 'at1999', 'at2002', 'at2006', 'at2008', 'at2013',
+ 'be_ch1991', 'be_ch1995', 'be_ch1999', 'be_ch2003', 'be_ch2007',
+ 'be_ch2010', 'be_ch2014', 'be_se1995', 'be_se1999', 'be_se2003',
+ 'be_se2007', 'be_se2010', 'bg2005', 'bg2009', 'bg2013', 'bg2014',
+ 'ch_cn1995', 'ch_cn1999', 'ch_cn2003', 'ch_cn2007', 'ch_cn2011',
+ 'ch_cs1995', 'ch_cs1999', 'ch_cs2003', 'ch_cs2007', 'ch_cs2011',
+ 'cz_po1996', 'cz_po1998', 'cz_po2002', 'cz_po2006', 'cz_po2010',
+ 'cz_po2013', 'cz_se1996', 'cz_se1998', 'cz_se2002', 'cz_se2006',
+ 'cz_se2010', 'cz_se2013', 'dk2004', 'dk2005', 'dk2007', 'dk2011',
+ 'dk2015', 'ee2007', 'ee2011', 'ee2015', 'fi1999', 'fi2003', 'fi2007',
+ 'fi2011', 'fr_an1986', 'fr_an1988', 'fr_an1997', 'fr_an2002',
+ 'fr_an2007', 'fr_an2012', 'fr_se1986', 'fr_se1988', 'fr_se1993',
+ 'fr_se1997', 'fr_se2002', 'fr_se2007', 'fr_se2012', 'hu1998', 'hu2002',
+ 'hu2006', 'hu2010', 'hu2014', 'ie_da1997', 'ie_da2002', 'ie_da2007',
+ 'ie_da2011', 'ie_se1997', 'ie_se2002', 'ie_se2007', 'ie_se2011',
+ 'il2009', 'il2013', 'il2015', 'is1995', 'is1999', 'is2003', 'is2007',
+ 'is2009', 'is2013', 'it_ca1983', 'it_ca1987', 'it_ca1992', 'it_ca1994',
+ 'it_ca1996', 'it_ca2001', 'it_ca2006', 'it_ca2008', 'it_ca2013',
+ 'it_se1996', 'it_se2001', 'it_se2006', 'it_se2008', 'it_se2013',
+ 'lt1992', 'lt1996', 'lt2000', 'lt2004', 'lt2008', 'lt2012', 'no1985',
+ 'no1989', 'no1993', 'no1997', 'no2001', 'no2005', 'no2009', 'no2013',
+ 'pt1991', 'pt1995', 'pt1999', 'pt2002', 'pt2005', 'pt2009', 'pt2011',
+ 'ro_ca1996', 'ro_ca2000', 'ro_ca2004', 'ro_ca2008', 'ro_ca2012',
+ 'ro_se1996', 'ro_se2000', 'ro_se2004', 'ro_se2008', 'ro_se2012',
+ 'se1988', 'se1991', 'se1994', 'se1998', 'se2002', 'se2006', 'se2010',
+ 'se2014', 'sk1998', 'sk2002', 'sk2006', 'sk2010', 'sk2012']
+files = [('parlnet.rda', alt, None) for alt in alts]
+
+def first(x, default=None):
+ if x is None or len(x) == 0 or x[0] is None:
+ return default
+ return x[0]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+@cache_network()
+@coerce_props(vforce={"name": "string", "lr": "double"})
+@annotate()
+def parse(alts=None):
+ global files
+ name = __name__.split(".")[-1]
+ data = None
+ for fname, alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+ if data is None:
+ with open_upstream_file(name, fname, "rb") as f:
+ data = read_rdata(f)
+ net = network_from_rdata(data[f"net_{alt}"])
+ gal, vattrs, edges = net["gal"], net["vattrs"], net["edges"]
+ g = Graph(directed=True)
+ g.add_vertex(int(first(gal["n"])))
+ for k, gk, t in [("country", "country", "string"),
+ ("chamber", "chamber", "string"),
+ ("type", "chamber_type", "string"),
+ ("legislature", "legislature", "string"),
+ ("years", "years", "string"),
+ ("lang", "lang", "string"),
+ ("ipu", "ipu", "int"),
+ ("seats", "seats", "int"),
+ ("n_cosponsored", "n_cosponsored", "int")]:
+ g.gp[gk] = g.new_gp(t, val=first(gal[k]))
+ def vals(k, default, conv=lambda x: x):
+ return [conv(first(v.get(k), default)) for v in vattrs]
+ g.vp.name = g.new_vp("string", vals=vals("vertex.names", ""))
+ g.vp.sex = g.new_vp("string", vals=vals("sex", ""))
+ g.vp.born = g.new_vp("int", vals=vals("born", -1, int))
+ g.vp.party = g.new_vp("string", vals=vals("party", ""))
+ g.vp.partyname = g.new_vp("string", vals=vals("partyname", ""))
+ g.vp.lr = g.new_vp("double", vals=vals("lr", numpy.nan, float))
+ g.vp.constituency = g.new_vp("string", vals=vals("constituency", ""))
+ g.vp.nyears = g.new_vp("int", vals=vals("nyears", -1, int))
+ g.vp.url = g.new_vp("string", vals=vals("url", ""))
+ # the Estonian networks call n_au "n_a"
+ n_au = "n_au" if "n_au" in vattrs[0] else "n_a"
+ g.vp.n_au = g.new_vp("int", vals=vals(n_au, 0, int))
+ g.vp.n_co = g.new_vp("int", vals=vals("n_co", 0, int))
+ g.vp.n_bills = g.new_vp("int", vals=vals("n_bills", 0, int))
+ has_comm = "committee" in edges[0][2]
+ g.ep.weight = g.new_ep("int")
+ g.ep.nfw = g.new_ep("double")
+ g.ep.gsw = g.new_ep("double")
+ eprops = [g.ep.weight, g.ep.nfw, g.ep.gsw]
+ if has_comm:
+ g.ep.committee = g.new_ep("int")
+ eprops.append(g.ep.committee)
+ el = []
+ for s, t, a in edges:
+ row = [s, t, int(first(a["raw"])), float(first(a["nfw"])),
+ float(first(a["gsw"]))]
+ if has_comm:
+ row.append(int(first(a["committee"], -1)))
+ el.append(row)
+ g.add_edge_list(el, eprops=eprops)
+ yield alt, g