From 3145c35ff29b3352860e78ea739836b3fcff96b9 Mon Sep 17 00:00:00 2001 From: michalovadek Date: Wed, 3 Jul 2024 01:26:00 +0100 Subject: [PATCH] end of council_votes() --- DESCRIPTION | 3 +- NAMESPACE | 1 - NEWS.md | 7 +- R/elx_council_votes.R | 12 +- R/elx_download_xml.R | 1 + README.md | 15 +- docs/404.html | 11 +- docs/articles/council.html | 164 ++------------------- docs/articles/eurlexpkg.html | 201 +++----------------------- docs/articles/index.html | 10 +- docs/articles/sparql-queries.html | 20 ++- docs/authors.html | 12 +- docs/index.html | 27 +++- docs/news/index.html | 29 +++- docs/pkgdown.yml | 4 +- docs/reference/elx_curia_list.html | 27 +++- docs/reference/elx_download_xml.html | 15 +- docs/reference/elx_fetch_data.html | 12 +- docs/reference/elx_label_eurovoc.html | 12 +- docs/reference/elx_make_query.html | 12 +- docs/reference/elx_run_query.html | 12 +- docs/reference/index.html | 14 +- man/elx_council_votes.Rd | 19 --- man/elx_download_xml.Rd | 1 + vignettes/articles/council.Rmd | 105 +------------- vignettes/articles/council.txt | 104 +++++++++++++ vignettes/articles/eurlexpkg.Rmd | 2 +- vignettes/sparql-queries.Rmd | 2 +- 28 files changed, 338 insertions(+), 516 deletions(-) delete mode 100644 man/elx_council_votes.Rd create mode 100644 vignettes/articles/council.txt diff --git a/DESCRIPTION b/DESCRIPTION index 81df081..b93efd7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: eurlex Type: Package Title: Retrieve Data on European Union Law -Version: 0.4.7 +Version: 0.4.8 Authors@R: c(person(given = "Michal", family = "Ovadek", role = c("aut", "cre", "cph"), @@ -23,7 +23,6 @@ Imports: rvest, rlang, stringr, - readr, pdftools, antiword Suggests: diff --git a/NAMESPACE b/NAMESPACE index 558969d..0d9e266 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,5 @@ # Generated by roxygen2: do not edit by hand -export(elx_council_votes) export(elx_curia_list) export(elx_download_xml) export(elx_fetch_data) diff --git a/NEWS.md b/NEWS.md index 4b2069a..7dcd411 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,10 @@ -# eurlex 0.5.0 +# eurlex 0.4.8 ## Major changes -- the Council votes API was discontinued in May 2024. Hopefully there is a new one to replace it soon +- the Council votes API was discontinued in May 2024. The function `elx_council_votes()` is no longer exported + +## Minor changes +- tempfile created for XML download now gets deleted # eurlex 0.4.7 diff --git a/R/elx_council_votes.R b/R/elx_council_votes.R index b4f87ad..6466513 100644 --- a/R/elx_council_votes.R +++ b/R/elx_council_votes.R @@ -1,15 +1,11 @@ #' Retrieve Council votes on EU acts +#' NOTE: The Council votes API was discontinued in May 2024. #' #' Executes a SPARQL query to the Council's endpoint. -#' #' @importFrom rlang .data -#' @return -#' A data frame with Council votes on EU acts. -#' @export -#' @examples -#' \donttest{ -#' votes <- elx_council_votes() -#' } +#' +#' @noRd +#' elx_council_votes <- function(){ diff --git a/R/elx_download_xml.R b/R/elx_download_xml.R index 16ead59..20acd4a 100644 --- a/R/elx_download_xml.R +++ b/R/elx_download_xml.R @@ -19,6 +19,7 @@ #' temploc <- paste(tempdir(), "elxnotice.xml", sep = "\\") #' elx_download_xml(url = "http://publications.europa.eu/resource/celex/32022D0154", #' file = temploc, notice = "object") +#' unlink(temploc) #' } elx_download_xml <- function(url, file = paste(basename(url), ".xml", sep = ""), diff --git a/README.md b/README.md index 568864b..b21d69b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,20 @@ Install from CRAN via `install.packages("eurlex")`. The development version is available via `remotes::install_github("michalovadek/eurlex")`. ## Cite -Michal Ovádek (2021) **Facilitating access to data on European Union laws**, *Political Research Exchange*, 3:1, DOI: [10.1080/2474736X.2020.1870150](https://www.tandfonline.com/doi/full/10.1080/2474736X.2020.1870150) +Michal Ovádek (2021) **Facilitating access to data on European Union laws**, *Political Research Exchange*, 3:1, DOI: [10.1080/2474736X.2020.1870150](https://doi.org/10.1080/2474736X.2020.1870150) + +``` r +@article{ovadek2021facilitating, + author = {Ovádek, Michal}, + title = {Facilitating access to data on European Union laws}, + year = {2021}, + journal = {Political Research Exchange}, + volume = {3}, + number = {1}, + pages = {Article No. 1870150}, + url = {https://doi.org/10.1080/2474736X.2020.1870150} +} +``` ## Basic usage The `eurlex` package currently envisions the typical use-case to consist of getting bulk information about EU legislation into R as fast as possible. The package contains three core functions to achieve that objective: `elx_make_query()` to create pre-defined or customized SPARQL queries; `elx_run_query()` to execute the pre-made or any other manually input query; and `elx_fetch_data()` to fire GET requests for certain metadata to the REST API. diff --git a/docs/404.html b/docs/404.html index 4cc0c41..39a0075 100644 --- a/docs/404.html +++ b/docs/404.html @@ -39,7 +39,7 @@ eurlex - 0.4.7 + 0.4.8 @@ -70,7 +70,14 @@ Changelog - + diff --git a/docs/articles/council.html b/docs/articles/council.html index 3f8bc59..d0dd812 100644 --- a/docs/articles/council.html +++ b/docs/articles/council.html @@ -40,7 +40,7 @@ eurlex - 0.4.7 + 0.4.8 @@ -71,7 +71,14 @@ Changelog - + @@ -81,14 +88,13 @@ - -
+
@@ -106,155 +112,13 @@

Voting in the Council of the EU

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.

-
-

Data on Council votes -

-

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}\)

