The European Union (EU) is often criticized for having a heavy regulatory touch and being unwieldy. What is the reality? This automatically updated research document gives a real-time overview of the EU’s legislative output and efficiency.

As of 26 April 2024, the EU and its predecessors have produced 141916 regulations, 4313 directives, 37372 decisions and 1453 recommendations, according to Eur-Lex data pulled via the eurlex package for R1 and a SPARQL API maintained by the Publications Office of the European Union.2 (More example use cases, as well as arguments for integrating open data APIs into research workflows, can be found in this open access paper.)

The following table shows the most recent legislation published by the EU:

1 Number of acts over time

We start by looking at how the number of the four main legal acts changed over the lifespan of the EU. The number of acts adopted yearly is a parsimonious – but imperfect3 – proxy for the EU’s regulatory tendencies.

1.1 Plot

1.2 Dataframe

1.3 Code: Data

# packages
library(eurlex)
library(ggplot2)
library(dplyr)
library(purrr)
library(tidyr)
library(stringr)
library(rmarkdown)
library(modelsummary)
library(ggiraph)
library(equatiomatic)
library(DT)

# seed
set.seed(65234)

# current date
date_now <- Sys.Date()
date_now_f <- str_remove(format(date_now, "%d %B %Y"), "^0")
day_now <- as.integer(str_sub(date_now, 9, 10))
year_now <- as.integer(str_sub(date_now, 1, 4))

# acts
acts_proposals <- elx_make_query("any", sector = 3,
                                 include_date = TRUE,
                                 include_proposal = TRUE) |>
  elx_run_query() |> 
  select(-work)

# acts only
acts <- acts_proposals |> 
  filter(!is.na(celex),
         !date %in% c("1003-03-03")) |> 
  distinct(celex, .keep_all = T) |> 
  select(-proposal)

# in force
in_force <- elx_make_query("any", sector = 3,
                           include_force = TRUE,
                           include_date_force = TRUE) |>
  elx_run_query() |> 
  select(-work) |> 
  rename(date_force = dateforce) |> 
  arrange(celex, date_force) |> 
  distinct(celex, .keep_all = TRUE) |> 
  drop_na() |> 
  filter(between(as.Date(date_force), as.Date("1952-01-01"),
                 as.Date(date_now)))

# 4 main types of acts
regs <- acts |> filter(str_sub(celex,6,6) == "R")
decs <- acts |> filter(str_sub(celex,6,6) == "D")
dirs <- acts |> filter(str_sub(celex,6,6) == "L")
recs <- acts |> filter(str_sub(celex,6,6) == "H")

# proposals
proposals <- elx_make_query("proposal", include_date = TRUE) |> 
  elx_run_query() |> 
  select(-work,-type) |> 
  rename(date_proposal = date) |> 
  filter(!is.na(celex),
         !is.na(date_proposal)) |> 
  distinct(celex, .keep_all = TRUE)

# latest act titles and EuroVoc
recent_titles <- acts |> 
  arrange(desc(date), desc(celex)) |> 
  filter(str_sub(celex,6,6) %in% c("D","R","L","H")) |> 
  slice(1:50) |> 
  mutate(title = map_chr(str_c("http://publications.europa.eu/resource/celex/",
                               celex), 
                         possibly(eurlex::elx_fetch_data, otherwise = NA_character_), "title")) |> 
  mutate(date = as.Date(date))

# citation
cit_page <- paste("Michal Ovádek, '",rmarkdown::metadata$title,"', available at https://michalovadek.github.io/eulaw/, accessed on ", date_now_f,
                  sep = "")

1.4 Code: Plot

# select only main act types
relevant_acts <- bind_rows(Regulation = regs, Decison = decs, Directive = dirs,
                           Recommendation = recs,
                        .id = "type") |> 
  mutate(year = as.integer(str_sub(celex, 2,5))) |> 
  mutate(month_year = as.Date(str_replace(date, "[:digit:][:digit:]$", "01")))

# count
n_relevant <- relevant_acts |> 
  count(year, type)

# viz
ggplot(n_relevant, aes(x = year, y = n, fill = type, color = type)) +
  geom_col(show.legend = FALSE) +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  facet_wrap(~type, scale = "free_y") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white", color = "grey88"),
        panel.grid = element_line(color = "grey95"),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        plot.subtitle = element_text(face = "italic"),
        strip.text = element_text(hjust = 0, face = "bold")) +
  labs(x = NULL,
       y = "Number of acts",
       title = "Number of legal acts produced by the European Union",
       subtitle = "Aggregated at the year level by date of publication")

