Members of Parliament

Swiss Parliament: Members

The Federal Chancellery maintains data on the Swiss political system. The curia dataset is publicly available and it provides data on political parties, parliamentary comissions, members of parliament and their affiliations.

Parliament data is also available as Linked Data.

Setup

SPARQL endpoint

Swiss political data can be accessed with SPARQL queries.
You can send queries using HTTP requests. The API endpoint is https://lindas.admin.ch/query/.

SPARQL client

We will use the SparqlClient from graphly to communicate with the database. Graphly will allow us to:

  • send SPARQL queries
  • automatically add prefixes to all queries
  • format response to pandas or geopandas
In [1]:
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import datetime
import warnings

from graphly.api_client import SparqlClient

%matplotlib inline
pd.set_option('display.max_rows', 100)
warnings.filterwarnings('ignore')
In [2]:
# Uncomment to install dependencies in Colab environment
#!pip install git+https://github.com/zazuko/graphly.git
In [3]:
sparql = SparqlClient("https://int.lindas.admin.ch/query")

sparql.add_prefixes({
    "schema": "<http://schema.org/>"
})

SPARQL queries can become very long. To improve the readibility, we will work wih prefixes.

Using the add_prefixes method, we can define persistent prefixes. Every time you send a query, graphly will now automatically add the prefixes for you.

MEP by gender, age and chamber

In [4]:
# https://s.zazuko.com/5nEJGu
query = """
SELECT ?council ?name ?gender ?age ?start
FROM <https://lindas.admin.ch/fch/curia>
FROM <https://lindas.admin.ch/fch/rvov>
WHERE {
  
  VALUES ?_council {<https://politics.ld.admin.ch/council/N> <https://politics.ld.admin.ch/council/S>}
  ?member a schema:Person ;
         schema:name ?name;
         schema:gender ?gender;
         schema:birthDate ?birthday;
         schema:memberOf ?role.
  
  ?role a schema:Role;
        schema:member ?member;
        schema:startDate ?start;
        schema:memberOf ?_council.
  
  ?_council schema:name ?council.
  
       
  FILTER (lang(?council) = "de")
  FILTER NOT EXISTS { ?role schema:endDate ?end }
  
  BIND( "2022-06-16"^^<http://www.w3.org/2001/XMLSchema#date> as ?today )
  BIND(YEAR(?today)-YEAR(?birthday) as ?age)
}
ORDER BY ?age
"""