-
-
-

Interactive plot -

-

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.

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

  2. -
-
+could access these through a dedicated API maintained by the Council +until May 2024.

+
diff --git a/docs/articles/eurlexpkg.html b/docs/articles/eurlexpkg.html index 1c3d454..721b9a3 100644 --- a/docs/articles/eurlexpkg.html +++ b/docs/articles/eurlexpkg.html @@ -41,7 +41,7 @@ eurlex - 0.4.7 + 0.4.8 @@ -72,7 +72,14 @@ Changelog - + @@ -89,7 +96,7 @@

eurlex: Retrieve data on European Union law in R

- + Source: vignettes/articles/eurlexpkg.Rmd @@ -139,9 +146,10 @@

The eurlex package

The package also contains largely self-explanatory functions for retrieving data on EU court cases (elx_curia_list()) and -Council votes (elx_council_votes()) from outside Eur-Lex. -More advanced users might be interested in downloading and -custom-parsing XML notices with elx_download_xml().

+Council votes (elx_council_votes(), currently +dysfunctional) from outside Eur-Lex. More advanced users might be +interested in downloading and custom-parsing XML notices with +elx_download_xml().

elx_make_query(): Generate SPARQL queries @@ -164,50 +172,12 @@

 query_dir %>% 
   cat()
-#> PREFIX cdm: <http://publications.europa.eu/ontology/cdm#>
-#>   PREFIX annot: <http://publications.europa.eu/ontology/annotation#>
-#>   PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
-#>   PREFIX dc:<http://purl.org/dc/elements/1.1/>
-#>   PREFIX xsd:<http://www.w3.org/2001/XMLSchema#>
-#>   PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-#>   PREFIX owl:<http://www.w3.org/2002/07/owl#>
-#>   select distinct ?work ?type ?celex where{ ?work cdm:work_has_resource-type ?type. FILTER(?type=<http://publications.europa.eu/resource/authority/resource-type/DIR>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/DIR_IMPL>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/DIR_DEL>) 
-#>  FILTER not exists{?work cdm:work_has_resource-type <http://publications.europa.eu/resource/authority/resource-type/CORRIGENDUM>} OPTIONAL{?work cdm:resource_legal_id_celex ?celex.} FILTER not exists{?work cdm:do_not_index "true"^^<http://www.w3.org/2001/XMLSchema#boolean>}. }
 
 elx_make_query(resource_type = "caselaw") %>% 
   cat()
-#> PREFIX cdm: <http://publications.europa.eu/ontology/cdm#>
-#>   PREFIX annot: <http://publications.europa.eu/ontology/annotation#>
-#>   PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
-#>   PREFIX dc:<http://purl.org/dc/elements/1.1/>
-#>   PREFIX xsd:<http://www.w3.org/2001/XMLSchema#>
-#>   PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-#>   PREFIX owl:<http://www.w3.org/2002/07/owl#>
-#>   select distinct ?work ?type ?celex where{ ?work cdm:work_has_resource-type ?type. FILTER(?type=<http://publications.europa.eu/resource/authority/resource-type/JUDG>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/ORDER>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/OPIN_JUR>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/THIRDPARTY_PROCEED>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/GARNISHEE_ORDER>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/RULING>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/JUDG_EXTRACT>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/INFO_JUDICIAL>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/VIEW_AG>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/OPIN_AG>) 
-#>  FILTER not exists{?work cdm:work_has_resource-type <http://publications.europa.eu/resource/authority/resource-type/CORRIGENDUM>} OPTIONAL{?work cdm:resource_legal_id_celex ?celex.} FILTER not exists{?work cdm:do_not_index "true"^^<http://www.w3.org/2001/XMLSchema#boolean>}. }
 
 elx_make_query(resource_type = "manual", manual_type = "SWD") %>% 