# vars for text
year1 <- as.integer(str_sub(date_now,1,4))-1
year2 <- as.integer(str_sub(date_now,1,4))-2

n_year1 <- n_relevant |> 
  filter(year == year1) |> 
  summarise(n = sum(n, na.rm = T)) |> 
  as.integer()

n_year2 <- n_relevant |> 
  filter(year == year2) |> 
  summarise(n = sum(n, na.rm = T)) |> 
  as.integer()

nyeardif <- ifelse(n_year1 < n_year2, "less than", "more than")
nyeardif <- ifelse(n_year1 == n_year2, "the same as", nyeardif)

In 2023, the EU produced 2090 legal acts which is less than the 2127 acts in 2022. Overall, the EU’s legislative output is currently considerably lower compared to, in particular, the 1980s and 1990s.

2 Proportion of act types

So we see there is significant over-time variation in the use of the different types of legal acts, but some types of acts, notably regulations, have been historically significantly more common than other types of acts. We can visualize the evolution of proportions directly.

2.1 Plot

2.2 Code

# create df
proptime_df <- expand.grid(year = min(n_relevant$year):max(n_relevant$year),
            type = unique(n_relevant$type)) |> 
  left_join(n_relevant) |> 
  mutate(n = ifelse(is.na(n), 0L, n)) |> 
  group_by(year) |> 
  mutate(total_y = sum(n),
         proportion = n/total_y) |> 
  ungroup()

# viz
proptime_df |> 
  ggplot(aes(x = year, y = proportion, fill = type, color = type)) +
  geom_area(show.legend = TRUE) +
  geom_vline(xintercept = c(1960,1980,2000,2020), lty = 2, color = "grey70") +
  geom_hline(yintercept = 0.50, color = "grey50", lty = 3) +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white", color = "grey88"),
        panel.grid = element_blank(),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        plot.subtitle = element_text(face = "italic"),
        strip.text = element_text(hjust = 0, face = "bold")) +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(expand = c(0,0)) +
  labs(x = NULL,
       y = NULL,
       color = NULL,
       fill = NULL,
       title = "Relative prevalence of legal acts",
       subtitle = "Proportion of each type of legal act in a given year")

2.3 Dataframe

We can see that although regulations used to dominate the EU’s legislative output, decisions – which are typically associated with various administrative actions – are nowadays almost equally prevalent.

The rise in the number of recommendations can be mostly attributed to the strengthening of the EU’s role in the surveillance and coordination of the Member States’ economic policies.

3 Year-on-year comparison

The daily streaming of data can be relied on to make completely up-to-date comparisons of EU legislative activity. For example, we can look at the extent to which the monthly adoption rate of legal acts this year differs from last year.

3.1 Plot

3.2 Code

# monthly output last two years
last_two_years <- relevant_acts |> 
  mutate(year = as.integer(str_sub(date, 1,4))) |> 
  filter(year %in% c(max(relevant_acts$year), max(relevant_acts$year)-1)) |> 
  count(month_year, type)

last_two_years_full <- expand.grid(month_year = unique(last_two_years$month_year),
                                   type = unique(last_two_years$type)) |> 
  left_join(last_two_years) |> 
  mutate(n = ifelse(is.na(n), 0L, n))

# year on year change
last_two_yoy_diff <- last_two_years_full |> 
  arrange(month_year) |> 
  mutate(month = str_sub(month_year, 6,7)) |> 
  group_by(month, type) |> 
  summarise(yoy_diff = diff(n)) |> 
  ungroup()

# this month for text
this_month_yoy <- last_two_yoy_diff |> 
  arrange(desc(month)) |> 
  slice(1:4) |> 
  summarise(sum = sum(yoy_diff)) |> 
  as.integer()

this_month_yoy_dir <- ifelse(this_month_yoy < 0, "fewer than", "more than")
#this_month_yoy_dir <- ifelse(this_month_yoy == 0, "the same as", this_month_yoy)

this_month_yoy_abs <- abs(this_month_yoy)