df = sparql.send_query(query)
df = df.replace({"http://schema.org/Male": "Male", "http://schema.org/Female": "Female"})
In [5]:
bucket=5
df["bucket"] = df.age.apply(lambda x: "{}-{}".format((x//bucket)*bucket, (x//bucket+1)*bucket))
counts = df.groupby(["bucket", "gender"]).size().reset_index(name='count')

fig = px.bar(counts, x="bucket", y="count", color="gender",
             barmode="stack", color_discrete_sequence=[px.colors.qualitative.Plotly[1],px.colors.qualitative.Plotly[0]])

fig.update_layout(
    title='Members of Parliament (as of today)', 
    title_x=0.5,
    yaxis_title="Share in %",
    xaxis_title="Age"
)
fig.show()

Age distribution

In [6]:
fig = make_subplots(rows=2, cols=1, shared_yaxes=True, y_title='Share in %', x_title='Age')

for i, gender in enumerate(df.gender.unique()[::-1]):
    subset=df[df.gender == gender]["age"]
    fig.append_trace(go.Histogram(x=subset, histnorm='percent', xbins=dict(start=25, end=80,size=5), name=gender), row=i+1, col=1)
fig.update_layout(
    title='Members of Parliament (as of today)', 
    title_x=0.5,
    bargap=0.1
)
fig.update_yaxes(range=[0,27.5])
fig.update_xaxes(range=[25,80])
fig.show()

Gender distribution by chamber

In [7]:
counts = df.groupby(["council", "gender"]).size().reset_index(name="count")

fig = make_subplots(rows=1, cols=2, subplot_titles=counts["council"].unique(), specs=[[{"type": "pie"}, {"type": "pie"}]])

for i, council in enumerate(counts["council"].unique()):
     fig.add_trace(go.Pie(
          values=counts[counts.council == council]["count"],
          labels=counts[counts.council == council]["gender"]
          ), row=1, col=i+1)

fig.update_annotations(yshift=-280)
fig.update_layout(height=400, title={"text": "Members of Parliament (as of today)", "x": 0.5})
fig.show()

MEP gender over time

In [8]:
# https://s.zazuko.com/3VYuVk
query = """
SELECT ?name ?gender ?birthday ?start ?end
FROM <https://lindas.admin.ch/fch/curia>
FROM <https://lindas.admin.ch/fch/rvov>
WHERE {
  
  VALUES ?_council {<https://politics.ld.admin.ch/council/N> <https://politics.ld.admin.ch/council/S>}
  ?member a schema:Person ;
         schema:name ?name;
         schema:gender ?gender;
         schema:birthDate ?birthday;
         schema:memberOf ?role.
  
  ?role a schema:Role;
        schema:member ?member;
        schema:startDate ?start;
        schema:memberOf ?_council.
  
  OPTIONAL {?role schema:endDate ?end.}
  
}
ORDER BY ?start
"""

df = sparql.send_query(query)
df.end = df.end.fillna(datetime.datetime.now())
In [9]:
m = []
f = []
yrs = range(1965,2021)

for year in yrs:
    
    point_in_time = datetime.datetime(year, 7, 1, 12, 0, 0)
    subset = df[(df.start <= point_in_time) & (point_in_time < df.end)]
    counts = subset.gender.value_counts()
    m.append(counts["http://schema.org/Male"])
    if "http://schema.org/Female" in counts:
        f.append(counts["http://schema.org/Female"])
    else:
        f.append(0)

res = pd.DataFrame(data={"year": yrs, "male": m, "female": f})
res["f_share"] = res["female"]/(res["female"] + res["male"])
In [10]:
fig = make_subplots(rows=1, cols=1)

fig.append_trace(go.Bar(x=res["year"],y=(1-res["f_share"])*100,
    name="male",
    marker_color=px.colors.qualitative.Plotly[0]), row=1, col=1)

fig.append_trace(go.Bar(x=res["year"],y=(res["f_share"])*100,
    name="female",
    marker_color=px.colors.qualitative.Plotly[1]), row=1, col=1)

fig['layout']['yaxis']['title']='MPs share in %'
fig['layout']['yaxis']['range']= [0,100]
fig.update_layout(height=800, title={"text": "Female in Swiss Parliment", "x": 0.5}, barmode = "stack",
                  legend = {"x": 1, "y": 0.37})
fig.show()

MEP: age over time

In [11]:
yrs = range(1850, 2021, 4)
#bucket=10
#all_buckets = set(range(2,9))
bucket=5
all_buckets = set(range(5,17))
bucket2age = {i: "{}-{}".format(i*bucket, (i+1)*bucket-1) for i in all_buckets}

res = pd.DataFrame()
average_age = pd.DataFrame(columns=["year", "Male", "Female"])
for year in yrs:
    
    point_in_time = datetime.datetime(year, 7, 1, 12, 0, 0)
    subset = df[(df.start <= point_in_time) & (point_in_time < df.end)]
    subset.loc[:,"age"] = point_in_time.year - subset.birthday.dt.year
    males_only = subset['gender'] == 'http://schema.org/Male'
    average_age_male = subset.loc[males_only, 'age'].mean()
    females_only = subset['gender'] == 'http://schema.org/Female'
    average_age_female = subset.loc[females_only, 'age'].mean()
    #average_age = subset["age"].mean()
    subset.loc[:,"bucket"] = subset.age.apply(lambda x: x//bucket)
    grouped = subset.groupby(["bucket"]).size().reset_index(name='count')
    grouped.loc[:, "count"] = grouped["count"]/(grouped["count"].sum())*100
    for b in all_buckets.difference(set(grouped.bucket.unique())):
        grouped = grouped.append({"bucket": b, "count": 0.0}, ignore_index=True)
    grouped.loc[:, "year"] = year
    res = res.append(grouped)
    average_age.loc[len(average_age.index)] = [year, average_age_male, average_age_female]
    
res = res.sort_values(by=["year", "bucket"]).reset_index(drop=True)
res.loc[:,"age"] = res["bucket"].replace(bucket2age)
    
In [12]:
fig = px.bar(res, x="age", y="count", animation_frame="year", range_y=[0,35])
fig.update_layout(
    title='Age Distribution', 
    title_x=0.5,
    yaxis_title="Share in %",
    xaxis_title="Age",
)
fig.show()

fig_average = px.bar(average_age, x="year", y=["Male", "Female"], barmode="group")
fig_average.update_layout(
    title='Age over time', 
    title_x=0.5,
    yaxis_title="Average Age",
    xaxis_title="Year",
    legend=dict(title=None)
)
fig_average.show()