-  cat()
-#> PREFIX cdm: <http://publications.europa.eu/ontology/cdm#>
-#>   PREFIX annot: <http://publications.europa.eu/ontology/annotation#>
-#>   PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
-#>   PREFIX dc:<http://purl.org/dc/elements/1.1/>
-#>   PREFIX xsd:<http://www.w3.org/2001/XMLSchema#>
-#>   PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-#>   PREFIX owl:<http://www.w3.org/2002/07/owl#>
-#>   select distinct ?work ?type ?celex where{ ?work cdm:work_has_resource-type ?type.FILTER(?type=<http://publications.europa.eu/resource/authority/resource-type/SWD>) 
-#>  FILTER not exists{?work cdm:work_has_resource-type <http://publications.europa.eu/resource/authority/resource-type/CORRIGENDUM>} OPTIONAL{?work cdm:resource_legal_id_celex ?celex.} FILTER not exists{?work cdm:do_not_index "true"^^<http://www.w3.org/2001/XMLSchema#boolean>}. }
+ cat()

There are various ways of querying the same information in the Cellar database due to the existence of several overlapping classes and identifiers describing the same resources. The queries generated by the @@ -231,43 +201,11 @@

 elx_make_query(resource_type = "directive", include_date = TRUE, include_force = TRUE) %>% 
   cat()
-#> PREFIX cdm: <http://publications.europa.eu/ontology/cdm#>
-#>   PREFIX annot: <http://publications.europa.eu/ontology/annotation#>
-#>   PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
-#>   PREFIX dc:<http://purl.org/dc/elements/1.1/>
-#>   PREFIX xsd:<http://www.w3.org/2001/XMLSchema#>
-#>   PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-#>   PREFIX owl:<http://www.w3.org/2002/07/owl#>
-#>   select distinct ?work ?type ?celex ?date ?force where{ ?work cdm:work_has_resource-type ?type. FILTER(?type=<http://publications.europa.eu/resource/authority/resource-type/DIR>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/DIR_IMPL>||
-#>   ?type=<http://publications.europa.eu/resource/authority/resource-type/DIR_DEL>) 
-#>  FILTER not exists{?work cdm:work_has_resource-type <http://publications.europa.eu/resource/authority/resource-type/CORRIGENDUM>} OPTIONAL{?work cdm:resource_legal_id_celex ?celex.} OPTIONAL{?work cdm:work_date_document ?date.} OPTIONAL{?work cdm:resource_legal_in-force ?force.} FILTER not exists{?work cdm:do_not_index "true"^^<http://www.w3.org/2001/XMLSchema#boolean>}. }
 
 # minimal query: elx_make_query(resource_type = "directive")
 
 elx_make_query(resource_type = "recommendation", include_date = TRUE, include_lbs = TRUE) %>% 
   cat()
-#> PREFIX cdm: <http://publications.europa.eu/ontology/cdm#>
-#>   PREFIX annot: <http://publications.europa.eu/ontology/annotation#>
-#>   PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
-#>   PREFIX dc:<http://purl.org/dc/elements/1.1/>
-#>   PREFIX xsd:<http://www.w3.org/2001/XMLSchema#>
-#>   PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-#>   PREFIX owl:<http://www.w3.org/2002/07/owl#>
-#>   select distinct ?work ?type ?celex ?date ?lbs ?lbcelex ?lbsuffix where{ ?work cdm:work_has_resource-type ?type. FILTER(?type=<http://publications.europa.eu/resource/authority/resource-type/RECO>||
-#>                    ?type=<http://publications.europa.eu/resource/authority/resource-type/RECO_DEC>||
-#>                    ?type=<http://publications.europa.eu/resource/authority/resource-type/RECO_DIR>||
-#>                    ?type=<http://publications.europa.eu/resource/authority/resource-type/RECO_OPIN>||
-#>                    ?type=<http://publications.europa.eu/resource/authority/resource-type/RECO_RES>||
-#>                    ?type=<http://publications.europa.eu/resource/authority/resource-type/RECO_REG>||
-#>                    ?type=<http://publications.europa.eu/resource/authority/resource-type/RECO_RECO>||
-#>                    ?type=<http://publications.europa.eu/resource/authority/resource-type/RECO_DRAFT>) 
-#>  FILTER not exists{?work cdm:work_has_resource-type <http://publications.europa.eu/resource/authority/resource-type/CORRIGENDUM>} OPTIONAL{?work cdm:resource_legal_id_celex ?celex.} OPTIONAL{?work cdm:work_date_document ?date.} OPTIONAL{?work cdm:resource_legal_based_on_resource_legal ?lbs.
-#>     ?lbs cdm:resource_legal_id_celex ?lbcelex.
-#>     OPTIONAL{?bn owl:annotatedSource ?work.
-#>     ?bn owl:annotatedProperty <http://publications.europa.eu/ontology/cdm#resource_legal_based_on_resource_legal>.
-#>     ?bn owl:annotatedTarget ?lbs.
-#>     ?bn annot:comment_on_legal_basis ?lbsuffix}} FILTER not exists{?work cdm:do_not_index "true"^^<http://www.w3.org/2001/XMLSchema#boolean>}. }
 
 # minimal query: elx_make_query(resource_type = "recommendation")