# viz
last_two_yoy_diff |> 
  ggplot(aes(x = month, y = yoy_diff, fill = type, color = type)) +
  geom_col(show.legend = FALSE) +
  geom_hline(yintercept = 0, color = "grey83", lty = 1) +
  facet_wrap(~type, scales = "free_y") +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white", color = "grey88"),
        panel.grid = element_blank(),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        plot.subtitle = element_text(face = "italic"),
        strip.text = element_text(hjust = 0, face = "bold")) +
  labs(x = "Month", y = "Year-on-year difference",
       title = "Monthly year-on-year comparison",
       subtitle = "Difference in the number of acts adopted this year compared to last year")

3.3 Dataframe

So far this month, the EU has adopted 2 legal acts fewer than the same month last year.

4 Acts in force

Naturally, not all legal acts remain in force indefinitely, which means that many of the 202384 acts ever adopted by the EU are mere relics of the past.

According to Eur-Lex, the EU has 41367 legal acts in force at the moment, though a closer examination shows that many of those acts are practically obsolete, even if technically still valid. For example, act 31952S0004 is considered in force, but its sole purpose was to set the period for the collection of coal and steel levies in 1953.

Despite this data caveat, it is still interesting to find out how long ago the acts that are currently applicable have entered into force.

4.1 Plot

4.2 Code

# viz
in_force |> 
  filter(force == "true") |> 
  mutate(days_in_force = as.integer(as.Date(Sys.Date()) - as.Date(date_force)),
         type = str_sub(celex, 6,6)) |> 
  filter(type %in% c("R","L","H","D")) |> 
  mutate(type = case_when(type == "D" ~ "Decision",
                          type == "H" ~ "Recommendation",
                          type == "L" ~ "Directive",
                          type == "R" ~ "Regulation",
                          T ~ NA_character_)) |> 
  mutate(months_in_force = days_in_force/12) |> 
  ggplot(aes(x = days_in_force, fill = type, color = type)) +
  geom_histogram(bins = 200, show.legend = FALSE) +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white", color = "grey88"),
        panel.grid = element_line(color = "grey95"),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        plot.subtitle = element_text(face = "italic"),
        strip.text = element_text(hjust = 0, face = "bold")) +
  facet_wrap(~type, scales = "free") +
  labs(x = NULL,
       y = NULL,
       color = NULL,
       fill = NULL,
       title = "How old are currently applicable legal acts?",
       subtitle = "Histogram of the number of days since currently applicable acts entered into force")

4.3 Dataframe

We see that the bulk of the currently legally applicable acts have been adopted relatively recently.

At the same time, there is seemingly meaningful variation among the different types of acts. Directives suffer the least from the “recency bias”, while the time horizons of regulations and decisions are considerably shorter.

5 Legislative efficiency

The image of the EU as an unwieldy bureaucracy with a complicated legislative system appears often in national media. Moreover, most theoretical literature on political systems would expect the number of veto players to affect legislative efficiency. On this basis, we formulate the simple hypothesis that

H1: The more EU Member States there are, the longer it takes to pass legislation.

Most people are therefore susceptible to assume that the EU needs a lot of time to adopt legal acts. Let’s use Eur-Lex data to find out just how long it normally takes to pass legislation.

5.1 Plot

5.2 Code

# calculate maximum length of adoption and discard negative
acts_days <- acts_proposals |> 
  filter(!is.na(celex),
         !is.na(date),
         !is.na(proposal)) |> 
  left_join(proposals, by = c("proposal"="celex")) |> 
  mutate(days = as.integer(as.Date(date) - as.Date(date_proposal))) |> 
  filter(days > -1) |> 
  group_by(celex) |> 
  filter(days == max(days)) |> 
  ungroup() |> 
  distinct(celex, .keep_all = TRUE) |> 
  arrange(date) |> 
  mutate(year_adopted = as.integer(str_sub(date, 1, 4)),
         type = str_sub(celex, 6, 6),
         type = case_when(type == "R" ~ "Regulation",
                          type == "L" ~ "Directive",
                          type == "D" ~ "Decision",
                          T ~ "Other"),
         five_year = cut_interval(year_adopted, length = 5)) |> 
  filter(year_adopted > 1985) # not many complete pairs before this

# identify outliers
outliers <- acts_days |> 
  group_by(type) |> 
  mutate(max = max(days),
         mean = mean(days),
         coef = days / mean) |> 
  filter(coef > 2.9) |> 
  ungroup()

