council.Rmd
Few would disagree that the Council of the European Union (EU) – sometimes also referred to as the Council of Ministers – is the most important decision-maker in the EU’s legislative process. The Council brings together the government representatives of the Member States to, among others, negotiate EU legislation with the European Parliament as part of the ordinary legislative procedure (OLP).
Under the OLP, which is nowadays the most common type of law-making
procedure, the Council should make decisions by qualified majority. In
practice, it often decides by consensus, as Member States tend to avoid
open disagreements. Still, enough votes are taken to give us some
insight into the variation in Member State governments’ behaviour. We
access these through a dedicated API maintained by the Council, which is
also wrapped in the eurlex
package.
First we obtain the available data on Council votes using
eurlex::elx_council_votes()
and process the API
response.
# packages
library(eurlex)
library(ggplot2)
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
library(tidyr)
library(stringr)
library(ggiraph)
# pull Council voting data
cns_votes_raw <- elx_council_votes()
# vote level, keep only votes with disagreements
votes_dis <- cns_votes_raw %>%
select(voteProc, starts_with("countryCode")) %>%
select(-countryCodeNotParticipatingGrouped) %>%
distinct() %>%
filter((!is.na(countryCodeAgainstGrouped) & !is.na(countryCodeAbstainedGrouped))) %>%
pivot_longer(cols = starts_with("country"), names_to = "vote", values_to = "country") %>%
separate_rows(country, sep = "\\|") %>%
drop_na() %>%
mutate(vote = case_when(str_detect(vote, "Favour") ~ 1L,
str_detect(vote, "Absta") ~ 2L,
str_detect(vote, "Against") ~ 3L,
T ~ NA_integer_))
# country vote counts
country_votes_n <- votes_dis %>%
count(country, vote)
# weighted vote proportion
country_votes_prop <- country_votes_n %>%
mutate(value = case_when(vote == 1 ~ n * 1,
vote == 2 ~ n * -1,
vote == 3 ~ n * -2)) %>%
group_by(country) %>%
summarise(value = sum(value),
n_votes = sum(n),
prop = round(value / n_votes, 3)) %>%
ungroup()
Excluding votes where all governments voted in favour, we are left
with between 110
and 81
votes per Member
State. While these numbers do not represent the entire historical voting
record, they should still help us lift the veil on variation in Member
States’ propensity to disagree. Note that due to opt-outs not all
countries have participated in every vote.
To highlight that votes against tend to represent a strong signal of disagreement, the following simple formula is used to calculate the weighted proportion of Member State discontent: \(p_i = \frac{\sum_j^a{\text{infavour}_{ij}} - \sum_k^b{\text{abstain}_{ik}} - \sum_l^c{\text{against}_{il} * 2}}{a + b + c}\)
The data on votes are easy enough to plot interactively with
ggplot2
and ggiraph
. The following plot shows
the variation in \(p_i\) where \(i\) indexes Member States.
# viz Council votes and weighted proportion
iplot_votes_prop <- country_votes_prop %>%
mutate(tooltip = str_c(country,": ", prop, ". Total number of votes: ", n_votes)) %>%
ggplot(aes(y = reorder(country, prop), x = prop, yend = reorder(country, prop), xend = 0, color = prop)) +
geom_vline(xintercept = c(0.25,0.5,0.75), color = "grey90", lty = 2) +
geom_point_interactive(aes(tooltip = tooltip, data_id = country),
show.legend = FALSE) +
geom_segment_interactive(aes(tooltip = tooltip, data_id = country),
show.legend = FALSE) +
theme_minimal(base_family = "Arial") +
theme(legend.position = "top",
legend.justification = "left",
legend.title = element_text(face = "italic"),
plot.background = element_rect(fill = "white", color = "grey88"),
axis.text = element_text(color = "grey10", size = 12),
title = element_text(face = "bold", size = 16),
panel.grid = element_line(color = "grey94"),
axis.title = element_text(hjust = 1, size = 14),
plot.subtitle = element_text(face = "italic", size = 15),
plot.caption = element_text(face = "italic", size = 8),
strip.text = element_text(hjust = 0, face = "bold")) +
scale_x_continuous(expand = c(0.01,0)) +
scale_color_gradient(low = "red", high = "navyblue") +
labs(x = NULL,
y = NULL,
color = NULL,
fill = NULL,
title = "Legislative discontent in the Council",
subtitle = "Weighted proportion of government votes in favour on contested legislation*",
caption = "* Only legislation with at least one vote not in favour; abstentions (x1) and votes against (x2) are subtracted from votes in favour")
# interactive plot
girafe(ggobj = iplot_votes_prop,
fonts = list(sans = "Arial"),
width_svg = 12,
height_svg = 8,
options = list(opts_sizing(rescale = TRUE),
opts_toolbar(saveaspng = FALSE),
opts_tooltip(css = "background-color:gray;color:white;font-style:italic;padding:9px;border-radius:5px;font-size:15px;",
use_fill = TRUE),
opts_hover_inv(css = "opacity:0.1;"),
opts_hover(css = "fill:green;"))
)
The country comparison reveals substantial variation in the frequency of disagreement. The only Member State to ever exit the EU, the United Kingdom, was particularly active when it comes to abstaining or voting against legislation. On the other end of the scale is France, which has been happy to support almost every law or Council position put in front of it. We are unable to tell from this simple comparison whether a supportive voting record reflects satisfaction with the negotiated substance or governments’ overall stance on European integration (or both).
A more sophisticated way of scaling Member States’ preferences would involve deriving their ideal points from the votes through an item-response model.1 In our example, we assume that all votes are equally informative and important; ideally, we would want to relax this assumption.
An example application of an ideal point IRT model can be found in this paper.↩︎