You can also decide to not specify any resource types, in which case @@ -284,30 +222,7 @@

elx_make_query(resource_type = "any", directory = "18", sector = 3) %>% - cat() -#> PREFIX cdm: <http://publications.europa.eu/ontology/cdm#> -#> PREFIX annot: <http://publications.europa.eu/ontology/annotation#> -#> PREFIX skos:<http://www.w3.org/2004/02/skos/core#> -#> PREFIX dc:<http://purl.org/dc/elements/1.1/> -#> PREFIX xsd:<http://www.w3.org/2001/XMLSchema#> -#> PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#> -#> PREFIX owl:<http://www.w3.org/2002/07/owl#> -#> select distinct ?work ?type ?celex where{ -#> VALUES (?value) -#> { (<http://publications.europa.eu/resource/authority/fd_555/18>) -#> (<http://publications.europa.eu/resource/authority/dir-eu-legal-act/18>) -#> } -#> {?work cdm:resource_legal_is_about_concept_directory-code ?value. -#> } -#> UNION -#> {?work cdm:resource_legal_is_about_concept_directory-code ?directory. -#> ?value skos:narrower+ ?directory. -#> } -#> -#> ?work cdm:resource_legal_id_sector ?sector. -#> FILTER(str(?sector)='3') -#> -#> FILTER not exists{?work cdm:work_has_resource-type <http://publications.europa.eu/resource/authority/resource-type/CORRIGENDUM>} OPTIONAL{?work cdm:resource_legal_id_celex ?celex.} FILTER not exists{?work cdm:do_not_index "true"^^<http://www.w3.org/2001/XMLSchema#boolean>}. } + cat()

Now that we have a query, we are ready to run it.

@@ -326,15 +241,7 @@

# elx_make_query("directive") %>% # elx_run_query()

-as_tibble(results)
-#> # A tibble: 4,449 × 3
-#>   work                                 type  celex     
-#>   <chr>                                <chr> <chr>     
-#> 1 469391ea-6c79-4680-84aa-c33db274e271 DIR   31979L0173
-#> 2 e8fcaf0d-443a-40ec-b778-34b7d895d334 DIR   31989L0194
-#> 3 52639f5f-ecaf-4f99-b633-e954cea5c8f3 DIR   31984L0378
-#> 4 c7560407-689b-4752-9fb0-d0624ed83a19 DIR   31966L0683
-#> # ℹ 4,445 more rows
+as_tibble(results)

The function outputs a data.frame where each column corresponds to one of the requested variables, while the rows accumulate observations of the resource type satisfying the query criteria. @@ -356,16 +263,9 @@

erring on the side of over-inclusiveness rather than vice versa.

 head(results$type,5)
-#> [1] "DIR" "DIR" "DIR" "DIR" "DIR"
 
 results %>% 
-  distinct(type)
-#> # A tibble: 3 × 1
-#>   type    
-#>   <chr>   
-#> 1 DIR     
-#> 2 DIR_IMPL
-#> 3 DIR_DEL
+ distinct(type)

The data is returned in the long format, which means that rows are recycled up to the length of the variable with the most data points. For example, if 20 directives are returned, each with two legal bases, the @@ -387,15 +287,7 @@

EuroVoc descriptorselx_run_query() # truncated results for sake of the example rec_eurovoc %>% - select(celex, eurovoc) -#> # A tibble: 10 × 2 -#> celex eurovoc -#> <chr> <chr> -#> 1 32012H0090 http://eurovoc.europa.eu/1425 -#> 2 31962H0816 http://eurovoc.europa.eu/1004 -#> 3 31974H0435 http://eurovoc.europa.eu/1085 -#> 4 31996H0592 http://eurovoc.europa.eu/1076 -#> # ℹ 6 more rows + select(celex, eurovoc)

By default, the endpoint returns the EuroVoc concept codes rather than the labels (keywords). The function elx_label_eurovoc() needs to be called to obtain a look-up @@ -403,30 +295,13 @@

EuroVoc descriptors
 eurovoc_lookup <- elx_label_eurovoc(uri_eurovoc = rec_eurovoc$eurovoc)
 
-print(eurovoc_lookup)
-#> # A tibble: 9 × 2
-#>   eurovoc                       labels         
-#>   <chr>                         <chr>          
-#> 1 http://eurovoc.europa.eu/1085 France         
-#> 2 http://eurovoc.europa.eu/1442 food inspection
-#> 3 http://eurovoc.europa.eu/1076 form           
-#> 4 http://eurovoc.europa.eu/1318 Germany        
-#> # ℹ 5 more rows
+print(eurovoc_lookup)

The results include labels only for unique identifiers, but with dplyr::left_join() it is straightforward to append the labels to the entire dataset.

 rec_eurovoc %>% 