# plot
acts_days |> 
  filter(!celex %in% outliers$celex) |> 
  group_by(type) |> 
  mutate(global_mean = mean(days)) |> 
  ggplot(aes(x = five_year, y = days, color = type, fill = type)) +
  geom_boxplot(alpha = 0.1, show.legend = FALSE) +
  geom_hline(aes(yintercept = global_mean), lty = 2, color = "grey70") +
  facet_wrap(~type, dir = "h", ncol = 1, scales = "free_y") +
  scale_fill_brewer(palette = "Spectral") +
  scale_color_brewer(palette = "Spectral") +
  theme_minimal() +
  theme(legend.position = "top",
        legend.justification = "left",
        legend.title = element_text(face = "italic"),
        plot.background = element_rect(fill = "white", color = "grey88"),
        panel.grid = element_blank(),
        axis.text = element_text(color = "grey10"),
        title = element_text(face = "bold"),
        plot.subtitle = element_text(face = "italic"),
        plot.caption = element_text(face = "italic", size = 8),
        strip.text = element_text(hjust = 0, face = "bold")) +
  labs(x = NULL,
       y = "Days to adoption",
       title = "Number of days between proposal and adoption",
       subtitle = "By five-year window in which the act was adopted*",
       caption = "* Outliers are pruned from the analysis, dashed line shows the global mean")

5.3 Dataframe

Directives take by far the longest to adopt; unlike decisions and regulations, they are rarely deployed to regulate trivial matters. Nonetheless, there are few easily discernible temporal patterns in the plot. Most of all, it is not obvious that legislative efficiency would decrease as the EU expanded its membership.

However, before moving onto further investigation of the efficiency hypothesis, we should pause and take a look at some of the discarded outliers. What legislation took the longest to adopt?

It turns out that many of the slowest legislative processes concerned – and this is particularly visible recently – the conclusion of international agreements. In these situations, the speed does not depend solely on the EU, as an agreement can only be formally concluded once the legal text is successfully negotiated with a third party.

Most acts on the list of slowest legislative files took well over ten years to complete.

5.4 Regression

Regression analysis unpacks the relationship between a response (dependent) variable and one or more explanatory variables. In our case, we are interested in the relationship (or absence thereof) between the number of days it takes the EU to turn a Commission proposal into law and the number of Member States.

We will assume the relationship between the variables is functionally linear. Canonically, the linear model can be written as:

\[ Y \sim \beta_0 + X\beta_1 + \epsilon \]

\[ \epsilon \sim N(0,\sigma^2) \]

where \(Y\) is the response variable, \(X\) the explanatory variable, \(\beta_0\) and \(\beta_1\) the unknown parameters of the model and \(\epsilon\) the error term which comes from a normal distribution with mean zero.

5.4.1 Table

Linear regression model of legislative efficiency (Y_i = days)
Baseline Controls  Zero Intercept
Number of Member States 5.313*** 6.400*** 6.400***
(0.516) (0.505) (0.505)
Constant 169.023***
(10.467)
Act type = Other 117.715*** 117.715***
(30.686) (30.686)
Act type = Regulation −25.982 91.732***
(28.940) (9.818)
Act type = Decision −34.229 83.485***
(28.972) (12.381)
Act type = Directive 473.438*** 591.152***
(30.082) (13.297)
Num.Obs. 14613 14613 14613
R2 0.007 0.149 0.390
R2 Adj. 0.007 0.149 0.390
AIC 218616.5 216367.9 216367.9
BIC 218639.3 216413.5 216413.5
Log.Lik. −109305.248 −108177.968 −108177.968
RMSE 428.83 396.99 396.99
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
In the ‘Controls’ specification, ‘Other’ is the reference category.

5.4.2 Equation

Plugging in the variables and estimated coefficients, the following regression equation predicts the number of days an act would take to pass given some combination of input values:

\[ \operatorname{\widehat{days}} = 117.71() + 6.4(\operatorname{n\_ms}) - 34.23(\operatorname{type}_{\operatorname{Decision}}) + 473.44(\operatorname{type}_{\operatorname{Directive}}) - 25.98(\operatorname{type}_{\operatorname{Regulation}}) \]

5.4.3 Plot

5.4.4 Code