-  left_join(eurovoc_lookup)
-#> Joining with `by = join_by(eurovoc)`
-#> # A tibble: 10 × 5
-#>   work                                 type  celex      eurovoc           labels
-#>   <chr>                                <chr> <chr>      <chr>             <chr> 
-#> 1 e46f89c5-2db2-4157-9f24-644b32b64070 RECO  32012H0090 http://eurovoc.e… consu…
-#> 2 120a2b97-ef75-494e-ad48-1fa46932e26a RECO  31962H0816 http://eurovoc.e… welfa…
-#> 3 123da027-350c-4c61-bc40-8e46869b89cb RECO  31974H0435 http://eurovoc.e… France
-#> 4 8a363aee-7d70-4d41-b8df-3bf487320572 RECO  31996H0592 http://eurovoc.e… form  
-#> # ℹ 6 more rows
+ left_join(eurovoc_lookup)

As elsewhere in the API, we can tap into the multilingual nature of EU documents also when it comes to the EuroVoc keywords. Moreover, most concepts in the thesaurus are associated with alternative labels; these @@ -438,16 +313,7 @@

EuroVoc descriptorsrec_eurovoc %>% left_join(eurovoc_lookup) %>% - select(celex, eurovoc, labels) -#> Joining with `by = join_by(eurovoc)` -#> # A tibble: 10 × 3 -#> celex eurovoc labels -#> <chr> <chr> <chr> -#> 1 32012H0090 http://eurovoc.europa.eu/1425 informácie pre spotrebiteľa,vzdeláva… -#> 2 31962H0816 http://eurovoc.europa.eu/1004 blahobyt -#> 3 31974H0435 http://eurovoc.europa.eu/1085 Francúzska republika,Francúzsko -#> 4 31996H0592 http://eurovoc.europa.eu/1076 formulár -#> # ℹ 6 more rows + select(celex, eurovoc, labels)
@@ -481,15 +347,7 @@

as_tibble() %>% select(celex, title) -print(dir_titles) -#> # A tibble: 5 × 2 -#> celex title -#> <chr> <chr> -#> 1 31979L0173 Council Directive 79/173/EEC of 6 February 1979 on the programme f… -#> 2 31989L0194 Council Directive 89/194/EEC of 13 March 1989 amending Directive 6… -#> 3 31984L0378 Council Directive 84/378/EEC of 28 June 1984 amending the Annexes … -#> 4 31966L0683 Commission Directive 66/683/EEC of 7 November 1966 eliminating all… -#> # ℹ 1 more row

+print(dir_titles)

Note that text requests are by far the most time-intensive; requesting the full text for thousands of documents is liable to extend the run-time into hours. Texts are retrieved from html by priority, but @@ -519,7 +377,6 @@

Applicationcount(force) %>% ggplot(aes(x = force, y = n)) + geom_col() -

Directives become naturally outdated with time. It might be all the more interesting to see which older acts are thus still surviving.

@@ -531,7 +388,6 @@ 

Applicationtheme(axis.text.y = element_blank(), axis.line.y = element_blank(), axis.ticks.y = element_blank())

-

We want to know a bit more about some directives from the early 1970s that are still in force today. Their titles could give us a clue.

@@ -543,15 +399,7 @@ 

Application"title")) %>% as_tibble() -print(dirs_1970_title) -#> # A tibble: 10 × 6 -#> work type celex date force title -#> <chr> <chr> <chr> <chr> <chr> <chr> -#> 1 http://publications.europa.eu/resource/cellar/c… DIR 3197… 1972… true Coun… -#> 2 http://publications.europa.eu/resource/cellar/f… DIR 3197… 1972… true Coun… -#> 3 http://publications.europa.eu/resource/cellar/a… DIR 3197… 1971… true Coun… -#> 4 http://publications.europa.eu/resource/cellar/c… DIR 3197… 1970… true Coun… -#> # ℹ 6 more rows

+print(dirs_1970_title)

I will use the tidytext package to get a quick idea of what the legislation is about.

@@ -566,7 +414,6 @@ 

Applicationfilter(!grepl("\\d", word)) %>% bind_tf_idf(word, celex, n) %>% with(wordcloud(word, tf_idf, max.words = 40))

-

I use term-frequency inverse-document frequency (tf-idf) to weight the importance of the words in the wordcloud. If we used pure frequencies, the wordcloud would largely consist of words conveying diff --git a/docs/articles/index.html b/docs/articles/index.html index 057556c..bfefc63 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -17,7 +17,7 @@ eurlex - 0.4.7 + 0.4.8 @@ -44,7 +44,13 @@

  • Changelog
  • - + diff --git a/docs/articles/sparql-queries.html b/docs/articles/sparql-queries.html index 4ccaece..913ddb4 100644 --- a/docs/articles/sparql-queries.html +++ b/docs/articles/sparql-queries.html @@ -40,7 +40,7 @@ eurlex - 0.4.7 + 0.4.8 @@ -71,7 +71,14 @@ Changelog - + @@ -87,7 +94,7 @@

    Make SPARQL queries with eurlex

    - + Source: vignettes/sparql-queries.Rmd @@ -139,9 +146,10 @@

    The eurlex package

    The package also contains largely self-explanatory functions for retrieving data on EU court cases (elx_curia_list()) and -Council votes (elx_council_votes()) from outside Eur-Lex. -More advanced users might be interested in downloading and -custom-parsing XML notices with elx_download_xml().

    +Council votes (elx_council_votes(), currently +dysfunctional) from outside Eur-Lex. More advanced users might be +interested in downloading and custom-parsing XML notices with +elx_download_xml().

    elx_make_query(): Generate SPARQL queries diff --git a/docs/authors.html b/docs/authors.html index 955cc61..ddb334f 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -17,7 +17,7 @@ eurlex - 0.4.7 + 0.4.8

    @@ -44,7 +44,13 @@
  • Changelog
  • - + @@ -66,7 +72,7 @@

    Authors

    Citation

    - + Source: inst/CITATION
    diff --git a/docs/index.html b/docs/index.html index 25d65c3..9eadf26 100644 --- a/docs/index.html +++ b/docs/index.html @@ -40,7 +40,7 @@ eurlex - 0.4.7 + 0.4.8 @@ -71,7 +71,14 @@ Changelog - + @@ -100,7 +107,17 @@

    Installation

    Cite

    -

    Michal Ovádek (2021) Facilitating access to data on European Union laws, Political Research Exchange, 3:1, DOI: 10.1080/2474736X.2020.1870150

    +

    Michal Ovádek (2021) Facilitating access to data on European Union laws, Political Research Exchange, 3:1, DOI: 10.1080/2474736X.2020.1870150

    +
    @article{ovadek2021facilitating,
    +  author       = {Ovádek, Michal},
    +  title        = {Facilitating access to data on European Union laws},
    +  year         = {2021},
    +  journal      = {Political Research Exchange},
    +  volume       = {3},
    +  number       = {1},
    +  pages        = {Article No. 1870150},
    +  url          = {https://doi.org/10.1080/2474736X.2020.1870150}
    +}

    Basic usage @@ -108,7 +125,7 @@

    Basic usageelx_make_query() to create pre-defined or customized SPARQL queries; elx_run_query() to execute the pre-made or any other manually input query; and elx_fetch_data() to fire GET requests for certain metadata to the REST API.

    The function elx_make_query takes as its first argument the type of resource to be retrieved (such as “directive” or “any”) from the semantic database that powers Eur-Lex (and other publications) called Cellar. If you are familiar with SPARQL, you can always specify your own queries and execute them with elx_run_query().

    elx_run_query() executes SPARQL queries on a pre-specified endpoint of the EU Publication Office. It outputs a data.frame where each column corresponds to one of the requested variables, while the rows accumulate observations of the resource type satisfying the query criteria. Obviously, the more data is to be returned, the longer the execution time, varying from a few seconds to several hours, depending also on your connection. The first column always contains the unique URI of a “work” (usually legislative act or court judgment) which identifies each resource in Cellar. Several human-readable identifiers are normally associated with each “work” but the most useful one tends to be CELEX, retrieved by default.

    -
    +
     # load library
     library(eurlex)
     
    @@ -188,6 +205,8 @@ 

    Useful resourcesLinks

    diff --git a/docs/news/index.html b/docs/news/index.html index 2dfa544..b551cc9 100644 --- a/docs/news/index.html +++ b/docs/news/index.html @@ -17,7 +17,7 @@ eurlex - 0.4.7 + 0.4.8
    @@ -44,7 +44,13 @@
  • Changelog
  • -

    + @@ -54,11 +60,22 @@
    - + +
    +

    Major changes

    +
    • the Council votes API was discontinued in May 2024. The function elx_council_votes() is no longer exported
    • +
    +
    +

    Minor changes

    +
    • tempfile created for XML download now gets deleted
    • +
    +
    +
    +

    Minor changes

    • some http calls were still not failing gracefully
    • @@ -71,7 +88,7 @@
      • minor changes to documentation
      • cleaned up http calls code
      • -
      • calls to elx_council_votes() and elx_curia_list() now fail gracefully
      • +
      • calls to elx_council_votes() and elx_curia_list() now fail gracefully
      • .data replaced by quoted variables for tidyselect functions
      • Internet-using vignettes moved to site-only articles
    @@ -266,7 +283,7 @@
    diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index a9f75f5..222b00f 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -1,9 +1,9 @@ -pandoc: 3.1.1 +pandoc: 3.1.11 pkgdown: 2.0.7 pkgdown_sha: ~ articles: council: council.html eurlexpkg: eurlexpkg.html sparql-queries: sparql-queries.html -last_built: 2024-02-25T20:10Z +last_built: 2024-07-03T00:20Z diff --git a/docs/reference/elx_curia_list.html b/docs/reference/elx_curia_list.html index 6c73853..1322afb 100644 --- a/docs/reference/elx_curia_list.html +++ b/docs/reference/elx_curia_list.html @@ -18,7 +18,7 @@ eurlex - 0.4.7 + 0.4.8
    @@ -45,7 +45,13 @@
  • Changelog
  • - + @@ -55,7 +61,7 @@
    @@ -95,7 +101,20 @@

    Value

    Examples

    # \donttest{
     elx_curia_list(data = "cst_all", parse = FALSE)
    -#> Error in rvest::html_text(hrefs, "href"): `trim` must be `TRUE` or `FALSE`, not the string "href".
    +#> # A tibble: 1,760 × 3
    +#>    case_id    case_id_celex case_info                                           
    +#>    <chr>      <chr>         <chr>                                               
    +#>  1 F-1/05 *   NA            Judgment of 26 October 2006, Landgren / ETF (F-1/05…
    +#>  2 F-1/05     NA            Order of 22 May 2007, Landgren / ETF (F-1/05, ECR-S…
    +#>  3 F-1/05 INT NA            Order of 13 July 2007, Landgren / ETF (F-1/05 INT, …
    +#>  4 F-1/05     NA            Order of 9 November 2010, Landgren / ETF (F-1/05, u…
    +#>  5 F-2/05     NA            Removed from the register on 18 June 2008, Kröppeli…
    +#>  6 F-3/05     NA            Order of 15 May 2006, Schmit / Commission (F-3/05, …
    +#>  7 F-4/05     NA            Removed from the register on 18 June 2008, Huober /…
    +#>  8 F-5/05 *   NA            Judgment of 28 April 2009, Violetti and others / Co…
    +#>  9 F-6/05     NA            Removed from the register on 18 June 2008, Kröppeli…
    +#> 10 F-7/05     NA            Schmit / Commission (F-7/05) , see Case  F-5/05     
    +#> # ℹ 1,750 more rows
     # }
     
    diff --git a/docs/reference/elx_download_xml.html b/docs/reference/elx_download_xml.html index 1be1dfa..5a98ae5 100644 --- a/docs/reference/elx_download_xml.html +++ b/docs/reference/elx_download_xml.html @@ -17,7 +17,7 @@ eurlex - 0.4.7 + 0.4.8 @@ -44,7 +44,13 @@
  • Changelog
  • - + @@ -54,7 +60,7 @@
    @@ -121,6 +127,9 @@

    Examples

    temploc <- paste(tempdir(), "elxnotice.xml", sep = "\\") elx_download_xml(url = "http://publications.europa.eu/resource/celex/32022D0154", file = temploc, notice = "object") +#> Could not resolve host: publications.europa.eu (Error) +#> Error in utils::download.file(url = head$url, destfile = file, mode = mode): 'url' must be a length-one character vector +unlink(temploc) # }
    diff --git a/docs/reference/elx_fetch_data.html b/docs/reference/elx_fetch_data.html index 2feaf3a..d7ed3fa 100644 --- a/docs/reference/elx_fetch_data.html +++ b/docs/reference/elx_fetch_data.html @@ -17,7 +17,7 @@ eurlex - 0.4.7 + 0.4.8 @@ -44,7 +44,13 @@
  • Changelog
  • - + @@ -54,7 +60,7 @@
    diff --git a/docs/reference/elx_label_eurovoc.html b/docs/reference/elx_label_eurovoc.html index dc8b87a..0391c66 100644 --- a/docs/reference/elx_label_eurovoc.html +++ b/docs/reference/elx_label_eurovoc.html @@ -17,7 +17,7 @@ eurlex - 0.4.7 + 0.4.8
    @@ -44,7 +44,13 @@
  • Changelog
  • - + @@ -54,7 +60,7 @@
    diff --git a/docs/reference/elx_make_query.html b/docs/reference/elx_make_query.html index 84309e7..b570226 100644 --- a/docs/reference/elx_make_query.html +++ b/docs/reference/elx_make_query.html @@ -19,7 +19,7 @@ eurlex - 0.4.7 + 0.4.8
    @@ -46,7 +46,13 @@
  • Changelog
  • - + @@ -56,7 +62,7 @@
    diff --git a/docs/reference/elx_run_query.html b/docs/reference/elx_run_query.html index f2b765f..31bff56 100644 --- a/docs/reference/elx_run_query.html +++ b/docs/reference/elx_run_query.html @@ -19,7 +19,7 @@ eurlex - 0.4.7 + 0.4.8
    @@ -46,7 +46,13 @@
  • Changelog
  • - + @@ -56,7 +62,7 @@
    diff --git a/docs/reference/index.html b/docs/reference/index.html index d9e40dc..2e51eb1 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -17,7 +17,7 @@ eurlex - 0.4.7 + 0.4.8
    @@ -44,7 +44,13 @@
  • Changelog
  • - + @@ -61,10 +67,6 @@

    All functions

    -

    elx_council_votes()

    - -

    Retrieve Council votes on EU acts

    -

    elx_curia_list()

    Scrape list of court cases from Curia

    diff --git a/man/elx_council_votes.Rd b/man/elx_council_votes.Rd deleted file mode 100644 index f2658f3..0000000 --- a/man/elx_council_votes.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/elx_council_votes.R -\name{elx_council_votes} -\alias{elx_council_votes} -\title{Retrieve Council votes on EU acts} -\usage{ -elx_council_votes() -} -\value{ -A data frame with Council votes on EU acts. -} -\description{ -Executes a SPARQL query to the Council's endpoint. -} -\examples{ -\donttest{ -votes <- elx_council_votes() -} -} diff --git a/man/elx_download_xml.Rd b/man/elx_download_xml.Rd index 2a57aec..a19764f 100644 --- a/man/elx_download_xml.Rd +++ b/man/elx_download_xml.Rd @@ -43,5 +43,6 @@ To retrieve all identifiers associated with a url, use elx_fetch_data(type = "id temploc <- paste(tempdir(), "elxnotice.xml", sep = "\\\\") elx_download_xml(url = "http://publications.europa.eu/resource/celex/32022D0154", file = temploc, notice = "object") +unlink(temploc) } } diff --git a/vignettes/articles/council.Rmd b/vignettes/articles/council.Rmd index c39a72b..550b350 100644 --- a/vignettes/articles/council.Rmd +++ b/vignettes/articles/council.Rmd @@ -20,109 +20,6 @@ knitr::opts_chunk$set( 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. +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 could access these through a dedicated API maintained by the Council until May 2024. -## Data on Council votes -First we obtain the available data on Council votes using `eurlex::elx_council_votes()` and process the API response. - -```{r votingdata, eval=NOT_CRAN} -# packages -library(eurlex) -library(ggplot2) -library(dplyr) -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 ```r max(country_votes_prop$n_votes, na.rm = T)``` and ```r min(country_votes_prop$n_votes, na.rm = T)``` 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}$ - -## Interactive plot - -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. - -```{r votesproportion, eval=NOT_CRAN} -# 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.^[An example application of an ideal point IRT model can be found in [this paper](https://journals.sagepub.com/doi/full/10.1177/1465116520967414).] In our example, we assume that all votes are equally informative and important; ideally, we would want to relax this assumption. diff --git a/vignettes/articles/council.txt b/vignettes/articles/council.txt new file mode 100644 index 0000000..d45f8d8 --- /dev/null +++ b/vignettes/articles/council.txt @@ -0,0 +1,104 @@ +## Data on Council votes + +First we obtain the available data on Council votes using `eurlex::elx_council_votes()` and process the API response. + +```{r votingdata, eval=NOT_CRAN} +# packages +library(eurlex) +library(ggplot2) +library(dplyr) +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 ```r max(country_votes_prop$n_votes, na.rm = T)``` and ```r min(country_votes_prop$n_votes, na.rm = T)``` 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}$ + +## Interactive plot + +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. + +```{r votesproportion, eval=NOT_CRAN} +# 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.^[An example application of an ideal point IRT model can be found in [this paper](https://journals.sagepub.com/doi/full/10.1177/1465116520967414).] In our example, we assume that all votes are equally informative and important; ideally, we would want to relax this assumption. \ No newline at end of file diff --git a/vignettes/articles/eurlexpkg.Rmd b/vignettes/articles/eurlexpkg.Rmd index a90f9c8..e574b32 100644 --- a/vignettes/articles/eurlexpkg.Rmd +++ b/vignettes/articles/eurlexpkg.Rmd @@ -36,7 +36,7 @@ The `eurlex` R package attempts to significantly reduce the overhead associated The `eurlex` package currently envisions the typical use-case to consist of getting bulk information about EU law and policy into R as fast as possible. The package contains three core functions to achieve that objective: `elx_make_query()` to create SPARQL queries based on user input; `elx_run_query()` to execute the pre-made or any other manually input query; and `elx_fetch_data()` to fire GET requests for certain metadata to the REST API. -The package also contains largely self-explanatory functions for retrieving data on EU court cases (`elx_curia_list()`) and Council votes (`elx_council_votes()`) from outside Eur-Lex. More advanced users might be interested in downloading and custom-parsing XML notices with `elx_download_xml()`. +The package also contains largely self-explanatory functions for retrieving data on EU court cases (`elx_curia_list()`) and Council votes (`elx_council_votes()`, currently dysfunctional) from outside Eur-Lex. More advanced users might be interested in downloading and custom-parsing XML notices with `elx_download_xml()`. ## `elx_make_query()`: Generate SPARQL queries diff --git a/vignettes/sparql-queries.Rmd b/vignettes/sparql-queries.Rmd index 4fd6103..0a57d6b 100644 --- a/vignettes/sparql-queries.Rmd +++ b/vignettes/sparql-queries.Rmd @@ -32,7 +32,7 @@ The `eurlex` R package attempts to significantly reduce the overhead associated The `eurlex` package currently envisions the typical use-case to consist of getting bulk information about EU law and policy into R as fast as possible. The package contains three core functions to achieve that objective: `elx_make_query()` to create SPARQL queries based on user input; `elx_run_query()` to execute the pre-made or any other manually input query; and `elx_fetch_data()` to fire GET requests for certain metadata to the REST API. -The package also contains largely self-explanatory functions for retrieving data on EU court cases (`elx_curia_list()`) and Council votes (`elx_council_votes()`) from outside Eur-Lex. More advanced users might be interested in downloading and custom-parsing XML notices with `elx_download_xml()`. +The package also contains largely self-explanatory functions for retrieving data on EU court cases (`elx_curia_list()`) and Council votes (`elx_council_votes()`, currently dysfunctional) from outside Eur-Lex. More advanced users might be interested in downloading and custom-parsing XML notices with `elx_download_xml()`. ## `elx_make_query()`: Generate SPARQL queries