# number of member states over time
ms_years <- bind_rows(
  data.frame(
    year = 1952:1972,
    n_ms = 6
  ),
  data.frame(
    year = 1973:1980,
    n_ms = 9
  ),
  data.frame(
    year = 1981:1985,
    n_ms = 10
  ),
  data.frame(
    year = 1986:1994,
    n_ms = 12
  ),
  data.frame(
    year = 1995:2004,
    n_ms = 15
  ),
  data.frame(
    year = 2005:2006,
    n_ms = 25
  ),
  data.frame(
    year = 2007:2013,
    n_ms = 27
  ),
  data.frame(
    year = 2014:2018,
    n_ms = 28
  ),
  data.frame(
    year = 2019:2025,
    n_ms = 27
  )
)

# append N member states
days_ms <- acts_days |> 
  left_join(ms_years, by = c("year_adopted"="year")) |> 
  mutate(type = relevel(as.factor(type), ref = "Other"))

# regress
models <- list()

## m0
models[['Baseline']] <- lm(
  data = days_ms,
  formula = days ~ n_ms
)
names(models[['Baseline']]$coefficients)[1] <- "Constant"

## m1
models[['Controls']] <- lm(
  data = days_ms,
  formula = days ~ n_ms + type
)
names(models[['Controls']]$coefficients)[1] <- "Act type = Other"

## m2
models[['Zero Intercept']] <- lm(
  data = days_ms,
  formula = days ~ n_ms + type + 0
)

# effect size
effect <- as.numeric(round(models[['Controls']]$coefficients[which(names(models[['Controls']]$coefficients) == "n_ms")],2))

# summary for p values
mod_sum <- summary(models[['Controls']])

# p value
p_value <- mod_sum$coefficient[names(mod_sum$coefficient[,1]) == "n_ms", 4]

# p threshold
p_thresh <- 0.05

# can we reject the null
reject_null <- ifelse(effect > 0 & p_value < p_thresh, "can reject", "cannot reject")

# show table
modelsummary(models, 
             stars = TRUE,
             coef_map = c("n_ms" = "Number of Member States",
                          "Constant" = "Constant",
                          "typeOther" = "Act type = Other",
                          "Act type = Other" = "Act type = Other",
                          "typeRegulation" = "Act type = Regulation",
                          "typeDecision" = "Act type = Decision",
                          "typeDirective" = "Act type = Directive"),
             title = "Linear regression model of legislative efficiency (Y_i = days)",
             notes = list("In the 'Controls' specification, 'Other' is the reference category."))

5.4.5 Dataframe

Controlling for the type of act, the linear model predicts that for every additional Member State, the EU takes on average 6.4 days longer to adopt the legal act. This effect is statistically significant at 0.05 alpha.

This model is far too simple to establish the existence of a causal relationship between the number of Member States and legislative efficiency. Indeed, some scholars would argue that no amount of conditioning on observables is sufficient to establish causality. Moreover, Poisson regression would be more appropriate here as our dependent variable is discrete and bounded at zero.

Nonetheless, our simple analysis suggests that we can reject the null hypothesis that more Member States in the EU system are not associated with slower law-making.

It turns out that most legal acts adopted by the EU have been delegated (and implementing) acts, that is acts based on another piece of legislation rather than one of the EU Treaties (primary acts). Last year, such delegated acts represented 68 per cent of the EU’s yearly legislative output.

7 Council votes

Few would disagree that the Council of the 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, there are enough voting records to give us an insight into Member State governments’ preferences. We access these through a dedicated API maintained by the Council, which is also wrapped in the eurlex package.

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.

7.1 Plot

7.2 Dataframe

7.3 Code: Data

# pull Council voting data
cns_votes_raw <- elx_council_votes()

# vote level, 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()

7.4 Code: Plot

# viz Council votes
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")

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, has been 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.4 In our example, we assume that all votes with a disagreement are equally informative and important; ideally, we would want to relax this assumption.

8 Cite

Cite this document as Michal Ovádek, ‘Legislative Output of the European Union’, available at https://michalovadek.github.io/eulaw/, accessed on 26 April 2024.


  1. See https://github.com/michalovadek/eurlex for more details.↩︎

  2. This also means that any omissions and mistakes present in Eur-Lex are carried through to the output shown here.↩︎

  3. We should also take into account the regulatory breadth and depth of each legal act. A decision increasing the tariff on steel by 5 per cent is a very different act from, say, the General Data Protection Regulation. ↩︎

  4. An example application of an ideal point IRT model can be found in this paper.↩︎