From e734c9175925023a3cbdacd1f238a8cd17f60e5d Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Thu, 18 Jan 2024 19:29:43 +0200 Subject: [PATCH 01/62] First attempt at more SDMX based data retrieval --- DESCRIPTION | 4 +- LICENSE | 2 +- NAMESPACE | 1 + R/eurostat-package.R | 2 +- R/get_eurostat_json.R | 24 +++- R/get_eurostat_sdmx.R | 246 +++++++++++++++++++++++++++++++++++++++ man/eurostat-package.Rd | 35 ++---- man/eurotime2date.Rd | 31 +---- man/get_eurostat.Rd | 31 +---- man/get_eurostat_dic.Rd | 31 +---- man/get_eurostat_json.Rd | 55 ++++----- man/get_eurostat_raw.Rd | 31 +---- man/get_eurostat_sdmx.Rd | 60 ++++++++++ man/get_eurostat_toc.Rd | 31 +---- man/search_eurostat.Rd | 31 +---- man/tidy_eurostat.Rd | 31 +---- 16 files changed, 398 insertions(+), 248 deletions(-) create mode 100644 R/get_eurostat_sdmx.R create mode 100644 man/get_eurostat_sdmx.Rd diff --git a/DESCRIPTION b/DESCRIPTION index ea2c8b76..15fb80c3 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: eurostat Title: Tools for Eurostat Open Data -Version: 4.0.0 -Date: 2023-12-19 +Version: 4.1.0.9000 +Date: 2024-01-18 Authors@R: c( person("Leo", "Lahti", , "leo.lahti@iki.fi", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5537-637X")), diff --git a/LICENSE b/LICENSE index 1de69c39..0325ecb1 100755 --- a/LICENSE +++ b/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2014-2022 +YEAR: 2014-2024 COPYRIGHT HOLDER: Leo Lahti, Janne Huovari, Markus Kainu, Przemyslaw Biecek \ No newline at end of file diff --git a/NAMESPACE b/NAMESPACE index 2ecf819b..a5278172 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -14,6 +14,7 @@ export(get_eurostat_folder) export(get_eurostat_geospatial) export(get_eurostat_interactive) export(get_eurostat_json) +export(get_eurostat_sdmx) export(get_eurostat_toc) export(grepEurostatTOC) export(harmonize_country_code) diff --git a/R/eurostat-package.R b/R/eurostat-package.R index 7f231408..2a0c0368 100755 --- a/R/eurostat-package.R +++ b/R/eurostat-package.R @@ -12,7 +12,7 @@ #' | **Package** | eurostat | #' | **Type** | Package | #' | **Version** | `r packageVersion("eurostat")` | -#' | **Date** | 2014-2023 | +#' | **Date** | 2014-2024 | #' | **License** | `r as.character(utils::packageDescription("eurostat")["License"])`| #' | **LazyLoad** | yes | #' diff --git a/R/get_eurostat_json.R b/R/get_eurostat_json.R index 3549e327..9080012a 100755 --- a/R/get_eurostat_json.R +++ b/R/get_eurostat_json.R @@ -1,11 +1,15 @@ -#' @title Get Data from Eurostat API in JSON -#' @description Retrieve data from Eurostat API in JSON format. +#' @title Get Data from Eurostat API Statistics +#' @description Retrieve data from Eurostat API Statistics in JSON-stat 2.0 format. #' @details #' Data to retrieve from -#' [The Eurostat Web Services](https://ec.europa.eu/eurostat/web/main/data/web-services) +#' [The Eurostat API Statistics](https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query) #' can be specified with filters. Normally, it is #' better to use JSON query through [get_eurostat()], than to use -#' [get_eurostat_json()] directly. +#' [get_eurostat_json()] directly. The main reason for this is that +#' [get_eurostat_json()] returns a relatively raw dataset that does not go +#' through helper functions in [get_eurostat()], such as [eurotime2date()] or +#' [eurotime2num()] functions or reading data from cache and saving data +#' to cache. #' #' Queries are limited to 50 sub-indicators at a time. A time can be #' filtered with fixed "time" filter or with "sinceTimePeriod" and @@ -26,6 +30,18 @@ #' the possible interpretation or cause of each error. These messages are #' returned if the API returns a status indicating a HTTP error #' (400 or greater). +#' +#' Additionally, there is limit on the size of the returned extractions +#' (error code 413). +#' At the time of publishing this package version, the max authorised size for +#' the extraction seems to be 5000000 (5 million) rows. The server seems to +#' estimate the size of the returned data object with a method that is +#' unknown to us so the number of rows might not be the same as the number +#' of returned rows in the JSON-stat object. You can limit the number of rows +#' in your extraction by providing more filters to the query. If the +#' extraction size continues to go over limit you can try to download the +#' whole dataset with [get_eurostat()] and filter it locally on your computer, +#' for example with base R or dplyr data wrangling functions. #' #' The Eurostat implementation seems to be based on SDMX 2.1, which is the #' reason we've used SDMX Standards guidelines as a supplementary source diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R new file mode 100644 index 00000000..8a4203c8 --- /dev/null +++ b/R/get_eurostat_sdmx.R @@ -0,0 +1,246 @@ +#' @title Get Eurostat Data from SDMX 2.1 API +#' +#' @description +#' Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} +#' +#' @details +#' Download data from SDMX API +#' +#' @inheritParams get_eurostat +#' @param agency Either "Eurostat" (default), "Eurostat_comext" +#' (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW" +#' +#' @importFrom curl curl_download +#' @importFrom utils download.file +#' @importFrom readr read_tsv cols col_character +#' @importFrom data.table fread +#' +#' @export +get_eurostat_sdmx <- function( + id, + time_format, + filters = NULL, + type, + lang, + use.data.table, + agency = "Eurostat", + compressed = TRUE, + label = FALSE + ) { + + # Check if you have access to ec.europe.eu. + # If dataset is cached, access to ec.europe.eu is not needed + # Therefore this is a warning, not a stop + if (!check_access_to_data()) { + # nocov start + stop("You have no access to ec.europe.eu. + Please check your connection and/or review your proxy settings") + # nocov end + } + + agency <- tolower(agency) + + agencyID <- switch( + agency, + eurostat = "ESTAT", + eurostat_comext = "ESTAT", + comp = "COMP", + empl = "EMPL", + grow = "GROW" + ) + + api_base_uri <- switch( + agency, + eurostat = "https://ec.europa.eu/eurostat/api/dissemination", + eurostat_comext = "https://ec.europa.eu/eurostat/api/comext/dissemination", + comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", + empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", + grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") + + if (is.null(api_base_uri)) stop("Use valid agency") + + if (is.null(filters) && agency == "eurostat_comext") { + stop("Use filters when querying data from Eurostat COMEXT or PRODCOM") + } + + # Following resource is supported: data + resource <- "data" + # The identifier of the dataflow reference + flowRef <- id + key <- data_filtering_on_dimension(api_base_uri, id, filters) + compressed <- ifelse(compressed == TRUE, "&compressed=true", "&compressed=false") + + url <- paste0( + api_base_uri, + "/sdmx/2.1/", + resource, + "/", + flowRef, + "/", + key, + "?", + "format=SDMX-CSV", + compressed + ) + + tfile <- tempfile() + on.exit(unlink(tfile)) + + curl::curl_download(url = url, destfile = tfile) + + x <- read.csv(tfile) + + # return(references_resolution(api_base_uri, resource = "datastructure", agencyID = agencyID, resourceID = id)) + + if (label){ + x <- label_eurostat_sdmx(x = x, + api_base_uri = api_base_uri, + resourceID = "codelist", + id = id) + } + + x + +} + +data_filtering_on_dimension <- function(api_base_uri, id, filters) { + # data_structure_definition_url <- paste0( + # api_base_url, + # "/sdmx/2.1/datastructure/estat/", + # id) + + filter_names <- toupper(names(filters)) + + dimension_df <- map_dimensions(api_base_uri = api_base_uri, + id = id) + dimension_id_upper <- dimension_df$dimension_id_upper + + # dsd <- xml2::read_xml(data_structure_definition_url) + # + # # dimension_id <- xml2::xml_text(xml2::xml_find_all(xml2::xml_find_all(dsd, ".//s:Dimension"), ".//Ref[@class='Codelist']/@id")) + # dimension_id <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/@id")) + # dimension_position <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/@position")) + # codelist_id <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/s:LocalRepresentation/s:Enumeration/Ref/@id")) + # dimension_id_upper <- toupper(dimension_id) + # dimension_df <- data.frame(dimension_position, dimension_id, dimension_id_upper, codelist_id) + + # if (!setequal(filter_names, dimension_id)) { + # stop(paste0("Use valid filter dimensions, in this order: ", paste(dimension_id, collapse = "."))) + # } + + if (!rlang::is_empty(setdiff(filter_names, dimension_id_upper))) { + stop(paste0("Use valid filter dimensions in the correct order: ", paste(dimension_id, collapse = "."))) + } + + filter_string <- "" + for (i in seq_along(dimension_df$dimension_id_upper)){ + if (dimension_df$dimension_id_upper[i] %in% filter_names) { + x <- paste(filters[[dimension_df$dimension_id_upper[i]]], collapse = "+") + filter_string <- paste0(filter_string, x, ".") + } else { + filter_string <- paste0(filter_string, ".") + } + if (i == length(dimension_df$dimension_id_upper)) { + # Remove final dot + filter_string <- substr(filter_string, 1, nchar(filter_string)-1) + } + } + return(filter_string) +} + +map_dimensions <- function(api_base_uri, id) { + data_structure_definition_url <- paste0( + api_base_uri, + "/sdmx/2.1/datastructure/estat/", + id) + + dsd <- xml2::read_xml(data_structure_definition_url) + + # dimension_id <- xml2::xml_text(xml2::xml_find_all(xml2::xml_find_all(dsd, ".//s:Dimension"), ".//Ref[@class='Codelist']/@id")) + dimension_id <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/@id")) + dimension_position <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/@position")) + codelist_id <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/s:LocalRepresentation/s:Enumeration/Ref/@id")) + dimension_id_upper <- toupper(dimension_id) + dimension_df <- data.frame(dimension_position, dimension_id, dimension_id_upper, codelist_id) + + dimension_df +} + +# references_resolution <- function(api_base_uri, resource, agencyID, resourceID) { +# # resource <- "datastructure" +# +# url <- paste0( +# api_base_uri, +# "/sdmx/2.1/", +# resource, +# "/", +# agencyID, +# "/", +# resourceID +# ) +# +# parsed <- xml2::read_xml(url) +# +# } + +# get_codelist <- function(api_base_uri, id, dimension_id) { +# +# } + +label_eurostat_sdmx <- function(x, api_base_uri, resourceID, id, lang = "en") { + # how many columns there are that can be labeled with a codelist + dimension_df <- map_dimensions(api_base_uri = api_base_uri, id = id) + resource <- "codelist" + agencyID <- "ESTAT" + for (i in seq_len(nrow(dimension_df))) { + resourceID <- dimension_df$codelist_id[[i]] + message(paste("Building codelist URL for resourceID:", resourceID)) + codelist_url <- paste0( + api_base_uri, + "/sdmx/2.1/", + resource, + "/", + agencyID, + "/", + resourceID, + "?format=TSV", + "&lang=", + lang + ) + codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc")) + column_to_handle <- dimension_df$dimension_id[[i]] + col <- x[[column_to_handle]] + codes_to_label <- codelist[(codelist[,1] %in% col),] + message(paste("Labeling dimension (column):", column_to_handle)) + for (j in seq_len(nrow(codes_to_label))) { + col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) + } + x[column_to_handle] <- col + } + if ("OBS_FLAG" %in% names(x)){ + resourceID <- "OBS_FLAG" + message(paste("Building codelist URL for resourceID:", resourceID)) + codelist_url <- paste0( + api_base_uri, + "/sdmx/2.1/", + resource, + "/", + agencyID, + "/", + resourceID, + "?format=TSV", + "&lang=", + lang + ) + codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc")) + column_to_handle <- "OBS_FLAG" + col <- x[[column_to_handle]] + codes_to_label <- codelist[(codelist[,1] %in% col),] + message(paste("Labeling dimension (column):", column_to_handle)) + for (j in seq_len(nrow(codes_to_label))) { + col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) + } + x[column_to_handle] <- col + } + x +} \ No newline at end of file diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index 19643fc5..582634d5 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -15,8 +15,8 @@ utilities. \tab \cr \strong{Package} \tab eurostat \cr \strong{Type} \tab Package \cr - \strong{Version} \tab 4.0.0 \cr - \strong{Date} \tab 2014-2023 \cr + \strong{Version} \tab 4.1.0.9000 \cr + \strong{Date} \tab 2014-2024 \cr \strong{License} \tab BSD_2_clause + file LICENSE \cr \strong{LazyLoad} \tab yes \cr } @@ -354,35 +354,14 @@ See \code{citation("eurostat")}: analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 -A BibTeX entry for LaTeX users is - - @Article\{10.32614/RJ-2017-019, - title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, - journal = \{The R Journal\}, - volume = \{9\}, - number = \{1\}, - pages = \{385--392\}, - year = \{2017\}, - doi = \{10.32614/RJ-2017-019\}, - url = \{https://doi.org/10.32614/RJ-2017-019\}, - \} - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.0.0. + and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9000. https://github.com/rOpenGov/eurostat -A BibTeX entry for LaTeX users is - - @Misc\{eurostat, - title = \{eurostat: Tools for Eurostat Open Data\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, - url = \{https://github.com/rOpenGov/eurostat\}, - type = \{Computer software\}, - year = \{2023\}, - note = \{R package version 4.0.0\}, - \} +To see these entries in BibTeX format, use 'print(, +bibtex=TRUE)', 'toBibtex(.)', or set +'options(citation.bibtex.max=999)'. }\if{html}{\out{}} When citing data downloaded from Eurostat, see section "Citing Eurostat data" diff --git a/man/eurotime2date.Rd b/man/eurotime2date.Rd index 06a925c7..8f5ebe58 100755 --- a/man/eurotime2date.Rd +++ b/man/eurotime2date.Rd @@ -52,35 +52,14 @@ See \code{citation("eurostat")}: # analysis of Eurostat open data with the eurostat package. The R # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # -# A BibTeX entry for LaTeX users is -# -# @Article\{10.32614/RJ-2017-019, -# title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, -# author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, -# journal = \{The R Journal\}, -# volume = \{9\}, -# number = \{1\}, -# pages = \{385--392\}, -# year = \{2017\}, -# doi = \{10.32614/RJ-2017-019\}, -# url = \{https://doi.org/10.32614/RJ-2017-019\}, -# \} -# # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.0.0. +# and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data +# [Computer software]. R package version 4.1.0.9000. # https://github.com/rOpenGov/eurostat # -# A BibTeX entry for LaTeX users is -# -# @Misc\{eurostat, -# title = \{eurostat: Tools for Eurostat Open Data\}, -# author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, -# url = \{https://github.com/rOpenGov/eurostat\}, -# type = \{Computer software\}, -# year = \{2023\}, -# note = \{R package version 4.0.0\}, -# \} +# To see these entries in BibTeX format, use 'print(, +# bibtex=TRUE)', 'toBibtex(.)', or set +# 'options(citation.bibtex.max=999)'. }\if{html}{\out{}} } \seealso{ diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index 1e0ee2a8..eada9cc8 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -375,35 +375,14 @@ See \code{citation("eurostat")}: analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 -A BibTeX entry for LaTeX users is - - @Article\{10.32614/RJ-2017-019, - title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, - journal = \{The R Journal\}, - volume = \{9\}, - number = \{1\}, - pages = \{385--392\}, - year = \{2017\}, - doi = \{10.32614/RJ-2017-019\}, - url = \{https://doi.org/10.32614/RJ-2017-019\}, - \} - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.0.0. + and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9000. https://github.com/rOpenGov/eurostat -A BibTeX entry for LaTeX users is - - @Misc\{eurostat, - title = \{eurostat: Tools for Eurostat Open Data\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, - url = \{https://github.com/rOpenGov/eurostat\}, - type = \{Computer software\}, - year = \{2023\}, - note = \{R package version 4.0.0\}, - \} +To see these entries in BibTeX format, use 'print(, +bibtex=TRUE)', 'toBibtex(.)', or set +'options(citation.bibtex.max=999)'. }\if{html}{\out{}} When citing data downloaded from Eurostat, see section "Citing Eurostat data" diff --git a/man/get_eurostat_dic.Rd b/man/get_eurostat_dic.Rd index c236e422..d31a2e7d 100755 --- a/man/get_eurostat_dic.Rd +++ b/man/get_eurostat_dic.Rd @@ -42,35 +42,14 @@ See \code{citation("eurostat")}: # analysis of Eurostat open data with the eurostat package. The R # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # -# A BibTeX entry for LaTeX users is -# -# @Article\{10.32614/RJ-2017-019, -# title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, -# author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, -# journal = \{The R Journal\}, -# volume = \{9\}, -# number = \{1\}, -# pages = \{385--392\}, -# year = \{2017\}, -# doi = \{10.32614/RJ-2017-019\}, -# url = \{https://doi.org/10.32614/RJ-2017-019\}, -# \} -# # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.0.0. +# and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data +# [Computer software]. R package version 4.1.0.9000. # https://github.com/rOpenGov/eurostat # -# A BibTeX entry for LaTeX users is -# -# @Misc\{eurostat, -# title = \{eurostat: Tools for Eurostat Open Data\}, -# author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, -# url = \{https://github.com/rOpenGov/eurostat\}, -# type = \{Computer software\}, -# year = \{2023\}, -# note = \{R package version 4.0.0\}, -# \} +# To see these entries in BibTeX format, use 'print(, +# bibtex=TRUE)', 'toBibtex(.)', or set +# 'options(citation.bibtex.max=999)'. }\if{html}{\out{}} } \seealso{ diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 83583600..53d477c1 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/get_eurostat_json.R \name{get_eurostat_json} \alias{get_eurostat_json} -\title{Get Data from Eurostat API in JSON} +\title{Get Data from Eurostat API Statistics} \usage{ get_eurostat_json( id, @@ -50,14 +50,18 @@ following: \code{basic}, digest, digest_ie, gssnegotiate, ntlm, any.} A dataset as an object of \code{data.frame} class. } \description{ -Retrieve data from Eurostat API in JSON format. +Retrieve data from Eurostat API Statistics in JSON-stat 2.0 format. } \details{ Data to retrieve from -\href{https://ec.europa.eu/eurostat/web/main/data/web-services}{The Eurostat Web Services} +\href{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query}{The Eurostat API Statistics} can be specified with filters. Normally, it is better to use JSON query through \code{\link[=get_eurostat]{get_eurostat()}}, than to use -\code{\link[=get_eurostat_json]{get_eurostat_json()}} directly. +\code{\link[=get_eurostat_json]{get_eurostat_json()}} directly. The main reason for this is that +\code{\link[=get_eurostat_json]{get_eurostat_json()}} returns a relatively raw dataset that does not go +through helper functions in \code{\link[=get_eurostat]{get_eurostat()}}, such as \code{\link[=eurotime2date]{eurotime2date()}} or +\code{\link[=eurotime2num]{eurotime2num()}} functions or reading data from cache and saving data +to cache. Queries are limited to 50 sub-indicators at a time. A time can be filtered with fixed "time" filter or with "sinceTimePeriod" and @@ -79,6 +83,18 @@ the possible interpretation or cause of each error. These messages are returned if the API returns a status indicating a HTTP error (400 or greater). +Additionally, there is limit on the size of the returned extractions +(error code 413). +At the time of publishing this package version, the max authorised size for +the extraction seems to be 5000000 (5 million) rows. The server seems to +estimate the size of the returned data object with a method that is +unknown to us so the number of rows might not be the same as the number +of returned rows in the JSON-stat object. You can limit the number of rows +in your extraction by providing more filters to the query. If the +extraction size continues to go over limit you can try to download the +whole dataset with \code{\link[=get_eurostat]{get_eurostat()}} and filter it locally on your computer, +for example with base R or dplyr data wrangling functions. + The Eurostat implementation seems to be based on SDMX 2.1, which is the reason we've used SDMX Standards guidelines as a supplementary source that we have included in the dataset. What this means in practice is that @@ -299,35 +315,14 @@ See \code{citation("eurostat")}: analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 -A BibTeX entry for LaTeX users is - - @Article\{10.32614/RJ-2017-019, - title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, - journal = \{The R Journal\}, - volume = \{9\}, - number = \{1\}, - pages = \{385--392\}, - year = \{2017\}, - doi = \{10.32614/RJ-2017-019\}, - url = \{https://doi.org/10.32614/RJ-2017-019\}, - \} - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.0.0. + and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9000. https://github.com/rOpenGov/eurostat -A BibTeX entry for LaTeX users is - - @Misc\{eurostat, - title = \{eurostat: Tools for Eurostat Open Data\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, - url = \{https://github.com/rOpenGov/eurostat\}, - type = \{Computer software\}, - year = \{2023\}, - note = \{R package version 4.0.0\}, - \} +To see these entries in BibTeX format, use 'print(, +bibtex=TRUE)', 'toBibtex(.)', or set +'options(citation.bibtex.max=999)'. }\if{html}{\out{}} When citing data downloaded from Eurostat, see section "Citing Eurostat data" diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index d3aca865..c3ea6770 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -122,35 +122,14 @@ See \code{citation("eurostat")}: # analysis of Eurostat open data with the eurostat package. The R # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # -# A BibTeX entry for LaTeX users is -# -# @Article\{10.32614/RJ-2017-019, -# title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, -# author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, -# journal = \{The R Journal\}, -# volume = \{9\}, -# number = \{1\}, -# pages = \{385--392\}, -# year = \{2017\}, -# doi = \{10.32614/RJ-2017-019\}, -# url = \{https://doi.org/10.32614/RJ-2017-019\}, -# \} -# # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.0.0. +# and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data +# [Computer software]. R package version 4.1.0.9000. # https://github.com/rOpenGov/eurostat # -# A BibTeX entry for LaTeX users is -# -# @Misc\{eurostat, -# title = \{eurostat: Tools for Eurostat Open Data\}, -# author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, -# url = \{https://github.com/rOpenGov/eurostat\}, -# type = \{Computer software\}, -# year = \{2023\}, -# note = \{R package version 4.0.0\}, -# \} +# To see these entries in BibTeX format, use 'print(, +# bibtex=TRUE)', 'toBibtex(.)', or set +# 'options(citation.bibtex.max=999)'. }\if{html}{\out{}} } \seealso{ diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd new file mode 100644 index 00000000..8a8c5da6 --- /dev/null +++ b/man/get_eurostat_sdmx.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_eurostat_sdmx.R +\name{get_eurostat_sdmx} +\alias{get_eurostat_sdmx} +\title{Get Eurostat Data from SDMX 2.1 API} +\usage{ +get_eurostat_sdmx( + id, + time_format, + filters = NULL, + type, + lang, + use.data.table, + agency = "Eurostat", + compressed = TRUE, + label = FALSE +) +} +\arguments{ +\item{id}{A unique identifier / code for the dataset of interest. If code is not +known \code{\link[=search_eurostat]{search_eurostat()}} function can be used to search Eurostat table +of contents.} + +\item{time_format}{a string giving a type of the conversion of the time column from the +eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +with the date being the first day of the period. A "\code{date_last}" argument +converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +that the exact date is the last date of the period. Period can be year, +semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for +more information). +Argument "\code{num}" converts the date into a numeric (integer) meaning that +the first day of the year 2000 is close to 2000.01 and the last day of the +year is close to 2000.99 (see \code{\link[=eurotime2num]{eurotime2num()}} for more information). +Using the argument "\code{raw}" preserves the dates as they were in the original +Eurostat data.} + +\item{filters}{A named list of filters. Names of list objects are Eurostat +variable codes and values are vectors of observation codes. If \code{NULL} +(default) the whole dataset is returned. See details for more information +on filters and limitations per query.} + +\item{type}{A type of variables, "\code{code}" (default), "\code{label}" or "\code{both}". +The parameter "\code{both}" will return a data_frame with named vectors, +labels as values and codes as names.} + +\item{lang}{2-letter language code, default is "\code{en}" (English), other +options are "\code{fr}" (French) and "\code{de}" (German). Used for labeling datasets.} + +\item{use.data.table}{Use faster data.table functions? Default is FALSE. +On Windows requires that RTools is installed.} + +\item{agency}{Either "Eurostat" (default), "Eurostat_comext" +(for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW"} +} +\description{ +Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} +} +\details{ +Download data from SDMX API +} diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index 1efe3545..ecb2314e 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -89,35 +89,14 @@ See \code{citation("eurostat")}: analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 -A BibTeX entry for LaTeX users is - - @Article\{10.32614/RJ-2017-019, - title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, - journal = \{The R Journal\}, - volume = \{9\}, - number = \{1\}, - pages = \{385--392\}, - year = \{2017\}, - doi = \{10.32614/RJ-2017-019\}, - url = \{https://doi.org/10.32614/RJ-2017-019\}, - \} - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.0.0. + and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9000. https://github.com/rOpenGov/eurostat -A BibTeX entry for LaTeX users is - - @Misc\{eurostat, - title = \{eurostat: Tools for Eurostat Open Data\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, - url = \{https://github.com/rOpenGov/eurostat\}, - type = \{Computer software\}, - year = \{2023\}, - note = \{R package version 4.0.0\}, - \} +To see these entries in BibTeX format, use 'print(, +bibtex=TRUE)', 'toBibtex(.)', or set +'options(citation.bibtex.max=999)'. }\if{html}{\out{}} When citing data downloaded from Eurostat, see section "Citing Eurostat data" diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index 2f96125b..7615b3ee 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -97,35 +97,14 @@ See \code{citation("eurostat")}: analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 -A BibTeX entry for LaTeX users is - - @Article\{10.32614/RJ-2017-019, - title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, - journal = \{The R Journal\}, - volume = \{9\}, - number = \{1\}, - pages = \{385--392\}, - year = \{2017\}, - doi = \{10.32614/RJ-2017-019\}, - url = \{https://doi.org/10.32614/RJ-2017-019\}, - \} - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.0.0. + and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9000. https://github.com/rOpenGov/eurostat -A BibTeX entry for LaTeX users is - - @Misc\{eurostat, - title = \{eurostat: Tools for Eurostat Open Data\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, - url = \{https://github.com/rOpenGov/eurostat\}, - type = \{Computer software\}, - year = \{2023\}, - note = \{R package version 4.0.0\}, - \} +To see these entries in BibTeX format, use 'print(, +bibtex=TRUE)', 'toBibtex(.)', or set +'options(citation.bibtex.max=999)'. }\if{html}{\out{}} When citing data downloaded from Eurostat, see section "Citing Eurostat data" diff --git a/man/tidy_eurostat.Rd b/man/tidy_eurostat.Rd index 55156ae6..cb39bf65 100755 --- a/man/tidy_eurostat.Rd +++ b/man/tidy_eurostat.Rd @@ -77,35 +77,14 @@ See \code{citation("eurostat")}: analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 -A BibTeX entry for LaTeX users is - - @Article\{10.32614/RJ-2017-019, - title = \{Retrieval and Analysis of Eurostat Open Data with the eurostat Package\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek\}, - journal = \{The R Journal\}, - volume = \{9\}, - number = \{1\}, - pages = \{385--392\}, - year = \{2017\}, - doi = \{10.32614/RJ-2017-019\}, - url = \{https://doi.org/10.32614/RJ-2017-019\}, - \} - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.0.0. + and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9000. https://github.com/rOpenGov/eurostat -A BibTeX entry for LaTeX users is - - @Misc\{eurostat, - title = \{eurostat: Tools for Eurostat Open Data\}, - author = \{Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen\}, - url = \{https://github.com/rOpenGov/eurostat\}, - type = \{Computer software\}, - year = \{2023\}, - note = \{R package version 4.0.0\}, - \} +To see these entries in BibTeX format, use 'print(, +bibtex=TRUE)', 'toBibtex(.)', or set +'options(citation.bibtex.max=999)'. }\if{html}{\out{}} When citing data downloaded from Eurostat, see section "Citing Eurostat data" From 5bf1f423f5ecf6750d88fa4407c520cbfe992536 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Fri, 19 Jan 2024 11:42:38 +0200 Subject: [PATCH 02/62] Fix issues with tsv headers, col classes, missing codelists --- R/get_eurostat_sdmx.R | 77 ++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 8a4203c8..41ca8c14 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -88,7 +88,7 @@ get_eurostat_sdmx <- function( curl::curl_download(url = url, destfile = tfile) - x <- read.csv(tfile) + x <- read.csv(tfile, colClasses = "character") # return(references_resolution(api_base_uri, resource = "datastructure", agencyID = agencyID, resourceID = id)) @@ -96,6 +96,7 @@ get_eurostat_sdmx <- function( x <- label_eurostat_sdmx(x = x, api_base_uri = api_base_uri, resourceID = "codelist", + agencyID = agencyID, id = id) } @@ -187,11 +188,11 @@ map_dimensions <- function(api_base_uri, id) { # # } -label_eurostat_sdmx <- function(x, api_base_uri, resourceID, id, lang = "en") { +label_eurostat_sdmx <- function(x, api_base_uri, resourceID, id, agencyID, lang = "en") { # how many columns there are that can be labeled with a codelist dimension_df <- map_dimensions(api_base_uri = api_base_uri, id = id) resource <- "codelist" - agencyID <- "ESTAT" + agencyID <- agencyID for (i in seq_len(nrow(dimension_df))) { resourceID <- dimension_df$codelist_id[[i]] message(paste("Building codelist URL for resourceID:", resourceID)) @@ -207,40 +208,48 @@ label_eurostat_sdmx <- function(x, api_base_uri, resourceID, id, lang = "en") { "&lang=", lang ) - codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc")) - column_to_handle <- dimension_df$dimension_id[[i]] - col <- x[[column_to_handle]] - codes_to_label <- codelist[(codelist[,1] %in% col),] - message(paste("Labeling dimension (column):", column_to_handle)) - for (j in seq_len(nrow(codes_to_label))) { - col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) - } - x[column_to_handle] <- col + tryCatch({ + codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc", col_names = FALSE)) + column_to_handle <- dimension_df$dimension_id[[i]] + col <- x[[column_to_handle]] + codes_to_label <- codelist[(codelist[,1] %in% col),] + message(paste("Labeling dimension (column):", column_to_handle)) + for (j in seq_len(nrow(codes_to_label))) { + col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) + } + x[column_to_handle] <- col + }, + error = function(e) message(paste("Couldn't label", resourceID)) + ) } if ("OBS_FLAG" %in% names(x)){ - resourceID <- "OBS_FLAG" - message(paste("Building codelist URL for resourceID:", resourceID)) - codelist_url <- paste0( - api_base_uri, - "/sdmx/2.1/", - resource, - "/", - agencyID, - "/", - resourceID, - "?format=TSV", - "&lang=", - lang + tryCatch({ + resourceID <- "OBS_FLAG" + message(paste("Building codelist URL for resourceID:", resourceID)) + codelist_url <- paste0( + api_base_uri, + "/sdmx/2.1/", + resource, + "/", + agencyID, + "/", + resourceID, + "?format=TSV", + "&lang=", + lang + ) + codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc")) + column_to_handle <- "OBS_FLAG" + col <- x[[column_to_handle]] + codes_to_label <- codelist[(codelist[,1] %in% col),] + message(paste("Labeling dimension (column):", column_to_handle)) + for (j in seq_len(nrow(codes_to_label))) { + col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) + } + x[column_to_handle] <- col + }, + error = function(e) message(paste("Couldn't label", resourceID)) ) - codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc")) - column_to_handle <- "OBS_FLAG" - col <- x[[column_to_handle]] - codes_to_label <- codelist[(codelist[,1] %in% col),] - message(paste("Labeling dimension (column):", column_to_handle)) - for (j in seq_len(nrow(codes_to_label))) { - col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) - } - x[column_to_handle] <- col } x } \ No newline at end of file From 92f80072e6313be105003c1e1d701d87dc6dda8a Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Fri, 19 Jan 2024 12:30:49 +0200 Subject: [PATCH 03/62] Simplify labeling by using agency instead of api_base_uri in params --- R/get_eurostat_sdmx.R | 95 +++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 41ca8c14..21206ac8 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -40,22 +40,25 @@ get_eurostat_sdmx <- function( agency <- tolower(agency) - agencyID <- switch( - agency, - eurostat = "ESTAT", - eurostat_comext = "ESTAT", - comp = "COMP", - empl = "EMPL", - grow = "GROW" - ) + # agencyID <- switch( + # agency, + # eurostat = "ESTAT", + # eurostat_comext = "ESTAT", + # comp = "COMP", + # empl = "EMPL", + # grow = "GROW" + # ) - api_base_uri <- switch( - agency, - eurostat = "https://ec.europa.eu/eurostat/api/dissemination", - eurostat_comext = "https://ec.europa.eu/eurostat/api/comext/dissemination", - comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", - empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", - grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") + api_base_uri <- build_api_base_uri(agency) + agencyID <- build_agencyID(agency) + + # api_base_uri <- switch( + # agency, + # eurostat = "https://ec.europa.eu/eurostat/api/dissemination", + # eurostat_comext = "https://ec.europa.eu/eurostat/api/comext/dissemination", + # comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", + # empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", + # grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") if (is.null(api_base_uri)) stop("Use valid agency") @@ -67,7 +70,7 @@ get_eurostat_sdmx <- function( resource <- "data" # The identifier of the dataflow reference flowRef <- id - key <- data_filtering_on_dimension(api_base_uri, id, filters) + key <- data_filtering_on_dimension(agency, id, filters) compressed <- ifelse(compressed == TRUE, "&compressed=true", "&compressed=false") url <- paste0( @@ -94,9 +97,8 @@ get_eurostat_sdmx <- function( if (label){ x <- label_eurostat_sdmx(x = x, - api_base_uri = api_base_uri, + agency = agency, resourceID = "codelist", - agencyID = agencyID, id = id) } @@ -104,7 +106,32 @@ get_eurostat_sdmx <- function( } -data_filtering_on_dimension <- function(api_base_uri, id, filters) { +build_api_base_uri <- function(agency) { + api_base_uri <- switch( + agency, + eurostat = "https://ec.europa.eu/eurostat/api/dissemination", + eurostat_comext = "https://ec.europa.eu/eurostat/api/comext/dissemination", + comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", + empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", + grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") + + api_base_uri +} + +build_agencyID <- function(agency) { + agencyID <- switch( + agency, + eurostat = "ESTAT", + eurostat_comext = "ESTAT", + comp = "COMP", + empl = "EMPL", + grow = "GROW" + ) + + agencyID +} + +data_filtering_on_dimension <- function(agency, id, filters) { # data_structure_definition_url <- paste0( # api_base_url, # "/sdmx/2.1/datastructure/estat/", @@ -112,27 +139,16 @@ data_filtering_on_dimension <- function(api_base_uri, id, filters) { filter_names <- toupper(names(filters)) - dimension_df <- map_dimensions(api_base_uri = api_base_uri, + dimension_df <- get_codelist_id(agency = agency, id = id) dimension_id_upper <- dimension_df$dimension_id_upper - # dsd <- xml2::read_xml(data_structure_definition_url) - # - # # dimension_id <- xml2::xml_text(xml2::xml_find_all(xml2::xml_find_all(dsd, ".//s:Dimension"), ".//Ref[@class='Codelist']/@id")) - # dimension_id <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/@id")) - # dimension_position <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/@position")) - # codelist_id <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/s:LocalRepresentation/s:Enumeration/Ref/@id")) - # dimension_id_upper <- toupper(dimension_id) - # dimension_df <- data.frame(dimension_position, dimension_id, dimension_id_upper, codelist_id) - - # if (!setequal(filter_names, dimension_id)) { - # stop(paste0("Use valid filter dimensions, in this order: ", paste(dimension_id, collapse = "."))) - # } - if (!rlang::is_empty(setdiff(filter_names, dimension_id_upper))) { stop(paste0("Use valid filter dimensions in the correct order: ", paste(dimension_id, collapse = "."))) } + # Assumes that dimensions are listed in the order of their positions + # If there is an example to the contrary somewhere, this should be changed filter_string <- "" for (i in seq_along(dimension_df$dimension_id_upper)){ if (dimension_df$dimension_id_upper[i] %in% filter_names) { @@ -149,7 +165,10 @@ data_filtering_on_dimension <- function(api_base_uri, id, filters) { return(filter_string) } -map_dimensions <- function(api_base_uri, id) { +get_codelist_id <- function(agency, id) { + + api_base_uri <- build_api_base_uri(agency) + data_structure_definition_url <- paste0( api_base_uri, "/sdmx/2.1/datastructure/estat/", @@ -188,10 +207,14 @@ map_dimensions <- function(api_base_uri, id) { # # } -label_eurostat_sdmx <- function(x, api_base_uri, resourceID, id, agencyID, lang = "en") { +label_eurostat_sdmx <- function(x, agency, id, lang = "en") { # how many columns there are that can be labeled with a codelist - dimension_df <- map_dimensions(api_base_uri = api_base_uri, id = id) + dimension_df <- get_codelist_id(agency = agency, id = id) resource <- "codelist" + + api_base_uri <- build_api_base_uri(agency) + agencyID <- build_agencyID(agency) + agencyID <- agencyID for (i in seq_len(nrow(dimension_df))) { resourceID <- dimension_df$codelist_id[[i]] From a0f5eb2baccb42d0aa3564413a8f7a0278038e5a Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Fri, 19 Jan 2024 17:39:32 +0200 Subject: [PATCH 04/62] Unify get_eurostat_sdmx with get_eurostat, address legacy output concerns --- R/get_eurostat.R | 8 ++++++ R/get_eurostat_sdmx.R | 55 ++++++++++++++++++++++++++++++++-------- man/get_eurostat.Rd | 5 ++++ man/get_eurostat_sdmx.Rd | 21 ++++++++++++--- 4 files changed, 75 insertions(+), 14 deletions(-) diff --git a/R/get_eurostat.R b/R/get_eurostat.R index 013fea38..98ee791e 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -65,6 +65,9 @@ #' can not be used with a `filters`. #' @param use.data.table Use faster data.table functions? Default is FALSE. #' On Windows requires that RTools is installed. +#' @param legacy.data.output Use legacy column names and data object structure. +#' Default is FALSE. If TRUE, the object will try to emulate the conventions of +#' eurostat package 3.7.x and earlier. #' @inheritDotParams get_eurostat_json #' #' @inherit eurostat-package references @@ -186,6 +189,7 @@ get_eurostat <- function(id, stringsAsFactors = FALSE, keepFlags = FALSE, use.data.table = FALSE, + legacy.data.output = FALSE, ...) { # Check if you have access to ec.europe.eu. @@ -463,6 +467,10 @@ get_eurostat <- function(id, saveRDS(y, file = cache_file, compress = compress_file) message("Table ", id, " cached at ", path.expand(cache_file)) } + + if (legacy.data.output){ + y <- legacy_data_format(y) + } y } diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 21206ac8..98ec7115 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -18,14 +18,15 @@ #' @export get_eurostat_sdmx <- function( id, - time_format, + time_format = "date", filters = NULL, - type, - lang, + type = "code", + lang = "en", use.data.table, agency = "Eurostat", compressed = TRUE, - label = FALSE + keepFlags = FALSE, + legacy.data.output = FALSE ) { # Check if you have access to ec.europe.eu. @@ -38,6 +39,8 @@ get_eurostat_sdmx <- function( # nocov end } + lang <- check_lang(lang) + agency <- tolower(agency) # agencyID <- switch( @@ -91,26 +94,55 @@ get_eurostat_sdmx <- function( curl::curl_download(url = url, destfile = tfile) - x <- read.csv(tfile, colClasses = "character") + dat <- read.csv(tfile, colClasses = "character") + if (!keepFlags) { + col_names <- names(dat) + dat <- dat[setdiff(col_names, "OBS_FLAG")] + } # return(references_resolution(api_base_uri, resource = "datastructure", agencyID = agencyID, resourceID = id)) - if (label){ - x <- label_eurostat_sdmx(x = x, + if (identical(type, "label")){ + dat <- label_eurostat_sdmx(x = dat, agency = agency, - resourceID = "codelist", - id = id) + id = id, + lang = lang) } - x + dat$TIME_PERIOD <- convert_time_col(x = dat$TIME_PERIOD, + time_format = time_format) + + dat$OBS_VALUE <- as.numeric(dat$OBS_VALUE) + + if (legacy.data.output) { + dat <- legacy_data_format(dat) + } + + dat } +legacy_data_format <- function(x, cols_to_drop = c("DATAFLOW", "LAST.UPDATE", "freq")) { + x <- x[setdiff(names(x), cols_to_drop)] + cols_to_rename <- data.frame(old = c("TIME_PERIOD", "OBS_VALUE"), + new = c("time", "values")) + for (i in seq_len(nrow(cols_to_rename))) { + old <- cols_to_rename[i,1] + if (old %in% names(x)) { + col_num <- which(names(x) == old) + colnames(x)[col_num] <- cols_to_rename[i,2] + } + } + x +} + build_api_base_uri <- function(agency) { + agency <- tolower(agency) api_base_uri <- switch( agency, eurostat = "https://ec.europa.eu/eurostat/api/dissemination", eurostat_comext = "https://ec.europa.eu/eurostat/api/comext/dissemination", + eurostat_prodcom = "https://ec.europa.eu/eurostat/api/comext/dissemination", comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") @@ -119,10 +151,12 @@ build_api_base_uri <- function(agency) { } build_agencyID <- function(agency) { + agency <- tolower(agency) agencyID <- switch( agency, eurostat = "ESTAT", eurostat_comext = "ESTAT", + eurostat_prodcom = "ESTAT", comp = "COMP", empl = "EMPL", grow = "GROW" @@ -211,6 +245,7 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en") { # how many columns there are that can be labeled with a codelist dimension_df <- get_codelist_id(agency = agency, id = id) resource <- "codelist" + lang <- check_lang(lang) api_base_uri <- build_api_base_uri(agency) agencyID <- build_agencyID(agency) diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index eada9cc8..e916c19a 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -18,6 +18,7 @@ get_eurostat( stringsAsFactors = FALSE, keepFlags = FALSE, use.data.table = FALSE, + legacy.data.output = FALSE, ... ) } @@ -86,6 +87,10 @@ can not be used with a \code{filters}.} \item{use.data.table}{Use faster data.table functions? Default is FALSE. On Windows requires that RTools is installed.} +\item{legacy.data.output}{Use legacy column names and data object structure. +Default is FALSE. If TRUE, the object will try to emulate the conventions of +eurostat package 3.7.x and earlier.} + \item{...}{ Arguments passed on to \code{\link[=get_eurostat_json]{get_eurostat_json}} \describe{ diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index 8a8c5da6..4c1807c3 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -6,14 +6,15 @@ \usage{ get_eurostat_sdmx( id, - time_format, + time_format = "date", filters = NULL, - type, - lang, + type = "code", + lang = "en", use.data.table, agency = "Eurostat", compressed = TRUE, - label = FALSE + keepFlags = FALSE, + legacy.data.output = FALSE ) } \arguments{ @@ -51,6 +52,18 @@ On Windows requires that RTools is installed.} \item{agency}{Either "Eurostat" (default), "Eurostat_comext" (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW"} + +\item{keepFlags}{a logical whether the flags (e.g. "confidential", +"provisional") should be kept in a separate column or if they +can be removed. Default is \code{FALSE}. For flag values see: +\url{https://ec.europa.eu/eurostat/data/database/information}. +Also possible non-real zero "0n" is indicated in flags column. +Flags are not available for eurostat API, so \code{keepFlags} +can not be used with a \code{filters}.} + +\item{legacy.data.output}{Use legacy column names and data object structure. +Default is FALSE. If TRUE, the object will try to emulate the conventions of +eurostat package 3.7.x and earlier.} } \description{ Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} From 78fb2360a3632f0add33b3e22b82981a5ca46c6b Mon Sep 17 00:00:00 2001 From: ake <40662956+ake123@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:07:07 +0200 Subject: [PATCH 05/62] metadata handling (#296) * medtadata handling * medtadata handling * metadata handling * metadata handling * metadata handling * metadata handling * metadata handling * metadata handling * metadata handling * metadata handling * metadata handling * metadata handling * changes * changes * changes * changes --- R/get_eurostat_json.R | 5 +-- R/get_eurostat_sdmx.R | 92 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/R/get_eurostat_json.R b/R/get_eurostat_json.R index 9080012a..0a001fc4 100755 --- a/R/get_eurostat_json.R +++ b/R/get_eurostat_json.R @@ -183,7 +183,6 @@ get_eurostat_json <- function(id, result <- httr2::resp_body_json( resp = resp, simplifyVector = TRUE) - if (httr2::resp_is_error(resp)) { # These objects are only needed if there is an error @@ -203,10 +202,10 @@ get_eurostat_json <- function(id, " Error id: {id} ({faultstring})\n", " Error label from API: {label}") ) - } else { + } else{ stop(stringr::str_glue("\n", "HTTP status: {status} ({status_code_label})\n", - " Error id: {id} ({faultstring})\n", + " Error id: {id} \n", " Error label from API: {label}") ) } diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 98ec7115..0d61b084 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -122,6 +122,98 @@ get_eurostat_sdmx <- function( } +extract_metadata <- function(id, agency = "Eurostat") { + + api_base_uri <- build_api_base_uri(agency) + + data_structure_definition_url <- paste0( + api_base_uri, + "/sdmx/2.1/dataflow/estat/", + id) + + dsd_xml <- xml2::read_xml(data_structure_definition_url) + + # Define namespaces + namespaces <- xml2::xml_ns(dsd_xml) + + # Extract Header information + header <- xml2::xml_find_first(dsd_xml, ".//m:Header", namespaces) + header_id <- xml2::xml_text(xml2::xml_find_first(header, ".//m:ID", namespaces)) + prepared <- substr(xml2::xml_text(xml2::xml_find_first(header, ".//m:Prepared", namespaces)),1,10) + sender_id <- xml2::xml_attr(xml2::xml_find_first(header, ".//m:Sender", namespaces), "id") + + # Continue with dataflow and annotations extraction + dataflow <- xml2::xml_find_first(dsd_xml, ".//s:Dataflow", namespaces) + dataflow_id <- xml2::xml_attr(dataflow, "id") + urn <- xml2::xml_attr(dataflow, "urn") + agencyID <- xml2::xml_attr(dataflow, "agencyID") + version <- xml2::xml_attr(dataflow, "version") + isFinal <- xml2::xml_attr(dataflow, "isFinal") + # Extract names in different languages + # Extract names in different languages independently + name_de <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='de']", namespaces)) + name_en <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='en']", namespaces)) + name_fr <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='fr']", namespaces)) + + source_institutions <- list() + doi_details <- NULL + + annotations_nodes <- xml2::xml_find_all(dataflow, ".//c:Annotation", namespaces) + for (node in annotations_nodes) { + title <- xml2::xml_text(xml2::xml_find_first(node, ".//c:AnnotationTitle", namespaces)) + type <- xml2::xml_text(xml2::xml_find_first(node, ".//c:AnnotationType", namespaces)) + texts_nodes <- xml2::xml_find_all(node, ".//c:AnnotationText", namespaces) + + + # Assign specific annotations based on type + if (type == "OBS_PERIOD_OVERALL_LATEST") { + latest_period_timestamp <- title # Directly store the latest period timestamp + } else if (type == "OBS_PERIOD_OVERALL_OLDEST") { + oldest_period_timestamp <- title + } else if (type == "UPDATE_DATA") { + update_data_timestamp <- title + } else if(type == "SOURCE_INSTITUTIONS"){ + for (text_node in texts_nodes) { + lang <- xml2::xml_attr(text_node, "xml:lang") + text <- xml2::xml_text(text_node) + source_institutions[[lang]] <- text + + } + + } + } + + # Extract DOI URL if the annotation contains adms:Identifier + if (grepl("adms:Identifier", title)) { + title_xml <- xml2::read_xml(title) + doi_url <- xml2::xml_attr(xml2::xml_find_first(title_xml, ".//adms:Identifier"), "rdf:about", xml2::xml_ns(title_xml)) + } + + + metadata <- list( + Name_EN = name_en, + Name_DE = name_de, + Name_FR = name_fr, + DOI_URL = doi_url, + DataflowID = dataflow_id, + AgencyID = agencyID, + ID = header_id, + Prepared = prepared, + SenderID = sender_id, + OldestPeriodTimestamp = oldest_period_timestamp, + LatestPeriodTimestamp = latest_period_timestamp, + UpdateDataTimestamp = update_data_timestamp, + SourceInstitutions = source_institutions, + URN = urn, + Version = version, + IsFinal = isFinal + ) + + return(metadata) + +} + + legacy_data_format <- function(x, cols_to_drop = c("DATAFLOW", "LAST.UPDATE", "freq")) { x <- x[setdiff(names(x), cols_to_drop)] cols_to_rename <- data.frame(old = c("TIME_PERIOD", "OBS_VALUE"), From beb5efecaa44dd2f1269016a581a38dd4c258e14 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 29 Apr 2024 17:04:27 +0300 Subject: [PATCH 06/62] New functionalities, local file reading etc --- DESCRIPTION | 8 +- NAMESPACE | 2 + NEWS.md | 8 ++ R/eurostat-package.R | 2 +- R/get_eurostat.R | 4 +- R/get_eurostat_local.R | 118 ++++++++++++++++++++++++ R/get_eurostat_sdmx.R | 167 +++++++++++++++++++++++----------- R/tidy_eurostat.R | 123 ++++++++++++++++++++++++- man/eurostat-package.Rd | 4 +- man/eurotime2date.Rd | 2 +- man/get_eurostat.Rd | 6 +- man/get_eurostat_dic.Rd | 2 +- man/get_eurostat_json.Rd | 2 +- man/get_eurostat_local.Rd | 70 ++++++++++++++ man/get_eurostat_raw.Rd | 2 +- man/get_eurostat_sdmx.Rd | 4 +- man/get_eurostat_toc.Rd | 2 +- man/search_eurostat.Rd | 2 +- man/tidy_eurostat.Rd | 6 +- man/tidy_eurostat_sdmx_csv.Rd | 88 ++++++++++++++++++ 20 files changed, 542 insertions(+), 80 deletions(-) create mode 100644 R/get_eurostat_local.R create mode 100644 man/get_eurostat_local.Rd create mode 100644 man/tidy_eurostat_sdmx_csv.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 15fb80c3..0547843e 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: eurostat Title: Tools for Eurostat Open Data -Version: 4.1.0.9000 -Date: 2024-01-18 +Version: 4.1.0.9001 +Date: 2024-04-29 Authors@R: c( person("Leo", "Lahti", , "leo.lahti@iki.fi", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5537-637X")), @@ -18,7 +18,7 @@ Authors@R: c( person("Paul", "Rougieux", role = "ctb"), person("Anna", "Vasylytsya", role = "ctb"), person("Oliver", "Reiter", role = "ctb"), - person("Pyry", "Kantanen", role = "ctb", + person("Pyry", "Kantanen", role = "aut", comment = c(ORCID = "0000-0003-2853-2765")), person("Enrico", "Spinielli", , "enrico.spinielli@gmail.com", role = "ctb", comment = c(ORCID = "0000-0001-8584-9131")) @@ -71,6 +71,6 @@ MailingList: rOpenGov NeedsCompilation: no Repository: CRAN Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.1 X-schema.org-isPartOf: http://ropengov.org/ X-schema.org-keywords: ropengov diff --git a/NAMESPACE b/NAMESPACE index a5278172..4c509c1b 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -14,6 +14,7 @@ export(get_eurostat_folder) export(get_eurostat_geospatial) export(get_eurostat_interactive) export(get_eurostat_json) +export(get_eurostat_local) export(get_eurostat_sdmx) export(get_eurostat_toc) export(grepEurostatTOC) @@ -41,6 +42,7 @@ importFrom(data.table,.SD) importFrom(data.table,fread) importFrom(data.table,melt) importFrom(data.table,setDT) +importFrom(data.table,setcolorder) importFrom(digest,digest) importFrom(dplyr,"%>%") importFrom(dplyr,case_when) diff --git a/NEWS.md b/NEWS.md index 22c8436a..630dc3ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +# eurostat 4.1.0.9000 + +## Major updates + +* Add new function `get_eurostat_sdmx()` for implementing functionalities that are specifically targeted for the new SDMX API. It is accompanied by other helper functions that are related to fetching metadata from the SDMX API: `build_api_base_uri()`, `build_agencyID()`, `data_filtering_on_dimension()`, `get_codelist_id()` and `label_eurostat_sdmx()` +* Add new function `get_eurostat_local()` for reading local SDMX-CSV (.csv) and compressed SDMX-CSV (.csv.gz) files downloaded manually from the Eurostat website. This function relies only on `data.table` functions as it is expected that locally stored files may be larger and require more efficient handling with regards to RAM and processing time constrains. `tidy_eurostat_sdmx()` and `parse_filename()` are helper functions related to this function. +* New `legacy_data_format()` function and `legacy.data.output` attribute in `get_eurostat()` and `get_eurostat_local()` transforms modern API naming conventions into the format that was used in the old API and eurostat package versions 3.8.* and prior. It is a helper function to other functions. + # eurostat 4.0.0 ## Major updates diff --git a/R/eurostat-package.R b/R/eurostat-package.R index 2a0c0368..211bda40 100755 --- a/R/eurostat-package.R +++ b/R/eurostat-package.R @@ -339,4 +339,4 @@ #' regions package are imported . #' @keywords package #' @seealso `help("regions")`, -NULL +"_PACKAGE" diff --git a/R/get_eurostat.R b/R/get_eurostat.R index 98ee791e..b1ccb80c 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -66,8 +66,8 @@ #' @param use.data.table Use faster data.table functions? Default is FALSE. #' On Windows requires that RTools is installed. #' @param legacy.data.output Use legacy column names and data object structure. -#' Default is FALSE. If TRUE, the object will try to emulate the conventions of -#' eurostat package 3.7.x and earlier. +#' Default is FALSE. If TRUE, the object will try to emulate the naming +#' conventions of eurostat package version 3.7.x and earlier. #' @inheritDotParams get_eurostat_json #' #' @inherit eurostat-package references diff --git a/R/get_eurostat_local.R b/R/get_eurostat_local.R new file mode 100644 index 00000000..24578eb4 --- /dev/null +++ b/R/get_eurostat_local.R @@ -0,0 +1,118 @@ +#' @title Read Local SDMX-CSV files +#' @description Read compressed or uncompressed SDMX-CSV files +#' @details +#' This implementation is experimental. It uses only data.table methods to read +#' and wrangle data files. +#' +#' Download datasets in sdmx-csv format from [https://ec.europa.eu/eurostat/databrowser/bulk?lang=en] +#' +#' @inheritParams get_eurostat +#' @importFrom data.table fread +#' @export + +get_eurostat_local <- function(file, + time_format = "date", + type = "code", + lang = "en", + select_time = NULL, + stringsAsFactors = FALSE, + keepFlags = FALSE, + legacy.data.output = FALSE) { + file_attrs <- parse_filename(file) + + if (file_attrs$filetype == "csv.gz"){ + # decompress and read + # dat <- readr::read_csv(gzfile(file), + # na = "", + # progress = TRUE, + # col_types = readr::cols(.default = readr::col_character()) + # ) + dat <- data.table::fread(file = file, + na.strings = "", + header = TRUE, + colClasses = "character", + stringsAsFactors = stringsAsFactors) + } else if (file_attrs$filetype == "csv") { + # read + dat <- data.table::fread(file = file, + na.strings = "", + header = TRUE, + colClasses = "character", + stringsAsFactors = stringsAsFactors) + } else { + stop("Invalid file") + } + + # For better code clarity, use only NULL in code + if (is.character(filters) && identical(tolower(filters), "none")) { + filters <- NULL + } else if (is.character(filters) && !identical(tolower(filters), "null")) { + message("Non-standard filters argument. Using argument 'filters = NULL'") + filters <- NULL + } + + # Inform user with message if keepFlags == TRUE cannot be delivered + if (keepFlags && !is.null(filters)) { + message("The keepFlags argument of the get_eurostat function + can be used only without filters. No Flags returned.") + keepFlags <- FALSE + } + + # Sanity check + type <- tolower(type) + time_format <- tolower(time_format) + lang <- check_lang(lang) + + y <- tidy_eurostat_sdmx_csv( + dat, + time_format, + select_time, + keepFlags = keepFlags + ) + + if (identical(type, "code")) { + # do nothing + # y <- y + } else if (identical(type, "label")) { + agency <- build_agencyID(file_attrs$agency) + y <- label_eurostat_sdmx(y, lang, id = file_attrs$code, agency = agency) + } else if (identical(type, "both")) { + stop(paste("type = \"both\" can be only used with JSON API.", + "Set filters argument")) + } else { + stop("Invalid type.") + } + + if (legacy.data.output) { + y <- legacy_data_format(y) + } + + y <- tibble::as_tibble(y) + return(y) +} + +parse_filename <- function(file){ + string = file + # If file path contains folders, or slashes + if (grepl("/", string)){ + # Find last instance of "/" + last_slash_index <- max(unlist(gregexpr("/", string))) + string <- substr(string, start = last_slash_index + 1, stop = nchar(string)) + } + # Initialize list object to return + x <- list() + split_string <- unlist(strsplit(string, "_")) + # Get agency name + x$agency <- split_string[1] + + # Get ID + x$code <- paste(split_string[2:(length(split_string)-1)], collapse = "_") + + # Get file type and language + filetype_and_language <- unlist(strsplit(split_string[length(split_string)], "\\.")) + x$lang <- filetype_and_language[1] + x$filetype <- paste(filetype_and_language[2:length(filetype_and_language)], collapse = ".") + + return(x) + +} \ No newline at end of file diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 98ec7115..fcf20edd 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -122,15 +122,34 @@ get_eurostat_sdmx <- function( } -legacy_data_format <- function(x, cols_to_drop = c("DATAFLOW", "LAST.UPDATE", "freq")) { - x <- x[setdiff(names(x), cols_to_drop)] - cols_to_rename <- data.frame(old = c("TIME_PERIOD", "OBS_VALUE"), - new = c("time", "values")) - for (i in seq_len(nrow(cols_to_rename))) { - old <- cols_to_rename[i,1] - if (old %in% names(x)) { - col_num <- which(names(x) == old) - colnames(x)[col_num] <- cols_to_rename[i,2] +legacy_data_format <- function(x, cols_to_drop = c("DATAFLOW", "LAST UPDATE", "freq")) { + if (inherits(x, "data.table")) { + # Drop columns that were not used in old API + for (i in seq_along(cols_to_drop)) { + if (cols_to_drop[i] %in% names(x)){ + x[, (cols_to_drop[i]):=NULL] + } + } + + # Rename columns + non_legacy_col_name = c("TIME_PERIOD", "OBS_VALUE", "OBS_FLAG") + legacy_col_name = c("time", "values", "flags") + for (i in seq_along(non_legacy_col_name)) { + if (non_legacy_col_name[i] %in% names(x)){ + data.table::setnames(x, (non_legacy_col_name[i]), (legacy_col_name[i])) + } + } + + } else { + x <- x[setdiff(names(x), cols_to_drop)] + cols_to_rename <- data.frame(non_legacy_col_name = c("TIME_PERIOD", "OBS_VALUE", "OBS_FLAG"), + legacy_col_name = c("time", "values", "flags")) + for (i in seq_len(nrow(cols_to_rename))) { + non_legacy_col_name <- cols_to_rename[i,1] + if (non_legacy_col_name %in% names(x)) { + col_num <- which(names(x) == non_legacy_col_name) + colnames(x)[col_num] <- cols_to_rename[i,2] + } } } x @@ -141,8 +160,11 @@ build_api_base_uri <- function(agency) { api_base_uri <- switch( agency, eurostat = "https://ec.europa.eu/eurostat/api/dissemination", + estat = "https://ec.europa.eu/eurostat/api/dissemination", eurostat_comext = "https://ec.europa.eu/eurostat/api/comext/dissemination", + comext = "https://ec.europa.eu/eurostat/api/comext/dissemination", eurostat_prodcom = "https://ec.europa.eu/eurostat/api/comext/dissemination", + prodcom = "https://ec.europa.eu/eurostat/api/comext/dissemination", comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") @@ -155,8 +177,11 @@ build_agencyID <- function(agency) { agencyID <- switch( agency, eurostat = "ESTAT", + estat = "ESTAT", eurostat_comext = "ESTAT", + comext = "ESTAT", eurostat_prodcom = "ESTAT", + prodcom = "ESTAT", comp = "COMP", empl = "EMPL", grow = "GROW" @@ -205,7 +230,7 @@ get_codelist_id <- function(agency, id) { data_structure_definition_url <- paste0( api_base_uri, - "/sdmx/2.1/datastructure/estat/", + "/sdmx/2.1/datastructure/ESTAT/", id) dsd <- xml2::read_xml(data_structure_definition_url) @@ -247,42 +272,16 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en") { resource <- "codelist" lang <- check_lang(lang) + # non-destructive editing + y <- x api_base_uri <- build_api_base_uri(agency) agencyID <- build_agencyID(agency) agencyID <- agencyID - for (i in seq_len(nrow(dimension_df))) { - resourceID <- dimension_df$codelist_id[[i]] - message(paste("Building codelist URL for resourceID:", resourceID)) - codelist_url <- paste0( - api_base_uri, - "/sdmx/2.1/", - resource, - "/", - agencyID, - "/", - resourceID, - "?format=TSV", - "&lang=", - lang - ) - tryCatch({ - codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc", col_names = FALSE)) - column_to_handle <- dimension_df$dimension_id[[i]] - col <- x[[column_to_handle]] - codes_to_label <- codelist[(codelist[,1] %in% col),] - message(paste("Labeling dimension (column):", column_to_handle)) - for (j in seq_len(nrow(codes_to_label))) { - col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) - } - x[column_to_handle] <- col - }, - error = function(e) message(paste("Couldn't label", resourceID)) - ) - } - if ("OBS_FLAG" %in% names(x)){ - tryCatch({ - resourceID <- "OBS_FLAG" + # data.table objects need different kind of handling + if (inherits(y, "data.table")) { + for (i in seq_len(nrow(dimension_df))) { + resourceID <- dimension_df$codelist_id[[i]] message(paste("Building codelist URL for resourceID:", resourceID)) codelist_url <- paste0( api_base_uri, @@ -296,18 +295,78 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en") { "&lang=", lang ) - codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc")) - column_to_handle <- "OBS_FLAG" - col <- x[[column_to_handle]] - codes_to_label <- codelist[(codelist[,1] %in% col),] - message(paste("Labeling dimension (column):", column_to_handle)) - for (j in seq_len(nrow(codes_to_label))) { - col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) - } - x[column_to_handle] <- col - }, - error = function(e) message(paste("Couldn't label", resourceID)) - ) + tryCatch({ + codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc", col_names = FALSE)) + column_to_handle <- dimension_df$dimension_id[[i]] + codes_to_label <- unique(y[[column_to_handle]]) + codelist_subset <- codelist[which(codelist[,1] %in% codes_to_label),] + message(paste("Labeling dimension (column):", column_to_handle)) + for (j in seq_len(nrow(codelist_subset))) { + data.table::set(y, i=which(y[[column_to_handle]] == codelist_subset[j,1]), j = column_to_handle, value = codelist_subset[j,2]) + } + }, + error = function(e) message(paste("Couldn't label", resourceID)) + ) + } + } else { + for (i in seq_len(nrow(dimension_df))) { + resourceID <- dimension_df$codelist_id[[i]] + message(paste("Building codelist URL for resourceID:", resourceID)) + codelist_url <- paste0( + api_base_uri, + "/sdmx/2.1/", + resource, + "/", + agencyID, + "/", + resourceID, + "?format=TSV", + "&lang=", + lang + ) + tryCatch({ + codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc", col_names = FALSE)) + column_to_handle <- dimension_df$dimension_id[[i]] + col <- x[[column_to_handle]] + codes_to_label <- codelist[(codelist[,1] %in% col),] + message(paste("Labeling dimension (column):", column_to_handle)) + for (j in seq_len(nrow(codes_to_label))) { + col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) + } + x[column_to_handle] <- col + }, + error = function(e) message(paste("Couldn't label", resourceID)) + ) + } + if ("OBS_FLAG" %in% names(x)){ + tryCatch({ + resourceID <- "OBS_FLAG" + message(paste("Building codelist URL for resourceID:", resourceID)) + codelist_url <- paste0( + api_base_uri, + "/sdmx/2.1/", + resource, + "/", + agencyID, + "/", + resourceID, + "?format=TSV", + "&lang=", + lang + ) + codelist <- as.data.frame(readr::read_tsv(file = codelist_url, col_types = "cc")) + column_to_handle <- "OBS_FLAG" + col <- x[[column_to_handle]] + codes_to_label <- codelist[(codelist[,1] %in% col),] + message(paste("Labeling dimension (column):", column_to_handle)) + for (j in seq_len(nrow(codes_to_label))) { + col[which(col == as.character(codes_to_label[j,][1]))] <- as.character(codes_to_label[j,2]) + } + x[column_to_handle] <- col + }, + error = function(e) message(paste("Couldn't label", resourceID)) + ) + } } x } \ No newline at end of file diff --git a/R/tidy_eurostat.R b/R/tidy_eurostat.R index ee3e9aa9..92dad346 100755 --- a/R/tidy_eurostat.R +++ b/R/tidy_eurostat.R @@ -1,5 +1,5 @@ -#' @title Transform Data into Row-Column-Value Format -#' @description Transform raw Eurostat data table downloaded from the +#' @title Transform TSV files into Row-Column-Value Format +#' @description Transform raw Eurostat TSV files downloaded from the #' API into a tidy row-column-value format (RCV). #' @param dat #' a data_frame from [get_eurostat_raw()]. @@ -78,7 +78,7 @@ tidy_eurostat <- function(dat, dat$values <- as.numeric(gsub("[^0-9.-]+", "", as.character(dat$values))) # variable columns - var_cols <- names(dat)[!(names(dat) %in% c("TIME_PERIOD", "values"))] + var_cols <- setdiff(names(dat), c("TIME_PERIOD", "values")) # reorder to standard order # OLD CODE @@ -250,3 +250,120 @@ convert_time_col <- function(x, time_format) { } y } + +#' @title Transform CSV files into Row-Column-Value Format data.table object +#' @description Transform raw Eurostat TSV files downloaded from the +#' API into a tidy row-column-value format (RCV). +#' @details +#' Can read only SDMX-CSV files, in compressed (.csv.gz) and uncompressed (.csv) +#' format. As opposed to regular tidy_eurostat function, stringsAsFactors is +#' determined outside this function, in `get_eurostat_local()`. +#' +#' @param dat +#' a data_frame from [get_eurostat_raw()]. +#' @inheritParams get_eurostat +#' @return data.table object in the melted format with the last column 'values' or 'OBS_VALUE'. +#' @seealso [get_eurostat_local()] +#' @inherit eurostat-package references +#' @author Pyry Kantanen +#' +#' @importFrom stringi stri_extract_first_regex stri_replace_all_regex +#' @importFrom stringi stri_replace_all_fixed +#' @importFrom tidyr separate pivot_longer +#' @importFrom dplyr filter +#' @importFrom data.table setDT melt .SD := setcolorder +#' @importFrom stats na.omit +#' +#' +#' @keywords internal utilities database +tidy_eurostat_sdmx_csv <- function(dat, + time_format = "date", + select_time = NULL, + keepFlags = FALSE) { + + dat <- dplyr::filter(dat, !is.na(OBS_VALUE)) + + if (keepFlags == FALSE) { + # Remove OBS_FLAG column + dat[,OBS_FLAG:=NULL] + } + + # clean time and values + dat$TIME_PERIOD <- gsub("X", "", dat$TIME_PERIOD, fixed = TRUE) + dat$OBS_VALUE <- as.numeric(gsub("[^0-9.-]+", "", as.character(dat$OBS_VALUE))) + + # variable columns + var_cols <- setdiff(names(dat), c("TIME_PERIOD", "OBS_VALUE")) + + # reorder to standard order + data.table::setcolorder(dat, c(var_cols, "TIME_PERIOD", "OBS_VALUE")) + + # columns from var_cols are converted into factors + # avoid convert = FALSE since it converts T into TRUE instead of TOTAL + # not needed in data.table + # if (stringsAsFactors) { + # dat[, var_cols] <- lapply( + # dat[, var_cols, drop = FALSE], + # function(x) factor(x, levels = unique(x)) + # ) + # } + + # For multiple time frequency + freqs <- unique(dat$freq) + + if (!is.null(select_time)) { + if (length(select_time) > 1) { + message( + "Selected multiple time frequencies with select_time parameter: ", + shQuote(select_time) + ) + } + + # Filter dataset according to select_time filter + # This only works when a single frequency is chosen + if (identical(select_time, "Y")) { + # Annual with old style notation, "Y" for annual + dat <- subset(dat, dat$freq == "A") + } else if (identical(select_time, "A")) { + # Annual with new notation, "A" for annual + dat <- subset(dat, dat$freq == "A") + } else { + # Others, subset the data with whatever choices + dat <- subset(dat, dat$freq %in% select_time) + } + # Test if filtered dataset actually contains any data + if (nrow(dat) == 0) { + stop( + "No data selected with select_time:", dQuote(select_time), "\n", + "Available frequencies: ", shQuote(freqs) + ) + } + } else { + + if (length(freqs) > 1 && time_format != "raw") { + message( + "Data includes several time frequencies. Select a single frequency \n", + "with select_time or use time_format = \"raw\" to return all data \n", + "without any filtering. Available frequencies: ", shQuote(freqs), "\n", + "Returning the dataset with multiple frequencies." + ) + } + } + + if (length(select_time) > 1) { + dat_copy <- data.frame() + for (i in seq_along(select_time)) { + dat_subset <- subset(dat, dat$freq == select_time[i]) + dat_subset$TIME_PERIOD <- convert_time_col(x = dat_subset$TIME_PERIOD, + time_format = time_format) + dat_copy <- rbind(dat_copy, dat_subset) + } + dat <- dat_copy + } else { + # convert time column to Date + dat$TIME_PERIOD <- convert_time_col(x = dat$TIME_PERIOD, + time_format = time_format) + } + + dat +} diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index 582634d5..528fb685 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -15,7 +15,7 @@ utilities. \tab \cr \strong{Package} \tab eurostat \cr \strong{Type} \tab Package \cr - \strong{Version} \tab 4.1.0.9000 \cr + \strong{Version} \tab 4.1.0.9001 \cr \strong{Date} \tab 2014-2024 \cr \strong{License} \tab BSD_2_clause + file LICENSE \cr \strong{LazyLoad} \tab yes \cr @@ -356,7 +356,7 @@ See \code{citation("eurostat")}: Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9000. + [Computer software]. R package version 4.1.0.9001. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/eurotime2date.Rd b/man/eurotime2date.Rd index 8f5ebe58..01e5fd33 100755 --- a/man/eurotime2date.Rd +++ b/man/eurotime2date.Rd @@ -54,7 +54,7 @@ See \code{citation("eurostat")}: # # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., # and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9000. +# [Computer software]. R package version 4.1.0.9001. # https://github.com/rOpenGov/eurostat # # To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index e916c19a..c33226b2 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -88,8 +88,8 @@ can not be used with a \code{filters}.} On Windows requires that RTools is installed.} \item{legacy.data.output}{Use legacy column names and data object structure. -Default is FALSE. If TRUE, the object will try to emulate the conventions of -eurostat package 3.7.x and earlier.} +Default is FALSE. If TRUE, the object will try to emulate the naming +conventions of eurostat package version 3.7.x and earlier.} \item{...}{ Arguments passed on to \code{\link[=get_eurostat_json]{get_eurostat_json}} @@ -382,7 +382,7 @@ See \code{citation("eurostat")}: Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9000. + [Computer software]. R package version 4.1.0.9001. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat_dic.Rd b/man/get_eurostat_dic.Rd index d31a2e7d..6307593c 100755 --- a/man/get_eurostat_dic.Rd +++ b/man/get_eurostat_dic.Rd @@ -44,7 +44,7 @@ See \code{citation("eurostat")}: # # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., # and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9000. +# [Computer software]. R package version 4.1.0.9001. # https://github.com/rOpenGov/eurostat # # To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 53d477c1..13037179 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -317,7 +317,7 @@ See \code{citation("eurostat")}: Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9000. + [Computer software]. R package version 4.1.0.9001. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat_local.Rd b/man/get_eurostat_local.Rd new file mode 100644 index 00000000..7023abdd --- /dev/null +++ b/man/get_eurostat_local.Rd @@ -0,0 +1,70 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_eurostat_local.R +\name{get_eurostat_local} +\alias{get_eurostat_local} +\title{Read Local SDMX-CSV files} +\usage{ +get_eurostat_local( + file, + time_format = "date", + type = "code", + lang = "en", + select_time = NULL, + stringsAsFactors = FALSE, + keepFlags = FALSE, + legacy.data.output = FALSE +) +} +\arguments{ +\item{time_format}{a string giving a type of the conversion of the time column from the +eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +with the date being the first day of the period. A "\code{date_last}" argument +converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +that the exact date is the last date of the period. Period can be year, +semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for +more information). +Argument "\code{num}" converts the date into a numeric (integer) meaning that +the first day of the year 2000 is close to 2000.01 and the last day of the +year is close to 2000.99 (see \code{\link[=eurotime2num]{eurotime2num()}} for more information). +Using the argument "\code{raw}" preserves the dates as they were in the original +Eurostat data.} + +\item{type}{A type of variables, "\code{code}" (default), "\code{label}" or "\code{both}". +The parameter "\code{both}" will return a data_frame with named vectors, +labels as values and codes as names.} + +\item{lang}{2-letter language code, default is "\code{en}" (English), other +options are "\code{fr}" (French) and "\code{de}" (German). Used for labeling datasets.} + +\item{select_time}{a character symbol for a time frequency or \code{NULL}, +which is used by default as most datasets have just one time +frequency. For datasets with multiple time +frequencies, select one or more of the desired frequencies with: +"Y" (or "A") = annual, "S" = semi-annual / semester, "Q" = quarterly, +"M" = monthly, "W" = weekly. For all frequencies in same data +frame \code{time_format = "raw"} should be used.} + +\item{stringsAsFactors}{if \code{TRUE} (the default) variables are converted to factors in the original +Eurostat order. If \code{FALSE} they are returned as strings.} + +\item{keepFlags}{a logical whether the flags (e.g. "confidential", +"provisional") should be kept in a separate column or if they +can be removed. Default is \code{FALSE}. For flag values see: +\url{https://ec.europa.eu/eurostat/data/database/information}. +Also possible non-real zero "0n" is indicated in flags column. +Flags are not available for eurostat API, so \code{keepFlags} +can not be used with a \code{filters}.} + +\item{legacy.data.output}{Use legacy column names and data object structure. +Default is FALSE. If TRUE, the object will try to emulate the naming +conventions of eurostat package version 3.7.x and earlier.} +} +\description{ +Read compressed or uncompressed SDMX-CSV files +} +\details{ +This implementation is experimental. It uses only data.table methods to read +and wrangle data files. + +Download datasets in sdmx-csv format from \link{https://ec.europa.eu/eurostat/databrowser/bulk?lang=en} +} diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index c3ea6770..be0ebded 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -124,7 +124,7 @@ See \code{citation("eurostat")}: # # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., # and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9000. +# [Computer software]. R package version 4.1.0.9001. # https://github.com/rOpenGov/eurostat # # To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index 4c1807c3..62b91375 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -62,8 +62,8 @@ Flags are not available for eurostat API, so \code{keepFlags} can not be used with a \code{filters}.} \item{legacy.data.output}{Use legacy column names and data object structure. -Default is FALSE. If TRUE, the object will try to emulate the conventions of -eurostat package 3.7.x and earlier.} +Default is FALSE. If TRUE, the object will try to emulate the naming +conventions of eurostat package version 3.7.x and earlier.} } \description{ Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index ecb2314e..3dc43a18 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -91,7 +91,7 @@ See \code{citation("eurostat")}: Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9000. + [Computer software]. R package version 4.1.0.9001. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index 7615b3ee..541f9de9 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -99,7 +99,7 @@ See \code{citation("eurostat")}: Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9000. + [Computer software]. R package version 4.1.0.9001. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/tidy_eurostat.Rd b/man/tidy_eurostat.Rd index cb39bf65..1f952cf1 100755 --- a/man/tidy_eurostat.Rd +++ b/man/tidy_eurostat.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/tidy_eurostat.R \name{tidy_eurostat} \alias{tidy_eurostat} -\title{Transform Data into Row-Column-Value Format} +\title{Transform TSV files into Row-Column-Value Format} \usage{ tidy_eurostat( dat, @@ -55,7 +55,7 @@ On Windows requires that RTools is installed.} tibble in the melted format with the last column 'values'. } \description{ -Transform raw Eurostat data table downloaded from the +Transform raw Eurostat TSV files downloaded from the API into a tidy row-column-value format (RCV). } \examples{ @@ -79,7 +79,7 @@ See \code{citation("eurostat")}: Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9000. + [Computer software]. R package version 4.1.0.9001. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/tidy_eurostat_sdmx_csv.Rd b/man/tidy_eurostat_sdmx_csv.Rd new file mode 100644 index 00000000..e327d3a3 --- /dev/null +++ b/man/tidy_eurostat_sdmx_csv.Rd @@ -0,0 +1,88 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tidy_eurostat.R +\name{tidy_eurostat_sdmx_csv} +\alias{tidy_eurostat_sdmx_csv} +\title{Transform CSV files into Row-Column-Value Format data.table object} +\usage{ +tidy_eurostat_sdmx_csv( + dat, + time_format = "date", + select_time = NULL, + keepFlags = FALSE +) +} +\arguments{ +\item{dat}{a data_frame from \code{\link[=get_eurostat_raw]{get_eurostat_raw()}}.} + +\item{time_format}{a string giving a type of the conversion of the time column from the +eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +with the date being the first day of the period. A "\code{date_last}" argument +converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +that the exact date is the last date of the period. Period can be year, +semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for +more information). +Argument "\code{num}" converts the date into a numeric (integer) meaning that +the first day of the year 2000 is close to 2000.01 and the last day of the +year is close to 2000.99 (see \code{\link[=eurotime2num]{eurotime2num()}} for more information). +Using the argument "\code{raw}" preserves the dates as they were in the original +Eurostat data.} + +\item{select_time}{a character symbol for a time frequency or \code{NULL}, +which is used by default as most datasets have just one time +frequency. For datasets with multiple time +frequencies, select one or more of the desired frequencies with: +"Y" (or "A") = annual, "S" = semi-annual / semester, "Q" = quarterly, +"M" = monthly, "W" = weekly. For all frequencies in same data +frame \code{time_format = "raw"} should be used.} + +\item{keepFlags}{a logical whether the flags (e.g. "confidential", +"provisional") should be kept in a separate column or if they +can be removed. Default is \code{FALSE}. For flag values see: +\url{https://ec.europa.eu/eurostat/data/database/information}. +Also possible non-real zero "0n" is indicated in flags column. +Flags are not available for eurostat API, so \code{keepFlags} +can not be used with a \code{filters}.} +} +\value{ +data.table object in the melted format with the last column 'values' or 'OBS_VALUE'. +} +\description{ +Transform raw Eurostat TSV files downloaded from the +API into a tidy row-column-value format (RCV). +} +\details{ +Can read only SDMX-CSV files, in compressed (.csv.gz) and uncompressed (.csv) +format. As opposed to regular tidy_eurostat function, stringsAsFactors is +determined outside this function, in \code{get_eurostat_local()}. +} +\references{ +See \code{citation("eurostat")}: + +\if{html}{\out{
}}\preformatted{Kindly cite the eurostat R package as follows: + + Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and + analysis of Eurostat open data with the eurostat package. The R + Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 + + Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., + and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9001. + https://github.com/rOpenGov/eurostat + +To see these entries in BibTeX format, use 'print(, +bibtex=TRUE)', 'toBibtex(.)', or set +'options(citation.bibtex.max=999)'. +}\if{html}{\out{
}} + +When citing data downloaded from Eurostat, see section "Citing Eurostat data" +in \code{\link[=get_eurostat]{get_eurostat()}} documentation. +} +\seealso{ +\code{\link[=get_eurostat_local]{get_eurostat_local()}} +} +\author{ +Pyry Kantanen +} +\keyword{database} +\keyword{internal} +\keyword{utilities} From 24da9db97db01ff9ae2c21d391c9487b7ef95d9c Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 29 Apr 2024 19:25:00 +0300 Subject: [PATCH 07/62] Update country datasets (fix issue #298) --- R/data_countries.R | 11 +++++++++-- data/ea_countries.rda | Bin 434 -> 781 bytes data/efta_countries.rda | Bin 245 -> 323 bytes data/eu_candidate_countries.rda | Bin 303 -> 608 bytes data/eu_countries.rda | Bin 521 -> 971 bytes man/eu_countries.Rd | 17 ++++++++++++----- 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/R/data_countries.R b/R/data_countries.R index 7d09d677..6af43ffa 100755 --- a/R/data_countries.R +++ b/R/data_countries.R @@ -2,9 +2,16 @@ #' @description Countries and country codes in EU, #' Euro area, EFTA and EU candidate countries. #' @format A data_frame: -#' * **code**: Country code in the Eurostat database. +#' * **code**: Country code in the Eurostat database (two-letter ISO code (ISO 3166 alpha-2) except in the case of Greece where EL is used). #' * **name**: Country name in English. -#' * **label**: Country name in the Eurostat database. +#' * **label**: Country name in the Eurostat database +#' * **name_fr**: Country name in French +#' * **name_de**: Country name in German +#' * **country_language**: Country name in national language(s). +#' +#' @details +#' Country codes are two-letter ISO codes (ISO 3166 alpha-2) except in the case +#' of Greece where EL is used instead of the standard ISO code. #' #' @family datasets #' @source , diff --git a/data/ea_countries.rda b/data/ea_countries.rda index 76ecea1d354d2f8934e9e179053f82ec23181a86..9ded67e0779f4b113a9f65fbb36cf57825337ac1 100644 GIT binary patch literal 781 zcmV+o1M>VrT4*^jL0KkKSgj^bh1m0-Y9eZF{7-=i(3~(N%&H+eIHO6SkgBNCW|*9fOkO4s3L z%eC8XSJh3m$ENQ`8C?Ag>JasPJk+pV{$cB#-Y?Ya6rvGGHsFE-u0DAMrLnD?ku0d8 zvEnC8e(S1Kl9YuM=hluw=?H!xve78{i20yH5?Bc#z5F=yg+=)o7G>A+0Up&1hy>3TBL6G2H}&C!&o(L}9kS*JM~XN7W|$0{j4RH~d- z;jWbHh0Ha=sRry!P;ruM+{12>R=&Odkr7JjFe$KTm_eE7BuR&#kkNffBQWbJAVB{i z7Txx!vEouU_JaL0z&>82LW&PKW&$kU@+a#&8Mb4@YDWyUm*P2<=*Xt!nqx-e*1K)? zNlg(w)y7slti*ar-&o*vJ@rd*ls*Z*(?3;4T| LDZ+$?et5>1DtU3K literal 434 zcmV;j0ZslwT4*^jL0KkKS+Y@dZ2$pk|H1$MNB}@#a3DXgUtquQ-{3$100IC3umP1+ zYJ{R9WNDKm+Mc7*Xa=5;*(206^i2ktqzNKa{Zq-L%|?NMfYTF9H8eEA0B91bY>foa z+D!le000001BBBg6(^j8tR#)8`e=%ej_=xll_uRTxF7;`Kupn9+>P(4wRv2x&519( z(xrl!)p#F7q{GT_4$)!k!mu$j4#UE@SY#mTNCAW<2X|y4)C5E!+i(DnW{FN=p22Guo4^8@i-F?zE)vr zK4K{;wi0r3@*D|AMKB66$;u94&T+zJCc?Qd4Gok~V+R5Op^+jO9V)`}AVfAGP~d^+ zmV0)Lax;uDMrk3tDU6`NUrVn9l(B|USZr5}hAiSmiG1IlaOpgz_=W^PI$9wVeOzT3 c3j#qy3l=~00E!?GH3t*Gy+L9Dtc;uiIi#TF#}BujT#zc$-UM# zq-Yk%RXDDAAy={iwS?R8>d&Z@$nvxlSXKJT(1m*;c}veloY?S#O~-`{!n{!);YJ}=ze8kL zq!>UgLS~C(D;XD??gDI5#|Zhph=AC^5*?dC10FXOMHG+N$1C4NwIVS_a^eT` zWs#A_EX6@u5^TCltC$l-gb8~~e%rVErrRE5Qb4L_Jk)J_9t%E6!VjgF0 Vq&9Lo^L`)V?ntK!5+3i!#{iKHh$a94 literal 245 zcmVFPe{Qr8ZZC=XcC%=GHIG4Kn9HfXbgu0k_5BPJvIjn z)+R%;*TU;y!$yMWySxCQFx#oFuCz~?F9YKrEu;s`5JM3R!UhmI&O@Gn;5qPkI}Co` zSliMT3A{@P?WWU|XQhN{-1ikwDw3NB*WOUZ&}os6GamjAKME|3Jci>;3IuFGaKI=) vNcNN*0t!dqZx}v9c!J|oC+IRRpn`!?NKAP!kU0LZLjEq~ig2MI=L%93-EL)| diff --git a/data/eu_candidate_countries.rda b/data/eu_candidate_countries.rda index 4c790ec4c69161eb1accdcfea16746d782a81249..99fa08c34eb8fd98f637d28af77950807c12ea32 100644 GIT binary patch literal 608 zcmV-m0-yatT4*^jL0KkKS#-tZZU6yn|NsC0`GH6QkN`f<9zehU|FB2^M%HR5c2@`h zU;rQh0D-^({2Hnlik_e{00000000000002f+MsDCpo)1T(2qoDG&BZ`BLvBy02%`U z7@7d#pa2>T27ti;G&Iv78X7dn0f-s^5-5Qh1w5w8Jx@(dHd9S9WNjeR)X;jGGI~G& z01c>uwCSY#A>z#vNoHyH_+n7`N>^+yFauE`HUkER#xyp@n_>jR3;{+kNCahJ zk)#r|6cpi9_z5e<19o}zN68Yl|7O;@>BC#BJD=UE#lY zF00HPs|9Hwd@*7GfFnU9(!C>5w3G%MCR0H)27i8OW;O*P59sPf!;mROn|1|J6_Hyu zJ#z($3(kR5P*sYA8-yB^cqt1t^^nsIT;(M02J$B-SOTM(#{8Uh@1he2LC7zbFUWbT z2XjhtY7upjMdgiI8il!P`SEnB8_iI~R_crcugJ6r^#mIphB3FGPyk@VPj=>(t+ika uHs71bq$gc~aCj-m&eibGLQ_(vFhEfKG)f{Q{on`${x0N-aG@aSi^$w2>i>KI literal 303 zcmV+~0nq+JT4*^jL0KkKSsaw$BLD!u|IGjL$pAnB5CA3*9zehEpP)bh00amC03ome z6-{9(YI>Se#Cn-csiQQWi1eO7jWqIYM8PSfpfof900003Mu94(MjDMUCWcHC5E%xB z6C5Imnv`*npn!Hj-B5^H*D*^aFbV2ey&%9Ahy=*Nl~wCjs_WrHv!XauKZrJGx+W0L763wHi|eHO!+V?eaL=l@EAXXw&4d->xYvAhbxx>cX0NjCl3R zJ&$GA4ZK54L}lu8*uHrzS59IedO8lHry5SC@*^GcbjB<$AQXAx@iKvRhuWanAU3kD zcEkyagbW&YjR-tdA)Ne_qp{e*!%Pxmm5^v0BYc8`>yiF&JHSHzF64@Ep&&Uaz(zsP BfTjQd diff --git a/data/eu_countries.rda b/data/eu_countries.rda index b9305db055f17bd24f45152c8cddd4d5b74c1def..4684146a11f8593d477945900af0baca665a8766 100644 GIT binary patch literal 971 zcmV;+12p_XT4*^jL0KkKSn`SfBgUcNC*I9@Ss2M-*CVG|L{O00RVJpAOai! z00B?~o?D&VS7&kCxV$9^G}36)6U?dVW~ZqAMv0@*FcZ=0WG03XdJPeiPf$t`qBK*` z3Vx(%rkZ+zr>USc%@ZJG000dDl+r1v@}8zm82|tP00001paJRt<3m8vrbe1H$P7#n z01W}4G{_7IkR*_#*rtuCrY4V2&;SO2$Y{_2000i>sx3kk5)~Mn0W9|dNVIKQV&@Y< zLf-{$7{Cs8Oh;BXXSCw*O%Lix0ThrPgpw(+lCcRa>T6qwjocg@FKVk{dlD`RmW=%i zhL7mbe=?VqRw5yzf-8E4ClaYSdQB#I)M?kZ2%8i}Axc81?2LMu?er2%>w;%#o?;Tb zLNlr$I*|`mJP`1tL8OIQFvAfH%r39L2)UfynEq2RJ&VKa?~#|B?`D~#1Ws#k-cMHP!Eh} zY3oTl5i69$RJ$G?BwmuZ8NPQ|itAgAi6eZ3qw*5co6$hLso1GVl;#-_hH5DinW~ya zg_`ih2OOrAq_ma1H?10^RZLgIbGGfeR4O8>D=Hb!6BFP%xT&T?4XVz@s}swxl&(lA zg-VgxNzNAvk>*z^KFX3WQjmNGlqP-ZN|p(cAclfyrEC}%X`bdSkp^Qht}3RkY)_F2 z#IZLu-?AXFM8j1@PBn$3RYaIUM;;*=j54BV-8JpyU~0^$v1m^eOgTbmA5R-6bxd_p zIR|?-fr3gEGB5SfaEZ4j4~XuN_fCwk5rGCP%$sccVv$moVR;RQ{7VG*l5{MRioUor zt+a}RKvXnb*V7}W<9bL)8qQKLV&+ogygc)8R-D?lI>h-%z$`M%C6x=|fZ42AkdRo` zsw`NfMIK|(27)d63i(H57}AACO!uF-9VO1GakiKXduUG-(rOTNWWxcS2QwC0&+H0ssI3zybR0 zyMSS;c`4}uqd))v03K6MPyhe{s*UPQo~97UGGb@|XblYkhychLB$_n!8fX9j00000 z0B8!TVgQPGG=o#gG#VI2Xn@ej1Il`Z0JV|}Za5fe0VMP)qs5ZWr+&5p0s1=EHV6QS z#Lxg>p5m`zW8m5ryy-Wfu_hoAo-NcTYaaoM#6~8Mqe7HbNxZE=S3F;C^jcqa$tJ@D z@J5{`s7wepNd$xh6ah+75K52$Njwl{Ktx0^mg3RgTCBV6kkb_mbwGgyg9a7|7`9^W zt(GspX|O@V1_+p{o)82%BtVAHneitYGYmD2z>FsE3Y-uN1!92eF|fvJOI5Gw@9xPg zFvov;XD8^HvS{pWYj_krKG~qCb*({C2qo9Gjm>JOt#S?xf4S3Vwv_X#yJ9eHrMcA1 z{=v}PwZPKBbFqAydK+xNt9B<7Q+);rx6mjtJ5;91f_gPN9whu|CuxI5cyZ3PnNt=; z4P~U1sZ^yxP(^FUebgpcu~J1yr%77u{&!0=-y~?r;48}bFxkA~bFr{7p=?!|d0mo| znL;vmLdP17X)>jS+ttFcp-YPbP)8zl9#&^6w@Y?utZQ;4R)(eOSFt0)c7MKTA^cs* L6yZWbL0!pY`K;p; diff --git a/man/eu_countries.Rd b/man/eu_countries.Rd index e7112547..8ad9bb7a 100755 --- a/man/eu_countries.Rd +++ b/man/eu_countries.Rd @@ -10,16 +10,19 @@ \format{ A data_frame: \itemize{ -\item \strong{code}: Country code in the Eurostat database. +\item \strong{code}: Country code in the Eurostat database (two-letter ISO code (ISO 3166 alpha-2) except in the case of Greece where EL is used). \item \strong{name}: Country name in English. -\item \strong{label}: Country name in the Eurostat database. +\item \strong{label}: Country name in the Eurostat database +\item \strong{name_fr}: Country name in French +\item \strong{name_de}: Country name in German +\item \strong{country_language}: Country name in national language(s). } -An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 19 rows and 3 columns. +An object of class \code{data.frame} with 20 rows and 6 columns. -An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 4 rows and 3 columns. +An object of class \code{data.frame} with 4 rows and 6 columns. -An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 7 rows and 3 columns. +An object of class \code{data.frame} with 9 rows and 6 columns. } \source{ \url{https://ec.europa.eu/eurostat/statistics-explained/index.php/Tutorial:Country_codes_and_protocol_order}, @@ -38,6 +41,10 @@ eu_candidate_countries Countries and country codes in EU, Euro area, EFTA and EU candidate countries. } +\details{ +Country codes are two-letter ISO codes (ISO 3166 alpha-2) except in the case +of Greece where EL is used instead of the standard ISO code. +} \seealso{ Other datasets: \code{\link{eurostat_geodata_60_2016}}, From c81f14bb77007ece075e35f8fa84f536e4de344c Mon Sep 17 00:00:00 2001 From: Aleksi Lahtinen Date: Tue, 10 Sep 2024 14:09:26 +0300 Subject: [PATCH 08/62] Fix a bug with get_eurostat_interactive (#307) --- R/get_eurostat.R | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/R/get_eurostat.R b/R/get_eurostat.R index b1ccb80c..9aea9255 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -688,7 +688,7 @@ get_eurostat_interactive <- function(code = NULL) { ) use.data.table_selection <- switch( - menu(choices = c("Do not use data.table functions (default", + menu(choices = c("Do not use data.table functions (default)", "Use data.table functions"), title = "Using data.table functions may help reduce time used in data processing and reduce RAM usage. It is advisable especially when dealing with large datasets.") + 1, FALSE, @@ -768,7 +768,7 @@ get_eurostat_interactive <- function(code = NULL) { } if (exists("eurostat_data")) { - print_code <- switch( + print_fixity <- switch( menu(choices = c("Yes", "No"), title = "Print dataset fixity checksum?") + 1, return(invisible()), @@ -776,7 +776,7 @@ get_eurostat_interactive <- function(code = NULL) { FALSE ) - if (print_code) { + if (print_fixity) { capture.output(cat("##### FIXITY CHECKSUM:\n\n"), file = tempfile_for_sinking, append = TRUE) capture.output(print(stringr::str_glue("Fixity checksum (md5) for dataset {code}: {eurostat:::fixity_checksum(eurostat_data, algorithm = 'md5')}")), file = tempfile_for_sinking, append = TRUE) capture.output(cat("\n"), file = tempfile_for_sinking, append = TRUE) @@ -784,10 +784,14 @@ get_eurostat_interactive <- function(code = NULL) { } if (exists("eurostat_data")) { - cat(readLines(tempfile_for_sinking), sep = "\n") + if (any(c(print_citation, print_code, print_fixity))) { + cat(readLines(tempfile_for_sinking), sep = "\n") + } return(eurostat_data) } else { - cat(readLines(tempfile_for_sinking), sep = "\n") + if (any(c(print_citation, print_code))) { + cat(readLines(tempfile_for_sinking), sep = "\n") + } return(invisible()) } # nocov end From dd76b755d47b3e5981840d4e638cb89a2bd1316d Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Thu, 5 Dec 2024 15:26:36 +0200 Subject: [PATCH 09/62] Fix for compression issue and missing DOI --- R/get_eurostat_sdmx.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 79a95a62..a35d0925 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -128,8 +128,9 @@ extract_metadata <- function(id, agency = "Eurostat") { data_structure_definition_url <- paste0( api_base_uri, - "/sdmx/2.1/dataflow/estat/", - id) + "/sdmx/2.1/dataflow/ESTAT/", + id, + "?compressed=false") dsd_xml <- xml2::read_xml(data_structure_definition_url) @@ -194,7 +195,7 @@ extract_metadata <- function(id, agency = "Eurostat") { Name_EN = name_en, Name_DE = name_de, Name_FR = name_fr, - DOI_URL = doi_url, + DOI_URL = ifelse(exists("doi_url"), eval(doi_url), NA_character_), DataflowID = dataflow_id, AgencyID = agencyID, ID = header_id, From ce4aa90981f107b9d9bd3a679890153ce0bd375d Mon Sep 17 00:00:00 2001 From: ake123 Date: Thu, 22 May 2025 11:11:48 +0300 Subject: [PATCH 10/62] Add support to Asynchronous API --- DESCRIPTION | 2 +- NAMESPACE | 2 + R/get_eurostat_async.R | 146 ++++++++++++++++++++++++++ R/get_eurostat_sdmx.R | 186 +++++++++++++++++++++------------ man/get_eurostat_async.Rd | 71 +++++++++++++ man/get_eurostat_geospatial.Rd | 13 ++- man/get_eurostat_json.Rd | 4 +- man/get_eurostat_sdmx.Rd | 4 +- 8 files changed, 355 insertions(+), 73 deletions(-) create mode 100644 R/get_eurostat_async.R create mode 100644 man/get_eurostat_async.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 0547843e..2bb83ac4 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -71,6 +71,6 @@ MailingList: rOpenGov NeedsCompilation: no Repository: CRAN Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 X-schema.org-isPartOf: http://ropengov.org/ X-schema.org-keywords: ropengov diff --git a/NAMESPACE b/NAMESPACE index 4c509c1b..13ddb6a3 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,6 +9,7 @@ export(eurotime2date) export(eurotime2num) export(get_bibentry) export(get_eurostat) +export(get_eurostat_async) export(get_eurostat_dic) export(get_eurostat_folder) export(get_eurostat_geospatial) @@ -37,6 +38,7 @@ importFrom(RefManageR,toBiblatex) importFrom(classInt,classIntervals) importFrom(countrycode,countrycode) importFrom(curl,curl_download) +importFrom(data.table,"%chin%") importFrom(data.table,":=") importFrom(data.table,.SD) importFrom(data.table,fread) diff --git a/R/get_eurostat_async.R b/R/get_eurostat_async.R new file mode 100644 index 00000000..bcb7ca8f --- /dev/null +++ b/R/get_eurostat_async.R @@ -0,0 +1,146 @@ +#' @title Get Asynchronous Eurostat SDMX Data by ID +#' +#' @description +#' Polls Eurostat's async API for the status of a submitted request and downloads the data when available. +#' +#' @inheritParams get_eurostat_sdmx +#' @param id Character. Async request ID from initial SDMX call. +#' @param wait Integer. Seconds between status checks. +#' @param max_wait Integer. Max time to wait in seconds. +#' @importFrom data.table %chin% +#' +#' @return A data.frame containing the SDMX-CSV data. +#' +#' @export +get_eurostat_async <- function( + id, + time_format = "date", + type = "code", + lang = "en", + use.data.table, + agency = "Eurostat", + keepFlags = FALSE, + legacy.data.output = FALSE, + wait = 1, + max_wait = 60, + compressed = TRUE +) { + stopifnot(nzchar(id)) + lang <- check_lang(lang) + agency <- tolower(agency) + api_base_uri <- build_api_base_uri(agency) + agencyID <- build_agencyID(agency) + + stopifnot(!is.null(api_base_uri)) + + status_url <- paste0(api_base_uri, "/1.0/async/status/", id) + data_url <- paste0(api_base_uri, "/1.0/async/data/", id, + "?format=SDMX-CSV", + if (compressed) "&compressed=true" else "&compressed=false") + + wait_times <- seq(wait, max_wait, by = wait) + + status_check <- function(t) { + Sys.sleep(wait) + res <- httr::GET(status_url) + content <- httr::content(res, as = "parsed", encoding = "UTF-8") + xml2::xml_text(xml2::xml_find_first(content, ".//status")) + } + + statuses <- vapply(wait_times, status_check, FUN.VALUE = character(1)) + + status_flags <- stats::setNames(seq_along(statuses), statuses) + available_pos <- status_flags["AVAILABLE"] + fail_pos <- which(statuses %in% c("EXPIRED", "ERROR", "UNKNOWN_REQUEST"))[1] + + outcome_index <- which(c(length(available_pos) > 0, length(fail_pos) > 0, TRUE))[1] + outcome <- c("success", "fail", "timeout")[outcome_index] + + message(sprintf("Final status after %d seconds: %s", wait_times[length(statuses)], outcome)) + + handlers <- list( + "success" = function() { + tfile <- tempfile() + on.exit(unlink(tfile), add = TRUE) + + # Enhanced download with retries and HTTP checks + download_attempt <- function(url, destfile, retries = 3, delay = 5) { + for (i in seq_len(retries)) { + res <- httr::GET(url) + if (httr::status_code(res) == 200) { + writeBin(httr::content(res, "raw"), destfile) + return(TRUE) + } + if (i < retries) { + message("Download failed (HTTP ", httr::status_code(res), "). Retrying...") + Sys.sleep(delay) + } + } + stop("Download failed after ", retries, " attempts. Last HTTP status: ", httr::status_code(res)) + } + + tryCatch( + download_attempt(data_url, tfile), + error = function(e) stop("Data download failed: ", e$message) + ) + + # Handle compressed/uncompressed content + if (compressed) { + is_zip <- identical(readBin(tfile, what = "raw", n = 2), charToRaw("PK")) + if (is_zip) { + zip_list <- unzip(tfile, list = TRUE) + if (nrow(zip_list) == 0) stop("Empty ZIP archive received") + csv_file <- unzip(tfile, exdir = tempdir())[1] + } else { + message("Expected ZIP archive, but received CSV instead — continuing anyway.") + csv_file <- tfile + } + } else { + csv_file <- tfile + } + on.exit(unlink(csv_file), add = TRUE) + + # Read data with data.table + dat <- data.table::fread(csv_file, colClasses = "character") + data.table::setnames(dat, toupper(names(dat))) + + # Cleanup flags + if (!keepFlags && "OBS_FLAG" %chin% names(dat)) { + dat[, OBS_FLAG := NULL] + } + + # Convert time format + #dat[, TIME_PERIOD := convert_time_col(TIME_PERIOD, time_format)] + if ("TIME_PERIOD" %in% names(dat)) { + dat[, TIME_PERIOD := convert_time_col(TIME_PERIOD, time_format)] + } else { + warning("'TIME_PERIOD' column not found in the downloaded data.") + } + + # Convert values to numeric + if ("OBS_VALUE" %chin% names(dat)) { + dat[, OBS_VALUE := as.numeric(OBS_VALUE)] + } + + # Apply labels if requested + if (type == "label") { + dat <- label_eurostat_sdmx(dat, agency = agency, id = id, lang = lang) + } + + # Convert to legacy format if needed + if (legacy.data.output) { + dat <- legacy_data_format(dat) + } + + return(dat) + }, + "fail" = function() { + stop("Eurostat async request failed. Final status: ", statuses[fail_pos]) + }, + "timeout" = function() { + stop("Eurostat async request timed out after ", max_wait, " seconds.") + } + ) + + handlers[[outcome]]() +} diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 79a95a62..139a5bd9 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -2,19 +2,19 @@ #' #' @description #' Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} -#' +#' #' @details #' Download data from SDMX API #' #' @inheritParams get_eurostat -#' @param agency Either "Eurostat" (default), "Eurostat_comext" +#' @param agency Either "Eurostat" (default), "Eurostat_comext" #' (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW" -#' +#' #' @importFrom curl curl_download #' @importFrom utils download.file #' @importFrom readr read_tsv cols col_character #' @importFrom data.table fread -#' +#' #' @export get_eurostat_sdmx <- function( id, @@ -26,9 +26,11 @@ get_eurostat_sdmx <- function( agency = "Eurostat", compressed = TRUE, keepFlags = FALSE, - legacy.data.output = FALSE + legacy.data.output = FALSE, + wait = 10, + max_wait = 600 ) { - + # Check if you have access to ec.europe.eu. # If dataset is cached, access to ec.europe.eu is not needed # Therefore this is a warning, not a stop @@ -38,11 +40,11 @@ get_eurostat_sdmx <- function( Please check your connection and/or review your proxy settings") # nocov end } - + lang <- check_lang(lang) - + agency <- tolower(agency) - + # agencyID <- switch( # agency, # eurostat = "ESTAT", @@ -51,10 +53,10 @@ get_eurostat_sdmx <- function( # empl = "EMPL", # grow = "GROW" # ) - + api_base_uri <- build_api_base_uri(agency) agencyID <- build_agencyID(agency) - + # api_base_uri <- switch( # agency, # eurostat = "https://ec.europa.eu/eurostat/api/dissemination", @@ -62,20 +64,22 @@ get_eurostat_sdmx <- function( # comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", # empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", # grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") - + if (is.null(api_base_uri)) stop("Use valid agency") - + if (is.null(filters) && agency == "eurostat_comext") { stop("Use filters when querying data from Eurostat COMEXT or PRODCOM") } - + # Following resource is supported: data resource <- "data" # The identifier of the dataflow reference flowRef <- id key <- data_filtering_on_dimension(agency, id, filters) - compressed <- ifelse(compressed == TRUE, "&compressed=true", "&compressed=false") - + compressed_flag <- compressed + compressed <- if (compressed_flag) "&compressed=true" else "&compressed=false" + + url <- paste0( api_base_uri, "/sdmx/2.1/", @@ -88,61 +92,115 @@ get_eurostat_sdmx <- function( "format=SDMX-CSV", compressed ) - + + res <- httr::GET(url) + #print(res) + ctype <- httr::headers(res)[["content-type"]] + #print(ctype) + + if (grepl("xml", ctype, ignore.case = TRUE)) { + content <- httr::content(res, as = "parsed", encoding = "UTF-8") + async_id <- xml2::xml_text(xml2::xml_find_first(content, ".//id")) + if (!is.na(async_id) && nzchar(async_id)) { + message("Async mode triggered. Downloading via async ID: ", async_id) + return(get_eurostat_async( + id = async_id, + time_format = time_format, + type = type, + lang = lang, + use.data.table = use.data.table, + agency = agency, + keepFlags = keepFlags, + legacy.data.output = legacy.data.output, + wait = wait, + max_wait = max_wait, + compressed = compressed_flag + )) + } else { + message(" Response was XML, but no async found. Treating as sync (unexpected).") + } + } else if (grepl("csv", ctype, ignore.case = TRUE)) { + message("Synchronous mode: CSV data returned directly.") + } else { + message("Unexpected content type: ", ctype) + } + + + # content <- httr::content(res, as = "parsed", encoding = "UTF-8") + # + # # Check for async ID + # async_id <- xml2::xml_text(xml2::xml_find_first(content, ".//id")) + # + # if (!is.na(async_id) && nzchar(async_id)) { + # message("Async mode triggered. Downloading via async ID: ", async_id) + # return(get_eurostat_async( + # id = async_id, + # time_format = time_format, + # type = type, + # lang = lang, + # use.data.table = use.data.table, + # agency = agency, + # keepFlags = keepFlags, + # legacy.data.output = legacy.data.output, + # wait = wait, + # max_wait = max_wait + # )) + # } + tfile <- tempfile() on.exit(unlink(tfile)) - + curl::curl_download(url = url, destfile = tfile) - dat <- read.csv(tfile, colClasses = "character") + dat <- read.csv(tfile, colClasses = "character", row.names = NULL, check.names = FALSE) if (!keepFlags) { col_names <- names(dat) dat <- dat[setdiff(col_names, "OBS_FLAG")] } - + # return(references_resolution(api_base_uri, resource = "datastructure", agencyID = agencyID, resourceID = id)) - + if (identical(type, "label")){ dat <- label_eurostat_sdmx(x = dat, agency = agency, id = id, lang = lang) } - + dat$TIME_PERIOD <- convert_time_col(x = dat$TIME_PERIOD, time_format = time_format) - + dat$OBS_VALUE <- as.numeric(dat$OBS_VALUE) - + if (legacy.data.output) { dat <- legacy_data_format(dat) } - + dat - + } extract_metadata <- function(id, agency = "Eurostat") { - + api_base_uri <- build_api_base_uri(agency) - + data_structure_definition_url <- paste0( api_base_uri, "/sdmx/2.1/dataflow/estat/", id) - + dsd_xml <- xml2::read_xml(data_structure_definition_url) - + # Define namespaces namespaces <- xml2::xml_ns(dsd_xml) - + # Extract Header information header <- xml2::xml_find_first(dsd_xml, ".//m:Header", namespaces) header_id <- xml2::xml_text(xml2::xml_find_first(header, ".//m:ID", namespaces)) prepared <- substr(xml2::xml_text(xml2::xml_find_first(header, ".//m:Prepared", namespaces)),1,10) sender_id <- xml2::xml_attr(xml2::xml_find_first(header, ".//m:Sender", namespaces), "id") - - # Continue with dataflow and annotations extraction + + # Continue with dataflow and annotations extraction dataflow <- xml2::xml_find_first(dsd_xml, ".//s:Dataflow", namespaces) dataflow_id <- xml2::xml_attr(dataflow, "id") urn <- xml2::xml_attr(dataflow, "urn") @@ -154,17 +212,17 @@ extract_metadata <- function(id, agency = "Eurostat") { name_de <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='de']", namespaces)) name_en <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='en']", namespaces)) name_fr <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='fr']", namespaces)) - + source_institutions <- list() doi_details <- NULL - + annotations_nodes <- xml2::xml_find_all(dataflow, ".//c:Annotation", namespaces) for (node in annotations_nodes) { title <- xml2::xml_text(xml2::xml_find_first(node, ".//c:AnnotationTitle", namespaces)) type <- xml2::xml_text(xml2::xml_find_first(node, ".//c:AnnotationType", namespaces)) texts_nodes <- xml2::xml_find_all(node, ".//c:AnnotationText", namespaces) - - + + # Assign specific annotations based on type if (type == "OBS_PERIOD_OVERALL_LATEST") { latest_period_timestamp <- title # Directly store the latest period timestamp @@ -177,19 +235,19 @@ extract_metadata <- function(id, agency = "Eurostat") { lang <- xml2::xml_attr(text_node, "xml:lang") text <- xml2::xml_text(text_node) source_institutions[[lang]] <- text - + } - + } } - + # Extract DOI URL if the annotation contains adms:Identifier if (grepl("adms:Identifier", title)) { title_xml <- xml2::read_xml(title) doi_url <- xml2::xml_attr(xml2::xml_find_first(title_xml, ".//adms:Identifier"), "rdf:about", xml2::xml_ns(title_xml)) } - - + + metadata <- list( Name_EN = name_en, Name_DE = name_de, @@ -208,9 +266,9 @@ extract_metadata <- function(id, agency = "Eurostat") { Version = version, IsFinal = isFinal ) - + return(metadata) - + } legacy_data_format <- function(x, cols_to_drop = c("DATAFLOW", "LAST UPDATE", "freq")) { @@ -221,7 +279,7 @@ legacy_data_format <- function(x, cols_to_drop = c("DATAFLOW", "LAST UPDATE", "f x[, (cols_to_drop[i]):=NULL] } } - + # Rename columns non_legacy_col_name = c("TIME_PERIOD", "OBS_VALUE", "OBS_FLAG") legacy_col_name = c("time", "values", "flags") @@ -230,7 +288,7 @@ legacy_data_format <- function(x, cols_to_drop = c("DATAFLOW", "LAST UPDATE", "f data.table::setnames(x, (non_legacy_col_name[i]), (legacy_col_name[i])) } } - + } else { x <- x[setdiff(names(x), cols_to_drop)] cols_to_rename <- data.frame(non_legacy_col_name = c("TIME_PERIOD", "OBS_VALUE", "OBS_FLAG"), @@ -259,7 +317,7 @@ build_api_base_uri <- function(agency) { comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") - + api_base_uri } @@ -277,7 +335,7 @@ build_agencyID <- function(agency) { empl = "EMPL", grow = "GROW" ) - + agencyID } @@ -286,17 +344,17 @@ data_filtering_on_dimension <- function(agency, id, filters) { # api_base_url, # "/sdmx/2.1/datastructure/estat/", # id) - + filter_names <- toupper(names(filters)) - + dimension_df <- get_codelist_id(agency = agency, id = id) dimension_id_upper <- dimension_df$dimension_id_upper - + if (!rlang::is_empty(setdiff(filter_names, dimension_id_upper))) { stop(paste0("Use valid filter dimensions in the correct order: ", paste(dimension_id, collapse = "."))) } - + # Assumes that dimensions are listed in the order of their positions # If there is an example to the contrary somewhere, this should be changed filter_string <- "" @@ -316,29 +374,29 @@ data_filtering_on_dimension <- function(agency, id, filters) { } get_codelist_id <- function(agency, id) { - + api_base_uri <- build_api_base_uri(agency) - + data_structure_definition_url <- paste0( api_base_uri, "/sdmx/2.1/datastructure/ESTAT/", id) - + dsd <- xml2::read_xml(data_structure_definition_url) - + # dimension_id <- xml2::xml_text(xml2::xml_find_all(xml2::xml_find_all(dsd, ".//s:Dimension"), ".//Ref[@class='Codelist']/@id")) dimension_id <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/@id")) dimension_position <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/@position")) codelist_id <- xml2::xml_text(xml2::xml_find_all(dsd, ".//s:Dimension/s:LocalRepresentation/s:Enumeration/Ref/@id")) dimension_id_upper <- toupper(dimension_id) dimension_df <- data.frame(dimension_position, dimension_id, dimension_id_upper, codelist_id) - + dimension_df } # references_resolution <- function(api_base_uri, resource, agencyID, resourceID) { # # resource <- "datastructure" -# +# # url <- paste0( # api_base_uri, # "/sdmx/2.1/", @@ -348,13 +406,13 @@ get_codelist_id <- function(agency, id) { # "/", # resourceID # ) -# +# # parsed <- xml2::read_xml(url) -# +# # } # get_codelist <- function(api_base_uri, id, dimension_id) { -# +# # } label_eurostat_sdmx <- function(x, agency, id, lang = "en") { @@ -362,12 +420,12 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en") { dimension_df <- get_codelist_id(agency = agency, id = id) resource <- "codelist" lang <- check_lang(lang) - + # non-destructive editing y <- x api_base_uri <- build_api_base_uri(agency) agencyID <- build_agencyID(agency) - + agencyID <- agencyID # data.table objects need different kind of handling if (inherits(y, "data.table")) { @@ -460,4 +518,4 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en") { } } x -} \ No newline at end of file +} diff --git a/man/get_eurostat_async.Rd b/man/get_eurostat_async.Rd new file mode 100644 index 00000000..5908b547 --- /dev/null +++ b/man/get_eurostat_async.Rd @@ -0,0 +1,71 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_eurostat_async.R +\name{get_eurostat_async} +\alias{get_eurostat_async} +\title{Get Asynchronous Eurostat SDMX Data by ID} +\usage{ +get_eurostat_async( + id, + time_format = "date", + type = "code", + lang = "en", + use.data.table, + agency = "Eurostat", + keepFlags = FALSE, + legacy.data.output = FALSE, + wait = 1, + max_wait = 60, + compressed = TRUE +) +} +\arguments{ +\item{id}{Character. Async request ID from initial SDMX call.} + +\item{time_format}{a string giving a type of the conversion of the time column from the +eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +with the date being the first day of the period. A "\code{date_last}" argument +converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +that the exact date is the last date of the period. Period can be year, +semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for +more information). +Argument "\code{num}" converts the date into a numeric (integer) meaning that +the first day of the year 2000 is close to 2000.01 and the last day of the +year is close to 2000.99 (see \code{\link[=eurotime2num]{eurotime2num()}} for more information). +Using the argument "\code{raw}" preserves the dates as they were in the original +Eurostat data.} + +\item{type}{A type of variables, "\code{code}" (default), "\code{label}" or "\code{both}". +The parameter "\code{both}" will return a data_frame with named vectors, +labels as values and codes as names.} + +\item{lang}{2-letter language code, default is "\code{en}" (English), other +options are "\code{fr}" (French) and "\code{de}" (German). Used for labeling datasets.} + +\item{use.data.table}{Use faster data.table functions? Default is FALSE. +On Windows requires that RTools is installed.} + +\item{agency}{Either "Eurostat" (default), "Eurostat_comext" +(for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW"} + +\item{keepFlags}{a logical whether the flags (e.g. "confidential", +"provisional") should be kept in a separate column or if they +can be removed. Default is \code{FALSE}. For flag values see: +\url{https://ec.europa.eu/eurostat/data/database/information}. +Also possible non-real zero "0n" is indicated in flags column. +Flags are not available for eurostat API, so \code{keepFlags} +can not be used with a \code{filters}.} + +\item{legacy.data.output}{Use legacy column names and data object structure. +Default is FALSE. If TRUE, the object will try to emulate the naming +conventions of eurostat package version 3.7.x and earlier.} + +\item{wait}{Integer. Seconds between status checks.} + +\item{max_wait}{Integer. Max time to wait in seconds.} +} +\value{ +A data.frame containing the SDMX-CSV data. +} +\description{ +Polls Eurostat's async API for the status of a submitted request and downloads the data when available. +} diff --git a/man/get_eurostat_geospatial.Rd b/man/get_eurostat_geospatial.Rd index 8634fa4c..347ec7cb 100755 --- a/man/get_eurostat_geospatial.Rd +++ b/man/get_eurostat_geospatial.Rd @@ -70,13 +70,16 @@ globally the file would be stored in the \code{\link[=tempdir]{tempdir()}}.} default is \code{FALSE}.} \item{\code{spatialtype}}{Type of geometry to be returned: \itemize{ -\item \strong{"BN"}: Boundaries - \code{LINESTRING} object. -\item \strong{"LB"}: Labels - \code{POINT} object. -\item \strong{"RG"}: Regions - \code{MULTIPOLYGON/POLYGON} object. -}} +\item \code{"BN"}: Boundaries - \code{LINESTRING} object. +\item \code{"LB"}: Labels - \code{POINT} object. +\item \code{"RG"}: Regions - \code{MULTIPOLYGON/POLYGON} object. +} + +\strong{Note that} parameters \code{country}, \code{nuts_level} and \code{nuts_id} would be only +applied when \code{spatialtype} is \code{"BN"} or \code{"RG"}.} \item{\code{country}}{Optional. A character vector of country codes. It could be either a vector of country names, a vector of ISO3 country codes or a -vector of Eurostat country codes. Mixed types (as \code{c("Turkey","US","FRA")}) +vector of Eurostat country codes. Mixed types (as \code{c("Italy","ES","FRA")}) would not work. See also \code{\link[countrycode:countrycode]{countrycode::countrycode()}}.} \item{\code{nuts_id}}{Optional. A character vector of NUTS IDs.} }} diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 13037179..f4f2976c 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -39,11 +39,11 @@ Eurostat order. If \code{FALSE} they are returned as strings.} \item{...}{ Arguments passed on to \code{\link[httr2:req_proxy]{httr2::req_proxy}} \describe{ - \item{\code{req}}{A \link[httr2]{request}.} + \item{\code{req}}{A httr2 \link[httr2]{request} object.} \item{\code{url,port}}{Location of proxy.} \item{\code{username,password}}{Login details for proxy, if needed.} \item{\code{auth}}{Type of HTTP authentication to use. Should be one of the -following: \code{basic}, digest, digest_ie, gssnegotiate, ntlm, any.} +following: \code{basic}, \code{digest}, \code{digest_ie}, \code{gssnegotiate}, \code{ntlm}, \code{any}.} }} } \value{ diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index 62b91375..c66071d3 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -14,7 +14,9 @@ get_eurostat_sdmx( agency = "Eurostat", compressed = TRUE, keepFlags = FALSE, - legacy.data.output = FALSE + legacy.data.output = FALSE, + wait = 10, + max_wait = 600 ) } \arguments{ From d60b018d21523b10a2eea10a62ce443b47ec63f9 Mon Sep 17 00:00:00 2001 From: wiibaa Date: Wed, 11 Jun 2025 08:36:08 +0200 Subject: [PATCH 11/62] update links to eurostat user-guide --- NEWS.md | 4 ++-- R/eurostat-package.R | 14 +++++++------- R/get_eurostat.R | 4 ++-- man/eurostat-package.Rd | 14 +++++++------- man/get_eurostat.Rd | 8 ++++---- man/get_eurostat_folder.Rd | 6 +++--- man/get_eurostat_json.Rd | 8 ++++---- man/get_eurostat_raw.Rd | 6 +++--- man/get_eurostat_toc.Rd | 2 +- man/search_eurostat.Rd | 2 +- vignettes/articles/dimlst_vs_allconceptschemes.Rmd | 2 +- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/NEWS.md b/NEWS.md index 630dc3ad..7ef8e954 100644 --- a/NEWS.md +++ b/NEWS.md @@ -78,7 +78,7 @@ downloading geospatial data (PR #264, thanks to @dieghernan): ## Major updates -* Updated `get_eurostat()` and its assorted functions to download data from the new dissemination API (related to issues #251, #243). See Eurostat web page Transition - from Eurostat Bulk Download to API for a list of differences between old and new data sources: https://wikis.ec.europa.eu/display/EUROSTATHELP/Transition+-+from+Eurostat+Bulk+Download+to+API +* Updated `get_eurostat()` and its assorted functions to download data from the new dissemination API (related to issues #251, #243). See Eurostat web page Transition - from Eurostat Bulk Download to API for a list of differences between old and new data sources: https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/bulkdownload * Added new temporary functions for downloading and handling data from the new dissemination API: `get_eurostat_raw2`, `tidy_eurostat2`, `convert_time_col2`, `eurotime2date2`, `eurotime2num2` and `label_eurostat2`. When the old bulk download facilities are decommissioned, these functions will replace the old functions with old naming schemes (without the 2s at the end). * `tidy_eurostat2` function is now able to handle multiple time frequencies in one call: For example, you can download annual, quarterly, and monthly data simply by using a vector c("A", "Q", "M") in select_time instead of using these singular frequencies in separate calls. The function will also return multiple time series in one dataset if select_time is NULL (as it is by default). If the dataset contains multiple time series and these are explicitly downloaded / no select_time parameter is given, a message will be printed. * `eurotime2num` can now handle monthly and weekly data as well. @@ -90,7 +90,7 @@ downloading geospatial data (PR #264, thanks to @dieghernan): # eurostat 3.7.13 (2023-02-01) -* Updated `get_eurostat_json()` to migrate from JSON web service to API Statistics (addressed in issues #243, #251). Please note that the output from JSON API is now slightly different than before: the datasets now contain a freq column to indicate the frequency with which data has been collected, for example annually "A", monthly "M" or quarterly "Q". See Eurostat - Data browser online help website for more information: https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+migrating+from+JSON+web+service+to+API+Statistics +* Updated `get_eurostat_json()` to migrate from JSON web service to API Statistics (addressed in issues #243, #251). Please note that the output from JSON API is now slightly different than before: the datasets now contain a freq column to indicate the frequency with which data has been collected, for example annually "A", monthly "M" or quarterly "Q". See Eurostat - Data browser online help website for more information: https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/json * Minor fixes in `get_bibentry()` and `get_eurostat_geospatial()` # eurostat 3.7.12 (2022-06-28) diff --git a/R/eurostat-package.R b/R/eurostat-package.R index 211bda40..2cd59699 100755 --- a/R/eurostat-package.R +++ b/R/eurostat-package.R @@ -50,7 +50,7 @@ #' Data is downloaded from Eurostat SDMX 2.1 API endpoint #' as compressed TSV files that are transformed into tabular format. #' See Eurostat documentation for more information: -#' \url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+query} +#' \url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Dataquery} #' #' The new dissemination API replaces the old bulk download facility that was #' used by Eurostat before October 2023 and by the eurostat R package versions @@ -58,7 +58,7 @@ #' See Eurostat documentation about the transition from Bulk Download to API #' for more information about the differences between the old bulk download #' facility and the data provided by the new API connection: -#' \url{https://wikis.ec.europa.eu/display/EUROSTATHELP/Transition+-+from+Eurostat+Bulk+Download+to+API} +#' \url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/bulkdownload} #' #' See especially the document Migrating_to_API_TSV.pdf that describes the #' changes in TSV file format in new applications. @@ -75,20 +75,20 @@ #' Eurostat. We may support this feature in the future. In the meantime, if you #' are interested in filtering Dissemination API data queries manually, please #' consult the following Eurostat documentation: -#' \url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+filtering} +#' \url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Filteringonseries-keys} #' #' # Data source: Eurostat API Statistics (JSON API) #' #' Data is downloaded from Eurostat API Statistics. See Eurostat documentation #' for more information about data queries in API Statistics -#' \url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query} +#' \url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api} #' #' This replaces the old JSON Web Services that was used by Eurostat before #' February 2023 and by the eurostat R package versions before 3.7.13. #' See Eurostat documentation about the migration from JSON web service to API #' Statistics for more information about the differences between the old and #' the new service: -#' \url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+migrating+from+JSON+web+service+to+API+Statistics} +#' \url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/json} #' #' For easily viewing which filtering options are available - in addition to #' the default ones, time and language - Eurostat Web services Query builder @@ -182,7 +182,7 @@ #' #' For more information about data filtering see Eurostat documentation #' on API Statistics: -#' \url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query#APIStatisticsdataquery-TheparametersdefinedintheRESTrequest} +#' \url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api#APIStatisticsdataquery-TheparametersdefinedintheRESTrequest} #' #' # Data source: Eurostat Table of Contents #' @@ -193,7 +193,7 @@ #' \url{https://ec.europa.eu/eurostat/api/dissemination/catalogue/toc/txt?lang=de} #' #' See Eurostat documentation on TOC items: -#' \url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+-+Detailed+guidelines+-+Catalogue+API+-+TOC} +#' \url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-detailed-guidelines/catalogue-api/toc} #' #' # Data source: GISCO - General Copyright #' diff --git a/R/get_eurostat.R b/R/get_eurostat.R index 9aea9255..dcd13c5f 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -83,9 +83,9 @@ #' #' @details #' Datasets are downloaded from -#' [the Eurostat SDMX 2.1 API](https://wikis.ec.europa.eu/display/EUROSTATHELP/Transition+-+from+Eurostat+Bulk+Download+to+API) +#' [the Eurostat SDMX 2.1 API](https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/bulkdownload) #' in TSV format or from The Eurostat -#' [API Statistics JSON API](https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query). +#' [API Statistics JSON API](https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api). #' If only the table `id` is given, the whole table is downloaded from the #' SDMX API. If any `filters` are given JSON API is used instead. #' diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index 528fb685..7c5ac11f 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -39,7 +39,7 @@ the data." Data is downloaded from Eurostat SDMX 2.1 API endpoint as compressed TSV files that are transformed into tabular format. See Eurostat documentation for more information: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+query} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Dataquery} The new dissemination API replaces the old bulk download facility that was used by Eurostat before October 2023 and by the eurostat R package versions @@ -47,7 +47,7 @@ before 4.0.0. See Eurostat documentation about the transition from Bulk Download to API for more information about the differences between the old bulk download facility and the data provided by the new API connection: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/Transition+-+from+Eurostat+Bulk+Download+to+API} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/bulkdownload} See especially the document Migrating_to_API_TSV.pdf that describes the changes in TSV file format in new applications. @@ -64,20 +64,20 @@ datasets downloaded through the SDMX Dissemination API is also supported by Eurostat. We may support this feature in the future. In the meantime, if you are interested in filtering Dissemination API data queries manually, please consult the following Eurostat documentation: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+filtering} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Filteringonseries-keys} } \section{Data source: Eurostat API Statistics (JSON API)}{ Data is downloaded from Eurostat API Statistics. See Eurostat documentation for more information about data queries in API Statistics -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api} This replaces the old JSON Web Services that was used by Eurostat before February 2023 and by the eurostat R package versions before 3.7.13. See Eurostat documentation about the migration from JSON web service to API Statistics for more information about the differences between the old and the new service: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+migrating+from+JSON+web+service+to+API+Statistics} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/json} For easily viewing which filtering options are available - in addition to the default ones, time and language - Eurostat Web services Query builder @@ -179,7 +179,7 @@ Example: For more information about data filtering see Eurostat documentation on API Statistics: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query#APIStatisticsdataquery-TheparametersdefinedintheRESTrequest} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api#APIGettingstartedwithstatisticsAPI-Filterdata} } } @@ -191,7 +191,7 @@ The Eurostat Table of Contents (TOC) is downloaded from \url{https://ec.europa.eu/eurostat/api/dissemination/catalogue/toc/txt?lang=de} See Eurostat documentation on TOC items: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+-+Detailed+guidelines+-+Catalogue+API+-+TOC} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-detailed-guidelines/catalogue-api/toc} } \section{Data source: GISCO - General Copyright}{ diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index c33226b2..735046ff 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -113,9 +113,9 @@ Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} } \details{ Datasets are downloaded from -\href{https://wikis.ec.europa.eu/display/EUROSTATHELP/Transition+-+from+Eurostat+Bulk+Download+to+API}{the Eurostat SDMX 2.1 API} +\href{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/bulkdownload}{the Eurostat SDMX 2.1 API} in TSV format or from The Eurostat -\href{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query}{API Statistics JSON API}. +\href{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api}{API Statistics JSON API}. If only the table \code{id} is given, the whole table is downloaded from the SDMX API. If any \code{filters} are given JSON API is used instead. @@ -257,7 +257,7 @@ Example: For more information about data filtering see Eurostat documentation on API Statistics: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query#APIStatisticsdataquery-TheparametersdefinedintheRESTrequest} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api#APIStatisticsdataquery-TheparametersdefinedintheRESTrequest} } } @@ -295,7 +295,7 @@ datasets downloaded through the SDMX Dissemination API is also supported by Eurostat. We may support this feature in the future. In the meantime, if you are interested in filtering Dissemination API data queries manually, please consult the following Eurostat documentation: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+filtering} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Filteringonseries-keys} } \section{Strategies for handling large datasets more efficiently}{ diff --git a/man/get_eurostat_folder.Rd b/man/get_eurostat_folder.Rd index 4551e0db..ac5054a3 100644 --- a/man/get_eurostat_folder.Rd +++ b/man/get_eurostat_folder.Rd @@ -36,14 +36,14 @@ The Eurostat Table of Contents (TOC) is downloaded from \url{https://ec.europa.eu/eurostat/api/dissemination/catalogue/toc/txt?lang=de} See Eurostat documentation on TOC items: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+-+Detailed+guidelines+-+Catalogue+API+-+TOC} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-detailed-guidelines/catalogue-api/toc} } \section{Data source: Eurostat SDMX 2.1 Dissemination API}{ Data is downloaded from Eurostat SDMX 2.1 API endpoint as compressed TSV files that are transformed into tabular format. See Eurostat documentation for more information: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+query} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Dataquery} The new dissemination API replaces the old bulk download facility that was used by Eurostat before October 2023 and by the eurostat R package versions @@ -51,7 +51,7 @@ before 4.0.0. See Eurostat documentation about the transition from Bulk Download to API for more information about the differences between the old bulk download facility and the data provided by the new API connection: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/Transition+-+from+Eurostat+Bulk+Download+to+API} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/bulkdownload} See especially the document Migrating_to_API_TSV.pdf that describes the changes in TSV file format in new applications. diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 13037179..4566fa10 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -104,14 +104,14 @@ in the Eurostat website. We hope you never encounter them. \section{Data source: Eurostat API Statistics (JSON API)}{ Data is downloaded from Eurostat API Statistics. See Eurostat documentation for more information about data queries in API Statistics -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api} This replaces the old JSON Web Services that was used by Eurostat before February 2023 and by the eurostat R package versions before 3.7.13. See Eurostat documentation about the migration from JSON web service to API Statistics for more information about the differences between the old and the new service: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+migrating+from+JSON+web+service+to+API+Statistics} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/json} For easily viewing which filtering options are available - in addition to the default ones, time and language - Eurostat Web services Query builder @@ -213,7 +213,7 @@ Example: For more information about data filtering see Eurostat documentation on API Statistics: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+Statistics+-+data+query#APIStatisticsdataquery-TheparametersdefinedintheRESTrequest} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api#APIStatisticsdataquery-TheparametersdefinedintheRESTrequest} } } @@ -273,7 +273,7 @@ datasets downloaded through the SDMX Dissemination API is also supported by Eurostat. We may support this feature in the future. In the meantime, if you are interested in filtering Dissemination API data queries manually, please consult the following Eurostat documentation: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+filtering} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Filteringonseries-keys} } \examples{ diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index be0ebded..7702d001 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -29,7 +29,7 @@ dissemination API. Data is downloaded from Eurostat SDMX 2.1 API endpoint as compressed TSV files that are transformed into tabular format. See Eurostat documentation for more information: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+query} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Dataquery} The new dissemination API replaces the old bulk download facility that was used by Eurostat before October 2023 and by the eurostat R package versions @@ -37,7 +37,7 @@ before 4.0.0. See Eurostat documentation about the transition from Bulk Download to API for more information about the differences between the old bulk download facility and the data provided by the new API connection: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/Transition+-+from+Eurostat+Bulk+Download+to+API} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-migrating/bulkdownload} See especially the document Migrating_to_API_TSV.pdf that describes the changes in TSV file format in new applications. @@ -103,7 +103,7 @@ datasets downloaded through the SDMX Dissemination API is also supported by Eurostat. We may support this feature in the future. In the meantime, if you are interested in filtering Dissemination API data queries manually, please consult the following Eurostat documentation: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+data+filtering} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Filteringonseries-keys} } \examples{ diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index 3dc43a18..0444ea6b 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -49,7 +49,7 @@ The Eurostat Table of Contents (TOC) is downloaded from \url{https://ec.europa.eu/eurostat/api/dissemination/catalogue/toc/txt?lang=de} See Eurostat documentation on TOC items: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+-+Detailed+guidelines+-+Catalogue+API+-+TOC} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-detailed-guidelines/catalogue-api/toc} } \examples{ diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index 541f9de9..e95ca8aa 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -74,7 +74,7 @@ The Eurostat Table of Contents (TOC) is downloaded from \url{https://ec.europa.eu/eurostat/api/dissemination/catalogue/toc/txt?lang=de} See Eurostat documentation on TOC items: -\url{https://wikis.ec.europa.eu/display/EUROSTATHELP/API+-+Detailed+guidelines+-+Catalogue+API+-+TOC} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-detailed-guidelines/catalogue-api/toc} } \examples{ diff --git a/vignettes/articles/dimlst_vs_allconceptschemes.Rmd b/vignettes/articles/dimlst_vs_allconceptschemes.Rmd index ecfa452e..3d77efa7 100644 --- a/vignettes/articles/dimlst_vs_allconceptschemes.Rmd +++ b/vignettes/articles/dimlst_vs_allconceptschemes.Rmd @@ -32,7 +32,7 @@ AGECHILD Age of the child AGEDEF Age definition ``` -An example of the new Concept Scheme file for dataset `NAMA_10_GDP` (see instructions for downloading [here](https://wikis.ec.europa.eu/display/EUROSTATHELP/API+SDMX+2.1+-+metadata+query#APISDMX2.1metadataquery-SDMX2.1endpoint-REST-SDMX-ML2.1ConceptScheme)): +An example of the new Concept Scheme file for dataset `NAMA_10_GDP` (see instructions for downloading [here](https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/sdmx2.1#APIGettingstartedwithSDMX2.1API-Lookingupinthemetadataofadataset)): ```{xml} From c7febc34462f2c8e039a31853e0ab2500077c9d5 Mon Sep 17 00:00:00 2001 From: ake123 Date: Mon, 7 Jul 2025 16:44:22 +0300 Subject: [PATCH 12/62] issues #316 and #317 --- R/get_eurostat_link.R | 169 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 R/get_eurostat_link.R diff --git a/R/get_eurostat_link.R b/R/get_eurostat_link.R new file mode 100644 index 00000000..76e22eaf --- /dev/null +++ b/R/get_eurostat_link.R @@ -0,0 +1,169 @@ +#' Download Eurostat Data from API Link (robust, no list-columns, async-aware) +#' +#' Supports TSV, SDMX-CSV, SDMX-ML, JSON-stat, and Spreadsheet (.xlsx) formats. +#' Always returns a tibble where possible. +#' +#' @param link Eurostat "Copy API link" URL or Data Browser download link +#' @param destfile Optional file path for saving raw files (only for Excel) +#' @return Tibble for all supported formats +#' @export +get_eurostat_link <- function(link, destfile = NULL) { + if (!is.character(link) || length(link) != 1) stop("Provide a valid Eurostat link.") + if (!grepl("ec\\.europa\\.eu/eurostat/", link)) stop("Only Eurostat links allowed.") + + as_tibble_safe <- function(x) { + if (is.data.frame(x)) tibble::as_tibble(x) + else tibble::tibble(data = list(x)) + } + + download_content <- function(link) { + resp <- httr::GET(link) + httr::stop_for_status(resp) + raw <- httr::content(resp, as = "raw") + if (httr::http_type(resp) == "application/gzip" || grepl("compress=true", link, ignore.case = TRUE)) { + message("Decompressing content...") + raw <- memDecompress(raw, type = "gzip", asChar = TRUE) + } else { + raw <- rawToChar(raw) + } + raw + } + + # # Comext DS-prefixed datasets must use correct base + if (grepl("/comext/dissemination", link)) { + # Extract dataset portion safely + dataset_match <- regmatches(link, regexpr("/ds-[0-9]+", tolower(link))) + + if (length(dataset_match) == 0) { + stop("Comext API requires DS-prefixed dataset codes (e.g., ds-056120).") + } + } + + # TSV format + if (grepl("format=TSV", link, ignore.case = TRUE) || grepl("\\.tsv", link, ignore.case = TRUE)) { + message("Downloading TSV...") + text_content <- download_content(link) + return(as_tibble_safe(read.delim(text = text_content, check.names = FALSE, sep = "\t"))) + } + + # SDMX-CSV + if (grepl("format=csvdata", link, ignore.case = TRUE) || grepl("\\.csv", link, ignore.case = TRUE)) { + message("Downloading SDMX-CSV 1.0...") + text_content <- download_content(link) + + lines <- strsplit(text_content, "\n", fixed = TRUE)[[1]] + + # Detect first line with clear header fields + data_start <- which(sapply(lines, function(l) { + fields <- strsplit(l, ",", fixed = TRUE)[[1]] + length(fields) > 1 && all(nzchar(fields) | fields == "") + }))[1] + + if (is.na(data_start)) stop("Could not detect valid SDMX-CSV header for Comext dataset.") + + clean_text <- paste(lines[data_start:length(lines)], collapse = "\n") + + # Explicit row.names = NULL prevents first column being treated as row names + return(as_tibble_safe(read.csv(text = clean_text, check.names = FALSE, row.names = NULL))) + } + + # SDMX-ML 2.1 StructureSpecific + if (grepl("format=structurespecificdata", link, ignore.case = TRUE) && grepl("formatVersion=2.1", link, ignore.case = TRUE)) { + if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") + message("Downloading SDMX-ML 2.1 StructureSpecific...") + xml_text <- download_content(link) + doc <- xml2::read_xml(xml_text) + ns <- xml2::xml_ns(doc) + series_nodes <- xml2::xml_find_all(doc, ".//Series", ns) + result <- purrr::map_dfr(series_nodes, function(series) { + series_atts <- xml2::xml_attrs(series) + obs_nodes <- xml2::xml_find_all(series, ".//Obs", ns) + purrr::map_dfr(obs_nodes, function(o) { + data.frame(as.list(series_atts), time = xml2::xml_attr(o, "TIME_PERIOD"), value = as.numeric(xml2::xml_attr(o, "OBS_VALUE")), stringsAsFactors = FALSE) + }) + }) + return(tibble::as_tibble(result)) + } + + # SDMX-ML 2.1 GenericData + if (grepl("format=genericdata", link, ignore.case = TRUE)) { + if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") + message("Downloading SDMX-ML 2.1 GenericData...") + xml_text <- download_content(link) + doc <- xml2::read_xml(xml_text) + ns <- xml2::xml_ns(doc) + series_nodes <- xml2::xml_find_all(doc, ".//g:Series", ns) + result <- purrr::map_dfr(series_nodes, function(series) { + key_nodes <- xml2::xml_find_all(series, ".//g:Value", ns) + key_vals <- setNames(xml2::xml_attr(key_nodes, "value"), xml2::xml_attr(key_nodes, "id")) + obs_nodes <- xml2::xml_find_all(series, ".//g:Obs", ns) + purrr::map_dfr(obs_nodes, function(obs) { + data.frame(as.list(key_vals), time = xml2::xml_attr(xml2::xml_find_first(obs, ".//g:ObsDimension", ns), "value"), value = as.numeric(xml2::xml_attr(xml2::xml_find_first(obs, ".//g:ObsValue", ns), "value")), stringsAsFactors = FALSE) + }) + }) + return(tibble::as_tibble(result)) + } + + # SDMX-ML 3.0 StructureSpecific + if (grepl("/sdmx/3.0/data", link) && (grepl("\\.xml", link, ignore.case = TRUE) || !grepl("format=", link, ignore.case = TRUE))) { + if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") + message("Downloading SDMX-ML 3.0 StructureSpecific...") + + xml_text <- download_content(link) + doc <- xml2::read_xml(xml_text) + ns <- xml2::xml_ns(doc) + + series_nodes <- xml2::xml_find_all(doc, ".//m:Series", ns) + if (length(series_nodes) == 0) { + series_nodes <- xml2::xml_find_all(doc, ".//Series", ns) + } + + result <- purrr::map_dfr(series_nodes, function(series) { + #series_atts <- as.list(xml2::xml_attrs(series)) + series_atts <- xml2::xml_attrs(series) + + obs <- xml2::xml_find_all(series, ".//Obs", ns) + if (length(obs) == 0) { + obs <- xml2::xml_find_all(series, ".//Obs", ns) + } + + purrr::map_dfr(obs, function(o) { + row <- c(series_atts, list( + time = xml2::xml_attr(o, "TIME_PERIOD"), + value = as.numeric(xml2::xml_attr(o, "OBS_VALUE")) + )) + data.frame(row, stringsAsFactors = FALSE) + }) + }) + + return(tibble::as_tibble(result)) + } + + # JSON-stat handling (Statistics API specific) + if (grepl("format=JSON", link, ignore.case = TRUE)) { + message("Downloading JSON-stat 2.0...") + json_data <- jsonlite::fromJSON(link) + value_data <- json_data$value + dims <- json_data$dimension + if (is.null(value_data) && !is.null(json_data$dataset$value)) { + value_data <- json_data$dataset$value + dims <- json_data$dataset$dimension + } + if (!is.null(value_data) && !is.null(dims)) { + dim_ids <- names(dims) + dim_lists <- lapply(dim_ids, function(id) names(dims[[id]]$category$index)) + names(dim_lists) <- dim_ids + grid <- expand.grid(dim_lists, stringsAsFactors = FALSE) + values <- rep(NA_real_, nrow(grid)) + idx <- as.integer(names(value_data)) + 1 + values[idx] <- as.numeric(unlist(value_data)) + grid$value <- values + message("Returning flattened tibble.") + return(tibble::as_tibble(grid)) + } + flat <- jsonlite::flatten(json_data) + return(as_tibble_safe(flat)) + } + + stop("Unsupported or unknown format.") +} From ea5d4c91762e58beebe00d855af358811bdcfb65 Mon Sep 17 00:00:00 2001 From: ake123 Date: Tue, 8 Jul 2025 16:24:17 +0300 Subject: [PATCH 13/62] correction --- NAMESPACE | 1 + R/get_eurostat_link.R | 79 +++++++++++++++++++++++----------------- man/get_eurostat_link.Rd | 20 ++++++++++ 3 files changed, 67 insertions(+), 33 deletions(-) create mode 100644 man/get_eurostat_link.Rd diff --git a/NAMESPACE b/NAMESPACE index 13ddb6a3..c57377bf 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,6 +15,7 @@ export(get_eurostat_folder) export(get_eurostat_geospatial) export(get_eurostat_interactive) export(get_eurostat_json) +export(get_eurostat_link) export(get_eurostat_local) export(get_eurostat_sdmx) export(get_eurostat_toc) diff --git a/R/get_eurostat_link.R b/R/get_eurostat_link.R index 76e22eaf..36f5025c 100644 --- a/R/get_eurostat_link.R +++ b/R/get_eurostat_link.R @@ -10,63 +10,83 @@ get_eurostat_link <- function(link, destfile = NULL) { if (!is.character(link) || length(link) != 1) stop("Provide a valid Eurostat link.") if (!grepl("ec\\.europa\\.eu/eurostat/", link)) stop("Only Eurostat links allowed.") - + as_tibble_safe <- function(x) { if (is.data.frame(x)) tibble::as_tibble(x) else tibble::tibble(data = list(x)) } - + download_content <- function(link) { - resp <- httr::GET(link) - httr::stop_for_status(resp) - raw <- httr::content(resp, as = "raw") - if (httr::http_type(resp) == "application/gzip" || grepl("compress=true", link, ignore.case = TRUE)) { + resp <- httr2::request(link) |> + httr2::req_user_agent("eurostat-r-client") |> + httr2::req_perform() + + httr2::resp_check_status(resp) + + raw <- httr2::resp_body_raw(resp) + + if (httr2::resp_content_type(resp) == "application/gzip" || grepl("compress=true", link, ignore.case = TRUE)) { message("Decompressing content...") raw <- memDecompress(raw, type = "gzip", asChar = TRUE) } else { raw <- rawToChar(raw) } + raw } - - # # Comext DS-prefixed datasets must use correct base + + # Comext DS-prefixed datasets must use correct base if (grepl("/comext/dissemination", link)) { - # Extract dataset portion safely dataset_match <- regmatches(link, regexpr("/ds-[0-9]+", tolower(link))) - if (length(dataset_match) == 0) { stop("Comext API requires DS-prefixed dataset codes (e.g., ds-056120).") } } - + # TSV format if (grepl("format=TSV", link, ignore.case = TRUE) || grepl("\\.tsv", link, ignore.case = TRUE)) { message("Downloading TSV...") text_content <- download_content(link) - return(as_tibble_safe(read.delim(text = text_content, check.names = FALSE, sep = "\t"))) + + df <- read.delim( + text = text_content, + sep = "\t", + check.names = FALSE, + na.strings = c(":", ": ") + ) + + # Unpack first column + first_col_name <- names(df)[1] + dim_names <- strsplit(first_col_name, ",", fixed = TRUE)[[1]] + dim_values <- strsplit(df[[1]], ",", fixed = TRUE) + dim_matrix <- do.call(rbind, dim_values) + dim_df <- as.data.frame(dim_matrix, stringsAsFactors = FALSE) + colnames(dim_df) <- dim_names + + # Optional: fix backslash column name + names(dim_df)[grepl("\\\\", names(dim_df))] <- sub("\\\\.*", "", names(dim_df)[grepl("\\\\", names(dim_df))]) + + final_df <- dplyr::bind_cols(dim_df, df[-1]) + return(tibble::as_tibble(final_df)) } - + + + + # SDMX-CSV if (grepl("format=csvdata", link, ignore.case = TRUE) || grepl("\\.csv", link, ignore.case = TRUE)) { message("Downloading SDMX-CSV 1.0...") text_content <- download_content(link) - lines <- strsplit(text_content, "\n", fixed = TRUE)[[1]] - - # Detect first line with clear header fields data_start <- which(sapply(lines, function(l) { fields <- strsplit(l, ",", fixed = TRUE)[[1]] length(fields) > 1 && all(nzchar(fields) | fields == "") }))[1] - if (is.na(data_start)) stop("Could not detect valid SDMX-CSV header for Comext dataset.") - clean_text <- paste(lines[data_start:length(lines)], collapse = "\n") - - # Explicit row.names = NULL prevents first column being treated as row names return(as_tibble_safe(read.csv(text = clean_text, check.names = FALSE, row.names = NULL))) } - + # SDMX-ML 2.1 StructureSpecific if (grepl("format=structurespecificdata", link, ignore.case = TRUE) && grepl("formatVersion=2.1", link, ignore.case = TRUE)) { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") @@ -84,7 +104,7 @@ get_eurostat_link <- function(link, destfile = NULL) { }) return(tibble::as_tibble(result)) } - + # SDMX-ML 2.1 GenericData if (grepl("format=genericdata", link, ignore.case = TRUE)) { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") @@ -103,30 +123,24 @@ get_eurostat_link <- function(link, destfile = NULL) { }) return(tibble::as_tibble(result)) } - + # SDMX-ML 3.0 StructureSpecific if (grepl("/sdmx/3.0/data", link) && (grepl("\\.xml", link, ignore.case = TRUE) || !grepl("format=", link, ignore.case = TRUE))) { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") message("Downloading SDMX-ML 3.0 StructureSpecific...") - xml_text <- download_content(link) doc <- xml2::read_xml(xml_text) ns <- xml2::xml_ns(doc) - series_nodes <- xml2::xml_find_all(doc, ".//m:Series", ns) if (length(series_nodes) == 0) { series_nodes <- xml2::xml_find_all(doc, ".//Series", ns) } - result <- purrr::map_dfr(series_nodes, function(series) { - #series_atts <- as.list(xml2::xml_attrs(series)) series_atts <- xml2::xml_attrs(series) - obs <- xml2::xml_find_all(series, ".//Obs", ns) if (length(obs) == 0) { obs <- xml2::xml_find_all(series, ".//Obs", ns) } - purrr::map_dfr(obs, function(o) { row <- c(series_atts, list( time = xml2::xml_attr(o, "TIME_PERIOD"), @@ -135,11 +149,10 @@ get_eurostat_link <- function(link, destfile = NULL) { data.frame(row, stringsAsFactors = FALSE) }) }) - return(tibble::as_tibble(result)) } - - # JSON-stat handling (Statistics API specific) + + # JSON-stat handling if (grepl("format=JSON", link, ignore.case = TRUE)) { message("Downloading JSON-stat 2.0...") json_data <- jsonlite::fromJSON(link) @@ -164,6 +177,6 @@ get_eurostat_link <- function(link, destfile = NULL) { flat <- jsonlite::flatten(json_data) return(as_tibble_safe(flat)) } - + stop("Unsupported or unknown format.") } diff --git a/man/get_eurostat_link.Rd b/man/get_eurostat_link.Rd new file mode 100644 index 00000000..14ce98cd --- /dev/null +++ b/man/get_eurostat_link.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_eurostat_link.R +\name{get_eurostat_link} +\alias{get_eurostat_link} +\title{Download Eurostat Data from API Link (robust, no list-columns, async-aware)} +\usage{ +get_eurostat_link(link, destfile = NULL) +} +\arguments{ +\item{link}{Eurostat "Copy API link" URL or Data Browser download link} + +\item{destfile}{Optional file path for saving raw files (only for Excel)} +} +\value{ +Tibble for all supported formats +} +\description{ +Supports TSV, SDMX-CSV, SDMX-ML, JSON-stat, and Spreadsheet (.xlsx) formats. +Always returns a tibble where possible. +} From dfb9be4a0de6c34a28de9420d3fc964d96e7e319 Mon Sep 17 00:00:00 2001 From: ake123 Date: Thu, 10 Jul 2025 15:41:01 +0300 Subject: [PATCH 14/62] adding verbose --- R/get_eurostat.R | 147 +++++++++++++++++---------------- R/get_eurostat_async.R | 9 +- R/get_eurostat_geospatial.R | 10 +-- R/get_eurostat_link.R | 21 ++--- R/get_eurostat_raw.R | 12 +-- R/get_eurostat_sdmx.R | 13 +-- R/set_eurostat_cache.R | 14 ++-- man/get_eurostat.Rd | 1 + man/get_eurostat_async.Rd | 3 +- man/get_eurostat_geospatial.Rd | 1 + man/get_eurostat_link.Rd | 2 +- man/get_eurostat_sdmx.Rd | 3 +- 12 files changed, 120 insertions(+), 116 deletions(-) diff --git a/R/get_eurostat.R b/R/get_eurostat.R index b1ccb80c..ef71f374 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -10,19 +10,19 @@ #' @param filters #' A named list of filters. Names of list objects are Eurostat #' variable codes and values are vectors of observation codes. If `NULL` -#' (default) the whole dataset is returned. See details for more information +#' (default) the whole dataset is returned. See details for more information #' on filters and limitations per query. #' @param time_format #' a string giving a type of the conversion of the time column from the #' eurostat format. The default argument "`date`" converts to a [Date()] class -#' with the date being the first day of the period. A "`date_last`" argument -#' converts the dataset date to a [Date()] class object with the difference +#' with the date being the first day of the period. A "`date_last`" argument +#' converts the dataset date to a [Date()] class object with the difference #' that the exact date is the last date of the period. Period can be year, -#' semester (half year), quarter, month, or week (See [eurotime2date()] for +#' semester (half year), quarter, month, or week (See [eurotime2date()] for #' more information). #' Argument "`num`" converts the date into a numeric (integer) meaning that #' the first day of the year 2000 is close to 2000.01 and the last day of the -#' year is close to 2000.99 (see [eurotime2num()] for more information). +#' year is close to 2000.99 (see [eurotime2num()] for more information). #' Using the argument "`raw`" preserves the dates as they were in the original #' Eurostat data. #' @param type @@ -37,7 +37,7 @@ #' "Y" (or "A") = annual, "S" = semi-annual / semester, "Q" = quarterly, #' "M" = monthly, "W" = weekly. For all frequencies in same data #' frame `time_format = "raw"` should be used. -#' @param lang 2-letter language code, default is "`en`" (English), other +#' @param lang 2-letter language code, default is "`en`" (English), other #' options are "`fr`" (French) and "`de`" (German). Used for labeling datasets. #' @param cache #' a logical whether to do caching. Default is `TRUE`. @@ -53,7 +53,7 @@ #' @param compress_file #' a logical whether to compress the RDS-file in caching. Default is `TRUE`. #' @param stringsAsFactors -#' if `TRUE` (the default) variables are converted to factors in the original +#' if `TRUE` (the default) variables are converted to factors in the original #' Eurostat order. If `FALSE` they are returned as strings. #' @param keepFlags #' a logical whether the flags (e.g. "confidential", @@ -63,13 +63,13 @@ #' Also possible non-real zero "0n" is indicated in flags column. #' Flags are not available for eurostat API, so `keepFlags` #' can not be used with a `filters`. -#' @param use.data.table Use faster data.table functions? Default is FALSE. +#' @param use.data.table Use faster data.table functions? Default is FALSE. #' On Windows requires that RTools is installed. #' @param legacy.data.output Use legacy column names and data object structure. #' Default is FALSE. If TRUE, the object will try to emulate the naming #' conventions of eurostat package version 3.7.x and earlier. #' @inheritDotParams get_eurostat_json -#' +#' #' @inherit eurostat-package references #' #' @inheritSection eurostat-package Eurostat: Copyright notice and free re-use of data @@ -80,7 +80,7 @@ #' #' @author #' Przemyslaw Biecek, Leo Lahti, Janne Huovari, Markus Kainu and Pyry Kantanen -#' +#' #' @details #' Datasets are downloaded from #' [the Eurostat SDMX 2.1 API](https://wikis.ec.europa.eu/display/EUROSTATHELP/Transition+-+from+Eurostat+Bulk+Download+to+API) @@ -110,7 +110,7 @@ #' . The Eurostat #' database gives codes in the Data Navigation Tree after every dataset #' in parenthesis. -#' +#' #' @return #' a tibble. #' @@ -121,10 +121,10 @@ #' dimensions are missing on particular time. In JSON API missing values are #' dropped only if all dimensions are missing on all times. The data from #' bulk download facility can be completed for example with [tidyr::complete()]. -#' +#' #' @seealso #' [search_eurostat()], [label_eurostat()] -#' +#' #' @examplesIf check_access_to_data() #' \dontrun{ #' k <- get_eurostat("nama_10_lp_ulc") @@ -190,6 +190,7 @@ get_eurostat <- function(id, keepFlags = FALSE, use.data.table = FALSE, legacy.data.output = FALSE, + verbose = TRUE, ...) { # Check if you have access to ec.europe.eu. @@ -201,22 +202,22 @@ get_eurostat <- function(id, Please check your connection and/or review your proxy settings") # nocov end } - + # For better code clarity, use only NULL in code if (is.character(filters) && identical(tolower(filters), "none")) { filters <- NULL } else if (is.character(filters) && !identical(tolower(filters), "null")) { - message("Non-standard filters argument. Using argument 'filters = NULL'") + if (verbose) message("Non-standard filters argument. Using argument 'filters = NULL'") filters <- NULL } # Inform user with message if keepFlags == TRUE cannot be delivered if (keepFlags && !is.null(filters)) { - message("The keepFlags argument of the get_eurostat function + if (verbose) message("The keepFlags argument of the get_eurostat function can be used only without filters. No Flags returned.") keepFlags <- FALSE } - + # Sanity check type <- tolower(type) time_format <- tolower(time_format) @@ -312,7 +313,7 @@ get_eurostat <- function(id, if (any(grepl(query_hash, cache_list_current))) { # Check if the same query has already been made - message("Dataset query already saved in cache_list.json...") + if (verbose) message("Dataset query already saved in cache_list.json...") } else if (!any(grepl(query_hash, cache_list_current))) { # If query was not made, append cache_list with new json query if (length(cache_list_current) == 0) { @@ -447,7 +448,7 @@ get_eurostat <- function(id, # which lists files downloaded and saved to cache but maybe in some # situations the cached file could go missing? Not very likely though - message(paste("Reading cache file", cache_file_bulk, "and filtering it")) + if (verbose) message(paste("Reading cache file", cache_file_bulk, "and filtering it")) y <- readRDS(cache_file_bulk) for (i in seq_along(filters)) { y <- dplyr::filter(y, @@ -456,18 +457,18 @@ get_eurostat <- function(id, # y <- y_raw } else if (file.exists(cache_file)) { cf <- path.expand(cache_file) - message(paste("Reading cache file", cf)) + if (verbose) message(paste("Reading cache file", cf)) y <- readRDS(cache_file) - message(paste("Table ", id, " read from cache file: ", cf)) + if (verbose) message(paste("Table ", id, " read from cache file: ", cf)) } # if update_cache = TRUE or cache file does not yet exist # -> save cache file to cache directory if (cache && (update_cache || !file.exists(cache_file))) { saveRDS(y, file = cache_file, compress = compress_file) - message("Table ", id, " cached at ", path.expand(cache_file)) + if (verbose) message("Table ", id, " cached at ", path.expand(cache_file)) } - + if (legacy.data.output){ y <- legacy_data_format(y) } @@ -484,36 +485,36 @@ get_eurostat <- function(id, #' as object names. The datasets are downloaded from SDMX API as TSV files, #' meaning that they are returned without filtering. No filters can be #' provided using this function. -#' +#' #' Please do not attempt to download too many datasets or the whole database -#' at once. The number of datasets that can be downloaded at once is hardcoded -#' to 20. The function also asks the user for confirmation if the number of +#' at once. The number of datasets that can be downloaded at once is hardcoded +#' to 20. The function also asks the user for confirmation if the number of #' datasets in a folder is more than 10. This is by design to discourage #' straining Eurostat API. #' @param code Folder code from Eurostat Table of Contents. #' @param env Name of the environment where downloaded datasets are assigned. #' Default is .EurostatEnv. If NULL, datasets are returned as a list object. -#' +#' #' @inheritSection eurostat-package Data source: Eurostat Table of Contents #' @inheritSection eurostat-package Data source: Eurostat SDMX 2.1 Dissemination API -#' +#' #' @author Pyry Kantanen -#' +#' #' @inherit set_eurostat_toc seealso -#' +#' #' @importFrom stringr str_glue #' @importFrom utils menu -#' -#' +#' +#' #' @export get_eurostat_folder <- function(code, env = .EurostatEnv) { - + # Limit after which the function prompts the user whether they really want # to proceed soft_limit <- 10 # Limit that cannot be crossed with this function hard_limit <- 20 - + toc <- get_eurostat_toc() if (toc[["type"]][which(toc[["code"]] == code)] != "folder") { warning("The code you provided is not a folder.") @@ -522,7 +523,7 @@ get_eurostat_folder <- function(code, env = .EurostatEnv) { children <- toc_list_children(code) # Filter out potential subfolders children <- children[which(children$type %in% c("dataset", "table")), ] - + if (nrow(children) == 0) { warning("The folder code you provided did not have any items.") return(invisible()) @@ -545,7 +546,7 @@ get_eurostat_folder <- function(code, env = .EurostatEnv) { message("Proceeding to download datasets in folder..."), return(invisible())) } - + if (!is.null(env)) { for (i in seq_len(nrow(children))) { dataset <- get_eurostat(children$code[i], cache = TRUE) @@ -574,28 +575,28 @@ get_eurostat_folder <- function(code, env = .EurostatEnv) { #' @title Get Eurostat data interactive #' @description -#' A simple interactive helper function to go through the steps of downloading -#' and/or finding suitable eurostat datasets. -#' +#' A simple interactive helper function to go through the steps of downloading +#' and/or finding suitable eurostat datasets. +#' #' @details #' This function is intended to enable easy exploration of different eurostat #' package functionalities and functions. In order to not drown the end user -#' in endless menus this function does not allow for setting +#' in endless menus this function does not allow for setting #' all possible [get_eurostat()] function arguments. It is possible to set #' `time_format`, `type`, `lang`, `stringsAsFactors`, `keepFlags`, and -#' `use.data.table` in the interactive menus. -#' -#' In some datasets setting these parameters may result in a -#' "Error in label_eurostat" error, for example: -#' "labels for XXXXXX includes duplicated labels in the Eurostat dictionary". +#' `use.data.table` in the interactive menus. +#' +#' In some datasets setting these parameters may result in a +#' "Error in label_eurostat" error, for example: +#' "labels for XXXXXX includes duplicated labels in the Eurostat dictionary". #' In these cases, and with other more complex queries, please #' use [get_eurostat()] function directly. -#' -#' @param code +#' +#' @param code #' A unique identifier / code for the dataset of interest. If code is not #' known [search_eurostat()] function can be used to search Eurostat table #' of contents. -#' +#' #' @seealso [get_eurostat()] #' @importFrom stringr str_glue #' @importFrom utils capture.output @@ -610,9 +611,9 @@ get_eurostat_interactive <- function(code = NULL) { "fr", "de" ) - + if (is.null(code)) { - + search_term <- readline(prompt = "Enter search term for data: ") results <- search_eurostat(pattern = search_term, lang = lang_selection) code_and_title <- paste0("[", results$code, "] ", results$title) @@ -627,9 +628,9 @@ get_eurostat_interactive <- function(code = NULL) { } code <- results$code[choice] } - + download_selection <- switch( - menu(choices = c("Yes", "No"), + menu(choices = c("Yes", "No"), title = "Download the dataset?") + 1, return(invisible()), TRUE, @@ -638,16 +639,16 @@ get_eurostat_interactive <- function(code = NULL) { # Set manual_selection to FALSE here to make it possible to print code # for downloading dataset later manual_selection <- FALSE - + if (download_selection) { manual_selection <- switch( - menu(choices = c("Default", "Manually selected"), + menu(choices = c("Default", "Manually selected"), title = "Would you like to use default download arguments or set them manually?") + 1, return(invisible()), FALSE, TRUE ) - + if (manual_selection) { time_format_selection <- switch( menu(choices = c("Convert to date, first day of the period (2000-04-01) (default)", @@ -660,7 +661,7 @@ get_eurostat_interactive <- function(code = NULL) { "num", "raw" ) - + type_selection <- switch( menu(choices = c("Return categorical variables as short codes (default)", "Return categorical variables in labeled (long) format"), @@ -669,7 +670,7 @@ get_eurostat_interactive <- function(code = NULL) { "code", "label" ) - + stringsAsFactors_selection <- switch( menu(choices = c("Return categorical data as characters (default)", "Convert categorical data into factors"), @@ -678,7 +679,7 @@ get_eurostat_interactive <- function(code = NULL) { FALSE, TRUE ) - + keepFlags_selection <- switch( menu(choices = c("Do not return flags, just remove them (default)", "Return flags in separate column")) + 1, @@ -686,7 +687,7 @@ get_eurostat_interactive <- function(code = NULL) { FALSE, TRUE ) - + use.data.table_selection <- switch( menu(choices = c("Do not use data.table functions (default", "Use data.table functions"), @@ -726,33 +727,33 @@ get_eurostat_interactive <- function(code = NULL) { eurostat_data <- get_eurostat(id = code) } } - + tempfile_for_sinking <- tempfile() - + # eurostat_data <- get_eurostat(id = code) print_citation <- switch( - menu(choices = c("Yes", "No"), + menu(choices = c("Yes", "No"), title = "Print dataset citation?") + 1, - return(invisible()), - TRUE, + return(invisible()), + TRUE, FALSE ) - + if (print_citation) { citation <- get_bibentry(code, lang = lang_selection) capture.output(cat("##### DATASET CITATION:\n\n"), file = tempfile_for_sinking, append = TRUE) capture.output(print(citation), file = tempfile_for_sinking, append = TRUE) capture.output(cat("\n"), file = tempfile_for_sinking, append = TRUE) } - + print_code <- switch( - menu(choices = c("Yes", "No"), + menu(choices = c("Yes", "No"), title = "Print code for downloading dataset?") + 1, - return(invisible()), + return(invisible()), TRUE, FALSE ) - + if (print_code == TRUE && manual_selection == TRUE) { capture.output(cat("##### DOWNLOAD PARAMETERS:\n\n")) capture.output(print(stringr::str_glue(paste0("get_eurostat(id = '{code}', time_format = '{time_format_selection}', ", @@ -766,23 +767,23 @@ get_eurostat_interactive <- function(code = NULL) { capture.output(print(stringr::str_glue("get_eurostat(id = '{code}')")), file = tempfile_for_sinking, append = TRUE) capture.output(cat("\n"), file = tempfile_for_sinking, append = TRUE) } - + if (exists("eurostat_data")) { print_code <- switch( - menu(choices = c("Yes", "No"), + menu(choices = c("Yes", "No"), title = "Print dataset fixity checksum?") + 1, return(invisible()), TRUE, FALSE ) - + if (print_code) { capture.output(cat("##### FIXITY CHECKSUM:\n\n"), file = tempfile_for_sinking, append = TRUE) capture.output(print(stringr::str_glue("Fixity checksum (md5) for dataset {code}: {eurostat:::fixity_checksum(eurostat_data, algorithm = 'md5')}")), file = tempfile_for_sinking, append = TRUE) capture.output(cat("\n"), file = tempfile_for_sinking, append = TRUE) } } - + if (exists("eurostat_data")) { cat(readLines(tempfile_for_sinking), sep = "\n") return(eurostat_data) diff --git a/R/get_eurostat_async.R b/R/get_eurostat_async.R index bcb7ca8f..e5462937 100644 --- a/R/get_eurostat_async.R +++ b/R/get_eurostat_async.R @@ -23,7 +23,8 @@ get_eurostat_async <- function( legacy.data.output = FALSE, wait = 1, max_wait = 60, - compressed = TRUE + compressed = TRUE, + verbose = TRUE ) { stopifnot(nzchar(id)) lang <- check_lang(lang) @@ -56,7 +57,7 @@ get_eurostat_async <- function( outcome_index <- which(c(length(available_pos) > 0, length(fail_pos) > 0, TRUE))[1] outcome <- c("success", "fail", "timeout")[outcome_index] - message(sprintf("Final status after %d seconds: %s", wait_times[length(statuses)], outcome)) + if (verbose) message(sprintf("Final status after %d seconds: %s", wait_times[length(statuses)], outcome)) handlers <- list( "success" = function() { @@ -72,7 +73,7 @@ get_eurostat_async <- function( return(TRUE) } if (i < retries) { - message("Download failed (HTTP ", httr::status_code(res), "). Retrying...") + if (verbose) message("Download failed (HTTP ", httr::status_code(res), "). Retrying...") Sys.sleep(delay) } } @@ -92,7 +93,7 @@ get_eurostat_async <- function( if (nrow(zip_list) == 0) stop("Empty ZIP archive received") csv_file <- unzip(tfile, exdir = tempdir())[1] } else { - message("Expected ZIP archive, but received CSV instead — continuing anyway.") + if (verbose) message("Expected ZIP archive, but received CSV instead — continuing anyway.") csv_file <- tfile } } else { diff --git a/R/get_eurostat_geospatial.R b/R/get_eurostat_geospatial.R index 6f7525c6..d99ff4a0 100755 --- a/R/get_eurostat_geospatial.R +++ b/R/get_eurostat_geospatial.R @@ -121,10 +121,10 @@ get_eurostat_geospatial <- function(output_class = "sf", nuts_level = "all", year = "2016", cache = TRUE, update_cache = FALSE, cache_dir = NULL, crs = "4326", - make_valid = "DEPRECATED", ...) { + make_valid = "DEPRECATED", verbose = TRUE, ...) { # nocov start if (!requireNamespace("sf")) { - message("'sf' package is required for geospatial functionalities") + if (verbose) message("'sf' package is required for geospatial functionalities") return(invisible()) } # nocov end @@ -133,7 +133,7 @@ get_eurostat_geospatial <- function(output_class = "sf", # Deprecation messages stopifnot(length(output_class) == 1L) if (output_class == "spdf") { - message("'spdf' output deprecated. Switching to sf output") + if (verbose) message("'spdf' output deprecated. Switching to sf output") output_class <- "sf" } @@ -189,7 +189,7 @@ get_eurostat_geospatial <- function(output_class = "sf", } # nocov end - message(paste0( + if (verbose) message(paste0( "Extracting data using giscoR package, please report issues", " on https://github.com/rOpenGov/giscoR/issues" )) @@ -205,7 +205,7 @@ get_eurostat_geospatial <- function(output_class = "sf", file.path(tempdir(), "eurostat") )) { # eurostat not set, using default giscoR cache management - message("Cache management as per giscoR. see 'giscoR::gisco_get_nuts()'") + if (verbose) message("Cache management as per giscoR. see 'giscoR::gisco_get_nuts()'") } else { cache_dir <- eur_helper_cachedir(cache_dir) } diff --git a/R/get_eurostat_link.R b/R/get_eurostat_link.R index 36f5025c..4cf5915f 100644 --- a/R/get_eurostat_link.R +++ b/R/get_eurostat_link.R @@ -7,7 +7,7 @@ #' @param destfile Optional file path for saving raw files (only for Excel) #' @return Tibble for all supported formats #' @export -get_eurostat_link <- function(link, destfile = NULL) { +get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { if (!is.character(link) || length(link) != 1) stop("Provide a valid Eurostat link.") if (!grepl("ec\\.europa\\.eu/eurostat/", link)) stop("Only Eurostat links allowed.") @@ -26,7 +26,7 @@ get_eurostat_link <- function(link, destfile = NULL) { raw <- httr2::resp_body_raw(resp) if (httr2::resp_content_type(resp) == "application/gzip" || grepl("compress=true", link, ignore.case = TRUE)) { - message("Decompressing content...") + if (verbose) message("Decompressing content...") raw <- memDecompress(raw, type = "gzip", asChar = TRUE) } else { raw <- rawToChar(raw) @@ -45,7 +45,7 @@ get_eurostat_link <- function(link, destfile = NULL) { # TSV format if (grepl("format=TSV", link, ignore.case = TRUE) || grepl("\\.tsv", link, ignore.case = TRUE)) { - message("Downloading TSV...") + if (verbose) message("Downloading TSV...") text_content <- download_content(link) df <- read.delim( @@ -70,12 +70,9 @@ get_eurostat_link <- function(link, destfile = NULL) { return(tibble::as_tibble(final_df)) } - - - # SDMX-CSV if (grepl("format=csvdata", link, ignore.case = TRUE) || grepl("\\.csv", link, ignore.case = TRUE)) { - message("Downloading SDMX-CSV 1.0...") + if (verbose) message("Downloading SDMX-CSV 1.0...") text_content <- download_content(link) lines <- strsplit(text_content, "\n", fixed = TRUE)[[1]] data_start <- which(sapply(lines, function(l) { @@ -90,7 +87,7 @@ get_eurostat_link <- function(link, destfile = NULL) { # SDMX-ML 2.1 StructureSpecific if (grepl("format=structurespecificdata", link, ignore.case = TRUE) && grepl("formatVersion=2.1", link, ignore.case = TRUE)) { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") - message("Downloading SDMX-ML 2.1 StructureSpecific...") + if (verbose) message("Downloading SDMX-ML 2.1 StructureSpecific...") xml_text <- download_content(link) doc <- xml2::read_xml(xml_text) ns <- xml2::xml_ns(doc) @@ -108,7 +105,7 @@ get_eurostat_link <- function(link, destfile = NULL) { # SDMX-ML 2.1 GenericData if (grepl("format=genericdata", link, ignore.case = TRUE)) { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") - message("Downloading SDMX-ML 2.1 GenericData...") + if (verbose) message("Downloading SDMX-ML 2.1 GenericData...") xml_text <- download_content(link) doc <- xml2::read_xml(xml_text) ns <- xml2::xml_ns(doc) @@ -127,7 +124,7 @@ get_eurostat_link <- function(link, destfile = NULL) { # SDMX-ML 3.0 StructureSpecific if (grepl("/sdmx/3.0/data", link) && (grepl("\\.xml", link, ignore.case = TRUE) || !grepl("format=", link, ignore.case = TRUE))) { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") - message("Downloading SDMX-ML 3.0 StructureSpecific...") + if (verbose) message("Downloading SDMX-ML 3.0 StructureSpecific...") xml_text <- download_content(link) doc <- xml2::read_xml(xml_text) ns <- xml2::xml_ns(doc) @@ -154,7 +151,7 @@ get_eurostat_link <- function(link, destfile = NULL) { # JSON-stat handling if (grepl("format=JSON", link, ignore.case = TRUE)) { - message("Downloading JSON-stat 2.0...") + if (verbose) message("Downloading JSON-stat 2.0...") json_data <- jsonlite::fromJSON(link) value_data <- json_data$value dims <- json_data$dimension @@ -171,7 +168,7 @@ get_eurostat_link <- function(link, destfile = NULL) { idx <- as.integer(names(value_data)) + 1 values[idx] <- as.numeric(unlist(value_data)) grid$value <- values - message("Returning flattened tibble.") + if (verbose) message("Returning flattened tibble.") return(tibble::as_tibble(grid)) } flat <- jsonlite::flatten(json_data) diff --git a/R/get_eurostat_raw.R b/R/get_eurostat_raw.R index edb01459..c4e9a897 100755 --- a/R/get_eurostat_raw.R +++ b/R/get_eurostat_raw.R @@ -7,9 +7,9 @@ #' years and column names are years with preceding X. Data is in #' character format as it contains values together with eurostat #' flags for data. -#' @seealso +#' @seealso #' [get_eurostat()] -#' +#' #' @inheritSection eurostat-package Data source: Eurostat SDMX 2.1 Dissemination API #' @inheritSection eurostat-package Eurostat: Copyright notice and free re-use of data #' @inheritSection eurostat-package Citing Eurostat data @@ -56,7 +56,7 @@ get_eurostat_raw <- function(id, use.data.table = FALSE) { on.exit(options(op), add = TRUE) utils::download.file(url, tfile) } - + if (!use.data.table) { # OLD CODE dat <- readr::read_tsv(gzfile(tfile), @@ -70,10 +70,10 @@ get_eurostat_raw <- function(id, use.data.table = FALSE) { na.strings = ":", header = TRUE, colClasses = "character") - + # OLD CODE - # data.table object does not need to be converted into a tibble at this - # point as it will handled by data.table functions in tidy_eurostat. + # data.table object does not need to be converted into a tibble at this + # point as it will handled by data.table functions in tidy_eurostat. # dat <- tibble::as_tibble(dat) } diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 139a5bd9..5f81b0da 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -28,7 +28,8 @@ get_eurostat_sdmx <- function( keepFlags = FALSE, legacy.data.output = FALSE, wait = 10, - max_wait = 600 + max_wait = 600, + verbose = TRUE ) { # Check if you have access to ec.europe.eu. @@ -102,7 +103,7 @@ get_eurostat_sdmx <- function( content <- httr::content(res, as = "parsed", encoding = "UTF-8") async_id <- xml2::xml_text(xml2::xml_find_first(content, ".//id")) if (!is.na(async_id) && nzchar(async_id)) { - message("Async mode triggered. Downloading via async ID: ", async_id) + if (verbose) message("Async mode triggered. Downloading via async ID: ", async_id) return(get_eurostat_async( id = async_id, time_format = time_format, @@ -120,9 +121,9 @@ get_eurostat_sdmx <- function( message(" Response was XML, but no async found. Treating as sync (unexpected).") } } else if (grepl("csv", ctype, ignore.case = TRUE)) { - message("Synchronous mode: CSV data returned directly.") + if (verbose) message("Synchronous mode: CSV data returned directly.") } else { - message("Unexpected content type: ", ctype) + if (verbose) message("Unexpected content type: ", ctype) } @@ -431,7 +432,7 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en") { if (inherits(y, "data.table")) { for (i in seq_len(nrow(dimension_df))) { resourceID <- dimension_df$codelist_id[[i]] - message(paste("Building codelist URL for resourceID:", resourceID)) + if (verbose) message(paste("Building codelist URL for resourceID:", resourceID)) codelist_url <- paste0( api_base_uri, "/sdmx/2.1/", @@ -449,7 +450,7 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en") { column_to_handle <- dimension_df$dimension_id[[i]] codes_to_label <- unique(y[[column_to_handle]]) codelist_subset <- codelist[which(codelist[,1] %in% codes_to_label),] - message(paste("Labeling dimension (column):", column_to_handle)) + if (verbose) message(paste("Labeling dimension (column):", column_to_handle)) for (j in seq_len(nrow(codelist_subset))) { data.table::set(y, i=which(y[[column_to_handle]] == codelist_subset[j,1]), j = column_to_handle, value = codelist_subset[j,2]) } diff --git a/R/set_eurostat_cache.R b/R/set_eurostat_cache.R index eaf66c6e..eee4ad81 100644 --- a/R/set_eurostat_cache.R +++ b/R/set_eurostat_cache.R @@ -210,9 +210,9 @@ eur_helper_cachedir <- function(cache_dir = NULL) { #' @description #' Parses cache_list.json file and returns a data.frame #' @return -#' A data.frame object with 3 columns: dataset code, download date and +#' A data.frame object with 3 columns: dataset code, download date and #' query md5 hash -#' +#' #' @inheritParams get_eurostat #' @importFrom jsonlite fromJSON #' @export @@ -220,18 +220,18 @@ list_eurostat_cache_items <- function(cache_dir = NULL) { if (is.null(cache_dir)) { cache_dir <- eur_helper_detect_cache_dir() } - + path <- paste0(cache_dir, "/cache_list.json") - + if (!file.exists(path)) { warning("No cache_list.json file found!") return(invisible()) } - + json_file <- jsonlite::fromJSON(path) # return(json_file) columns <- c("code", "download_date", "query_md5_hash") - df <- data.frame(matrix(nrow = 0, ncol = length(columns))) + df <- data.frame(matrix(nrow = 0, ncol = length(columns))) for (i in seq_len(length(json_file))) { df_i <- data.frame( code = json_file[[i]][["id"]], @@ -241,4 +241,4 @@ list_eurostat_cache_items <- function(cache_dir = NULL) { df <- rbind(df, df_i) } df -} \ No newline at end of file +} diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index c33226b2..a529f81f 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -19,6 +19,7 @@ get_eurostat( keepFlags = FALSE, use.data.table = FALSE, legacy.data.output = FALSE, + verbose = TRUE, ... ) } diff --git a/man/get_eurostat_async.Rd b/man/get_eurostat_async.Rd index 5908b547..9685e579 100644 --- a/man/get_eurostat_async.Rd +++ b/man/get_eurostat_async.Rd @@ -15,7 +15,8 @@ get_eurostat_async( legacy.data.output = FALSE, wait = 1, max_wait = 60, - compressed = TRUE + compressed = TRUE, + verbose = TRUE ) } \arguments{ diff --git a/man/get_eurostat_geospatial.Rd b/man/get_eurostat_geospatial.Rd index 347ec7cb..ecb160d1 100755 --- a/man/get_eurostat_geospatial.Rd +++ b/man/get_eurostat_geospatial.Rd @@ -21,6 +21,7 @@ get_eurostat_geospatial( cache_dir = NULL, crs = "4326", make_valid = "DEPRECATED", + verbose = TRUE, ... ) } diff --git a/man/get_eurostat_link.Rd b/man/get_eurostat_link.Rd index 14ce98cd..e5cce115 100644 --- a/man/get_eurostat_link.Rd +++ b/man/get_eurostat_link.Rd @@ -4,7 +4,7 @@ \alias{get_eurostat_link} \title{Download Eurostat Data from API Link (robust, no list-columns, async-aware)} \usage{ -get_eurostat_link(link, destfile = NULL) +get_eurostat_link(link, destfile = NULL, verbose = TRUE) } \arguments{ \item{link}{Eurostat "Copy API link" URL or Data Browser download link} diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index c66071d3..292e4d6f 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -16,7 +16,8 @@ get_eurostat_sdmx( keepFlags = FALSE, legacy.data.output = FALSE, wait = 10, - max_wait = 600 + max_wait = 600, + verbose = TRUE ) } \arguments{ From b43920be539b544592328dba8056deb646b304a8 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 14:53:07 +0300 Subject: [PATCH 15/62] Fix issue #326 --- R/check_access_to_data.R | 2 +- R/get_eurostat.R | 7 ++++--- R/get_eurostat_json.R | 4 ++-- R/get_eurostat_sdmx.R | 7 ++++--- R/label_eurostat.R | 4 ++-- R/search_eurostat.R | 4 ++-- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/R/check_access_to_data.R b/R/check_access_to_data.R index f5e26776..b061d1d2 100755 --- a/R/check_access_to_data.R +++ b/R/check_access_to_data.R @@ -1,4 +1,4 @@ -#' @title Check access to ec.europe.eu +#' @title Check access to ec.europa.eu #' @description Check if R has access to resources at http://ec.europa.eu #' @author Markus Kainu #' @return a logical. diff --git a/R/get_eurostat.R b/R/get_eurostat.R index 5b7a34e4..fbefcd8d 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -68,6 +68,7 @@ #' @param legacy.data.output Use legacy column names and data object structure. #' Default is FALSE. If TRUE, the object will try to emulate the naming #' conventions of eurostat package version 3.7.x and earlier. +#' @param verbose Output messages when downloading data? Default is `TRUE`. #' @inheritDotParams get_eurostat_json #' #' @inherit eurostat-package references @@ -193,12 +194,12 @@ get_eurostat <- function(id, verbose = TRUE, ...) { - # Check if you have access to ec.europe.eu. - # If dataset is cached, access to ec.europe.eu is not needed + # Check if you have access to ec.europa.eu. + # If dataset is cached, access to ec.europa.eu is not needed # Therefore this is a warning, not a stop if (!check_access_to_data()) { # nocov start - warning("You have no access to ec.europe.eu. + warning("You have no access to ec.europa.eu. Please check your connection and/or review your proxy settings") # nocov end } diff --git a/R/get_eurostat_json.R b/R/get_eurostat_json.R index 0a001fc4..4d1bb222 100755 --- a/R/get_eurostat_json.R +++ b/R/get_eurostat_json.R @@ -120,10 +120,10 @@ get_eurostat_json <- function(id, return(NULL) } - # Check if you have access to ec.europe.eu. + # Check if you have access to ec.europa.eu. if (!check_access_to_data()) { # nocov start - message("You have no access to ec.europe.eu. + message("You have no access to ec.europa.eu. Please check your connection and/or review your proxy settings") # nocov end } diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 789aa754..4ea7a51a 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -7,6 +7,7 @@ #' Download data from SDMX API #' #' @inheritParams get_eurostat +#' @inheritParams get_eurostat_async #' @param agency Either "Eurostat" (default), "Eurostat_comext" #' (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW" #' @@ -32,12 +33,12 @@ get_eurostat_sdmx <- function( verbose = TRUE ) { - # Check if you have access to ec.europe.eu. - # If dataset is cached, access to ec.europe.eu is not needed + # Check if you have access to ec.europa.eu. + # If dataset is cached, access to ec.europa.eu is not needed # Therefore this is a warning, not a stop if (!check_access_to_data()) { # nocov start - stop("You have no access to ec.europe.eu. + stop("You have no access to ec.europa.eu. Please check your connection and/or review your proxy settings") # nocov end } diff --git a/R/label_eurostat.R b/R/label_eurostat.R index bf2ad8bf..e33502df 100755 --- a/R/label_eurostat.R +++ b/R/label_eurostat.R @@ -93,9 +93,9 @@ label_eurostat <- custom_dic = NULL, fix_duplicated = FALSE) { - # Check if you have access to ec.europe.eu. + # Check if you have access to ec.europa.eu. if (!check_access_to_data()) { - message(paste("You have no access to ec.europe.eu. Please check your", + message(paste("You have no access to ec.europa.eu. Please check your", "connection and/or review your proxy settings")) } else { diff --git a/R/search_eurostat.R b/R/search_eurostat.R index ae439dbc..6d56c038 100755 --- a/R/search_eurostat.R +++ b/R/search_eurostat.R @@ -66,9 +66,9 @@ search_eurostat <- function(pattern, column <- "title" } - # Check if you have access to ec.europe.eu. + # Check if you have access to ec.europa.eu. if (!check_access_to_data()) { - message("You have no access to ec.europe.eu. + message("You have no access to ec.europa.eu. Please check your connection and/or review your proxy settings") return(invisible()) } From 3ee0262b33face2ad070623d4a3669a7a1c7031d Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 14:58:26 +0300 Subject: [PATCH 16/62] Fix issue #327 --- R/get_eurostat_geospatial.R | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/R/get_eurostat_geospatial.R b/R/get_eurostat_geospatial.R index d99ff4a0..4e1a5cc2 100755 --- a/R/get_eurostat_geospatial.R +++ b/R/get_eurostat_geospatial.R @@ -18,20 +18,14 @@ #' @param nuts_level Level of NUTS classification of the geospatial data. One of #' "0", "1", "2", "3" or "all" (mimics the original behaviour) #' @param year NUTS release year. One of -#' "2003", "2006", "2010", "2013", "2016" or "2021" -#' @param cache a logical whether to do caching. Default is `TRUE`. -#' @param update_cache a logical whether to update cache. Can be set also with -#' `options(eurostat_update = TRUE)` -#' @param cache_dir a path to a cache directory. See -#' [set_eurostat_cache_dir()]. If `NULL` and the cache dir has not been set -#' globally the file would be stored in the [tempdir()]. +#' "2003", "2006", "2010", "2013", "2016", "2021" or "2024" #' @param crs projection of the map: 4-digit #' [EPSG code](https://spatialreference.org/ref/epsg/). One of: #' * "4326" - WGS84 #' * "3035" - ETRS89 / ETRS-LAEA #' * "3857" - Pseudo-Mercator #' -#' @param make_valid Deprecated +#' @inheritParams get_eurostat #' #' @inheritDotParams giscoR::gisco_get_nuts -epsg #' @@ -120,8 +114,8 @@ get_eurostat_geospatial <- function(output_class = "sf", resolution = "60", nuts_level = "all", year = "2016", cache = TRUE, update_cache = FALSE, - cache_dir = NULL, crs = "4326", - make_valid = "DEPRECATED", verbose = TRUE, ...) { + cache_dir = NULL, crs = "4326", + verbose = TRUE, ...) { # nocov start if (!requireNamespace("sf")) { if (verbose) message("'sf' package is required for geospatial functionalities") @@ -137,10 +131,6 @@ get_eurostat_geospatial <- function(output_class = "sf", output_class <- "sf" } - if (!identical(make_valid, "DEPRECATED")) { - message("'make_valid' argument has been deprecated") - } - # Leaving only specific validations - rest of call would be handled by giscoR output_class <- match.arg(as.character(output_class), c("sf", "df")) From 31ef3980cdf5597ec811d282c4575ba4784d3d21 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 14:59:25 +0300 Subject: [PATCH 17/62] Declare imports, add httr and purrr temporarily to NAMESPACE --- NAMESPACE | 4 ++++ R/get_eurostat_async.R | 9 ++++++--- R/get_eurostat_link.R | 2 ++ R/get_eurostat_local.R | 3 ++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index c57377bf..0039a9e8 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -53,6 +53,9 @@ importFrom(dplyr,coalesce) importFrom(dplyr,filter) importFrom(dplyr,inner_join) importFrom(dplyr,mutate) +importFrom(httr,GET) +importFrom(httr,content) +importFrom(httr,status_code) importFrom(httr2,"%>%") importFrom(httr2,req_error) importFrom(httr2,req_perform) @@ -72,6 +75,7 @@ importFrom(lubridate,dmy) importFrom(lubridate,month) importFrom(lubridate,year) importFrom(lubridate,ymd) +importFrom(purrr,map_dfr) importFrom(readr,col_character) importFrom(readr,cols) importFrom(readr,read_tsv) diff --git a/R/get_eurostat_async.R b/R/get_eurostat_async.R index e5462937..b5b19781 100644 --- a/R/get_eurostat_async.R +++ b/R/get_eurostat_async.R @@ -5,9 +5,12 @@ #' #' @inheritParams get_eurostat_sdmx #' @param id Character. Async request ID from initial SDMX call. -#' @param wait Integer. Seconds between status checks. -#' @param max_wait Integer. Max time to wait in seconds. +#' @param wait Integer. Seconds between status checks. Default is 1 second. +#' @param max_wait Integer. Max time to wait in seconds. Default is 60 seconds. +#' @param compressed Logical. Download data in compressed format? Default is TRUE. +#' @inheritParams get_eurostat #' @importFrom data.table %chin% +#' @importFrom httr GET content status_code #' #' @return A data.frame containing the SDMX-CSV data. #' @@ -93,7 +96,7 @@ get_eurostat_async <- function( if (nrow(zip_list) == 0) stop("Empty ZIP archive received") csv_file <- unzip(tfile, exdir = tempdir())[1] } else { - if (verbose) message("Expected ZIP archive, but received CSV instead — continuing anyway.") + if (verbose) message("Expected ZIP archive, but received CSV instead - continuing anyway.") csv_file <- tfile } } else { diff --git a/R/get_eurostat_link.R b/R/get_eurostat_link.R index 4cf5915f..ff28150e 100644 --- a/R/get_eurostat_link.R +++ b/R/get_eurostat_link.R @@ -5,7 +5,9 @@ #' #' @param link Eurostat "Copy API link" URL or Data Browser download link #' @param destfile Optional file path for saving raw files (only for Excel) +#' @inheritParams get_eurostat #' @return Tibble for all supported formats +#' @importFrom purrr map_dfr #' @export get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { if (!is.character(link) || length(link) != 1) stop("Provide a valid Eurostat link.") diff --git a/R/get_eurostat_local.R b/R/get_eurostat_local.R index 24578eb4..6c98ef5a 100644 --- a/R/get_eurostat_local.R +++ b/R/get_eurostat_local.R @@ -4,9 +4,10 @@ #' This implementation is experimental. It uses only data.table methods to read #' and wrangle data files. #' -#' Download datasets in sdmx-csv format from [https://ec.europa.eu/eurostat/databrowser/bulk?lang=en] +#' Download datasets in sdmx-csv format from \url{https://ec.europa.eu/eurostat/databrowser/bulk?lang=en} #' #' @inheritParams get_eurostat +#' @param file file path as string #' @importFrom data.table fread #' @export From 5ee17955b929031c73517c6c54600ca3c54f6f31 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 14:59:38 +0300 Subject: [PATCH 18/62] Update NEWS --- NEWS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS.md b/NEWS.md index 7ef8e954..58d4217b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,15 @@ * Add new function `get_eurostat_local()` for reading local SDMX-CSV (.csv) and compressed SDMX-CSV (.csv.gz) files downloaded manually from the Eurostat website. This function relies only on `data.table` functions as it is expected that locally stored files may be larger and require more efficient handling with regards to RAM and processing time constrains. `tidy_eurostat_sdmx()` and `parse_filename()` are helper functions related to this function. * New `legacy_data_format()` function and `legacy.data.output` attribute in `get_eurostat()` and `get_eurostat_local()` transforms modern API naming conventions into the format that was used in the old API and eurostat package versions 3.8.* and prior. It is a helper function to other functions. +## Minor updates + +* `get_eurostat_geospatial()` documentation mentions that data from 2024 is now available (fixes issue #327 by @yhsong1013) +* Fix mentions to Eurostat base URLs in `check_access_to_data()`, `get_eurostat()`, `get_eurostat_json()`, `get_eurostat_sdmx()`, `label_eurostat()` and `search_eurostat()` (fixes issue #326 by @olivierchantrel) + +## Deprecated and defunct + +* Remove deprecated parameter `make_valid` from `get_eurostat_geospatial()` + # eurostat 4.0.0 ## Major updates From ad7080099bcd6ba2d4eae80b3cf88f73727e4f97 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 15:00:04 +0300 Subject: [PATCH 19/62] Update docs and DESCRIPTION --- DESCRIPTION | 6 ++++-- man/check_access_to_data.Rd | 2 +- man/eurostat-package.Rd | 8 ++++---- man/eurotime2date.Rd | 4 ++-- man/get_eurostat.Rd | 6 ++++-- man/get_eurostat_async.Rd | 8 ++++++-- man/get_eurostat_dic.Rd | 4 ++-- man/get_eurostat_geospatial.Rd | 15 +++++++-------- man/get_eurostat_json.Rd | 4 ++-- man/get_eurostat_link.Rd | 2 ++ man/get_eurostat_local.Rd | 4 +++- man/get_eurostat_raw.Rd | 4 ++-- man/get_eurostat_sdmx.Rd | 8 ++++++++ man/get_eurostat_toc.Rd | 4 ++-- man/search_eurostat.Rd | 4 ++-- man/tidy_eurostat.Rd | 4 ++-- man/tidy_eurostat_sdmx_csv.Rd | 4 ++-- 17 files changed, 55 insertions(+), 36 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 2bb83ac4..ec6a7e64 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: eurostat Title: Tools for Eurostat Open Data -Version: 4.1.0.9001 -Date: 2024-04-29 +Version: 4.1.0.9002 +Date: 2025-09-08 Authors@R: c( person("Leo", "Lahti", , "leo.lahti@iki.fi", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5537-637X")), @@ -39,6 +39,8 @@ Imports: digest, dplyr, httr2 (>= 0.2.3), + httr, + purrr, ISOweek, jsonlite, lubridate, diff --git a/man/check_access_to_data.Rd b/man/check_access_to_data.Rd index 836794f6..a588bbe6 100755 --- a/man/check_access_to_data.Rd +++ b/man/check_access_to_data.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/check_access_to_data.R \name{check_access_to_data} \alias{check_access_to_data} -\title{Check access to ec.europe.eu} +\title{Check access to ec.europa.eu} \usage{ check_access_to_data() } diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index 7c5ac11f..ab4a48ce 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -15,7 +15,7 @@ utilities. \tab \cr \strong{Package} \tab eurostat \cr \strong{Type} \tab Package \cr - \strong{Version} \tab 4.1.0.9001 \cr + \strong{Version} \tab 4.1.0.9002 \cr \strong{Date} \tab 2014-2024 \cr \strong{License} \tab BSD_2_clause + file LICENSE \cr \strong{LazyLoad} \tab yes \cr @@ -179,7 +179,7 @@ Example: For more information about data filtering see Eurostat documentation on API Statistics: -\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api#APIGettingstartedwithstatisticsAPI-Filterdata} +\url{https://ec.europa.eu/eurostat/web/user-guides/data-browser/api-data-access/api-getting-started/api#APIStatisticsdataquery-TheparametersdefinedintheRESTrequest} } } @@ -355,8 +355,8 @@ See \code{citation("eurostat")}: Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9001. + and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9002. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/eurotime2date.Rd b/man/eurotime2date.Rd index 01e5fd33..fdc0b920 100755 --- a/man/eurotime2date.Rd +++ b/man/eurotime2date.Rd @@ -53,8 +53,8 @@ See \code{citation("eurostat")}: # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9001. +# and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data +# [Computer software]. R package version 4.1.0.9002. # https://github.com/rOpenGov/eurostat # # To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index bb4aa8cd..619a81c5 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -92,6 +92,8 @@ On Windows requires that RTools is installed.} Default is FALSE. If TRUE, the object will try to emulate the naming conventions of eurostat package version 3.7.x and earlier.} +\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} + \item{...}{ Arguments passed on to \code{\link[=get_eurostat_json]{get_eurostat_json}} \describe{ @@ -382,8 +384,8 @@ See \code{citation("eurostat")}: Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9001. + and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9002. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat_async.Rd b/man/get_eurostat_async.Rd index 9685e579..a8d07785 100644 --- a/man/get_eurostat_async.Rd +++ b/man/get_eurostat_async.Rd @@ -60,9 +60,13 @@ can not be used with a \code{filters}.} Default is FALSE. If TRUE, the object will try to emulate the naming conventions of eurostat package version 3.7.x and earlier.} -\item{wait}{Integer. Seconds between status checks.} +\item{wait}{Integer. Seconds between status checks. Default is 1 second.} -\item{max_wait}{Integer. Max time to wait in seconds.} +\item{max_wait}{Integer. Max time to wait in seconds. Default is 60 seconds.} + +\item{compressed}{Logical. Download data in compressed format? Default is TRUE.} + +\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} } \value{ A data.frame containing the SDMX-CSV data. diff --git a/man/get_eurostat_dic.Rd b/man/get_eurostat_dic.Rd index 6307593c..a9b44276 100755 --- a/man/get_eurostat_dic.Rd +++ b/man/get_eurostat_dic.Rd @@ -43,8 +43,8 @@ See \code{citation("eurostat")}: # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9001. +# and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data +# [Computer software]. R package version 4.1.0.9002. # https://github.com/rOpenGov/eurostat # # To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat_geospatial.Rd b/man/get_eurostat_geospatial.Rd index ecb160d1..cac4c106 100755 --- a/man/get_eurostat_geospatial.Rd +++ b/man/get_eurostat_geospatial.Rd @@ -20,7 +20,6 @@ get_eurostat_geospatial( update_cache = FALSE, cache_dir = NULL, crs = "4326", - make_valid = "DEPRECATED", verbose = TRUE, ... ) @@ -43,16 +42,18 @@ been soft-deprecated, the function would switch to \code{sf}.} "0", "1", "2", "3" or "all" (mimics the original behaviour)} \item{year}{NUTS release year. One of -"2003", "2006", "2010", "2013", "2016" or "2021"} +"2003", "2006", "2010", "2013", "2016", "2021" or "2024"} \item{cache}{a logical whether to do caching. Default is \code{TRUE}.} \item{update_cache}{a logical whether to update cache. Can be set also with \code{options(eurostat_update = TRUE)}} -\item{cache_dir}{a path to a cache directory. See -\code{\link[=set_eurostat_cache_dir]{set_eurostat_cache_dir()}}. If \code{NULL} and the cache dir has not been set -globally the file would be stored in the \code{\link[=tempdir]{tempdir()}}.} +\item{cache_dir}{a path to a cache directory. \code{NULL} (default) uses and creates +'eurostat' directory in the temporary directory defined by base R +\code{\link[=tempdir]{tempdir()}} function. The user can set the cache directory to an existing +directory by using this argument. The cache directory can also be set with +\code{\link[=set_eurostat_cache_dir]{set_eurostat_cache_dir()}} function.} \item{crs}{projection of the map: 4-digit \href{https://spatialreference.org/ref/epsg/}{EPSG code}. One of: @@ -62,13 +63,11 @@ globally the file would be stored in the \code{\link[=tempdir]{tempdir()}}.} \item "3857" - Pseudo-Mercator }} -\item{make_valid}{Deprecated} +\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} \item{...}{ Arguments passed on to \code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts}} \describe{ - \item{\code{verbose}}{Logical, displays information. Useful for debugging, -default is \code{FALSE}.} \item{\code{spatialtype}}{Type of geometry to be returned: \itemize{ \item \code{"BN"}: Boundaries - \code{LINESTRING} object. diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 3a5f58e9..88f52ed7 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -316,8 +316,8 @@ See \code{citation("eurostat")}: Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9001. + and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9002. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat_link.Rd b/man/get_eurostat_link.Rd index e5cce115..e852d3ea 100644 --- a/man/get_eurostat_link.Rd +++ b/man/get_eurostat_link.Rd @@ -10,6 +10,8 @@ get_eurostat_link(link, destfile = NULL, verbose = TRUE) \item{link}{Eurostat "Copy API link" URL or Data Browser download link} \item{destfile}{Optional file path for saving raw files (only for Excel)} + +\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} } \value{ Tibble for all supported formats diff --git a/man/get_eurostat_local.Rd b/man/get_eurostat_local.Rd index 7023abdd..36a8ccb2 100644 --- a/man/get_eurostat_local.Rd +++ b/man/get_eurostat_local.Rd @@ -16,6 +16,8 @@ get_eurostat_local( ) } \arguments{ +\item{file}{file path as string} + \item{time_format}{a string giving a type of the conversion of the time column from the eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class with the date being the first day of the period. A "\code{date_last}" argument @@ -66,5 +68,5 @@ Read compressed or uncompressed SDMX-CSV files This implementation is experimental. It uses only data.table methods to read and wrangle data files. -Download datasets in sdmx-csv format from \link{https://ec.europa.eu/eurostat/databrowser/bulk?lang=en} +Download datasets in sdmx-csv format from \url{https://ec.europa.eu/eurostat/databrowser/bulk?lang=en} } diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index 7702d001..934cce8c 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -123,8 +123,8 @@ See \code{citation("eurostat")}: # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # # Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9001. +# and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data +# [Computer software]. R package version 4.1.0.9002. # https://github.com/rOpenGov/eurostat # # To see these entries in BibTeX format, use 'print(, diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index 292e4d6f..3e24531a 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -56,6 +56,8 @@ On Windows requires that RTools is installed.} \item{agency}{Either "Eurostat" (default), "Eurostat_comext" (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW"} +\item{compressed}{Logical. Download data in compressed format? Default is TRUE.} + \item{keepFlags}{a logical whether the flags (e.g. "confidential", "provisional") should be kept in a separate column or if they can be removed. Default is \code{FALSE}. For flag values see: @@ -67,6 +69,12 @@ can not be used with a \code{filters}.} \item{legacy.data.output}{Use legacy column names and data object structure. Default is FALSE. If TRUE, the object will try to emulate the naming conventions of eurostat package version 3.7.x and earlier.} + +\item{wait}{Integer. Seconds between status checks. Default is 1 second.} + +\item{max_wait}{Integer. Max time to wait in seconds. Default is 60 seconds.} + +\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} } \description{ Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index 0444ea6b..099eb7b9 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -90,8 +90,8 @@ See \code{citation("eurostat")}: Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9001. + and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9002. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index e95ca8aa..107c67c0 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -98,8 +98,8 @@ See \code{citation("eurostat")}: Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9001. + and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9002. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/tidy_eurostat.Rd b/man/tidy_eurostat.Rd index 1f952cf1..62b3ed09 100755 --- a/man/tidy_eurostat.Rd +++ b/man/tidy_eurostat.Rd @@ -78,8 +78,8 @@ See \code{citation("eurostat")}: Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9001. + and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9002. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, diff --git a/man/tidy_eurostat_sdmx_csv.Rd b/man/tidy_eurostat_sdmx_csv.Rd index e327d3a3..0465bdca 100644 --- a/man/tidy_eurostat_sdmx_csv.Rd +++ b/man/tidy_eurostat_sdmx_csv.Rd @@ -65,8 +65,8 @@ See \code{citation("eurostat")}: Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2024). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9001. + and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data + [Computer software]. R package version 4.1.0.9002. https://github.com/rOpenGov/eurostat To see these entries in BibTeX format, use 'print(, From b48e3052b5a218ae0055f9758e2e5a890e9813a4 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 15:00:16 +0300 Subject: [PATCH 20/62] Use verbose in get_eurostat_geospatial tests --- .../test_03_get_eurostat_geospatial.R | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/testthat/test_03_get_eurostat_geospatial.R b/tests/testthat/test_03_get_eurostat_geospatial.R index 7ea873d3..6d3f5893 100755 --- a/tests/testthat/test_03_get_eurostat_geospatial.R +++ b/tests/testthat/test_03_get_eurostat_geospatial.R @@ -6,32 +6,32 @@ test_that("get_eurostat_geospatial errors", { skip_if(!giscoR::gisco_check_access(), "No access to GISCO") # Testing argument 'output_class' - expect_error(get_eurostat_geospatial(output_class = 0)) - expect_error(get_eurostat_geospatial(output_class = "foo")) - expect_error(get_eurostat_geospatial(output_class = "sf", "df")) + expect_error(get_eurostat_geospatial(output_class = 0, verbose = FALSE)) + expect_error(get_eurostat_geospatial(output_class = "foo", verbose = FALSE)) + expect_error(get_eurostat_geospatial(output_class = "sf", "df", verbose = FALSE)) # Testing argument 'resolution' - expect_error(get_eurostat_geospatial(resolution = 12345)) - expect_error(get_eurostat_geospatial(resolution = 1:2)) + expect_error(get_eurostat_geospatial(resolution = 12345, verbose = FALSE)) + expect_error(get_eurostat_geospatial(resolution = 1:2, verbose = FALSE)) # Testing argument nuts_level - expect_error(get_eurostat_geospatial(nuts_level = 12345)) - expect_error(get_eurostat_geospatial(nuts_level = 1:2)) + expect_error(get_eurostat_geospatial(nuts_level = 12345, verbose = FALSE)) + expect_error(get_eurostat_geospatial(nuts_level = 1:2, verbose = FALSE)) # Testing argument year - expect_error(get_eurostat_geospatial(year = 1900)) - expect_error(get_eurostat_geospatial(year = c(2003, 2006))) + expect_error(get_eurostat_geospatial(year = 1900, verbose = FALSE)) + expect_error(get_eurostat_geospatial(year = c(2003, 2006, verbose = FALSE))) - # Testing argment cache - expect_error(get_eurostat_geospatial(cache = as.logical(NA), year = 2021)) - expect_error(get_eurostat_geospatial(cache = c(TRUE, FALSE), year = 2021)) + # Testing argument cache + expect_error(get_eurostat_geospatial(cache = as.logical(NA), year = 2021, verbose = FALSE)) + expect_error(get_eurostat_geospatial(cache = c(TRUE, FALSE), year = 2021, verbose = FALSE)) # Testing argument CRS expect_error(get_eurostat_geospatial(crs = "north polar stereographic")) - expect_error(get_eurostat_geospatial(crs = c(4326, 3035))) + expect_error(get_eurostat_geospatial(crs = c(4326, 3035), verbose = FALSE)) # Invalid combinations - expect_error(get_eurostat_geospatial(resolution = 60, year = 2003)) + expect_error(get_eurostat_geospatial(resolution = 60, year = 2003, verbose = FALSE)) }) test_that("get_eurostat_geospatial messages", { @@ -167,7 +167,7 @@ test_that("get_eurostat_geospatial df", { nuts_level = "all", output_class = "df", verbose = TRUE - ), "giscoR") + ), "Extracting data from eurostat::eurostat_geodata_60_2016") expect_message(gn0 <- get_eurostat_geospatial( nuts_level = "0", crs = 3035, From c175aa7f1c12ae42b7a438a005a9dd8d2872ef3d Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 16:50:13 +0300 Subject: [PATCH 21/62] Declare imports, use dplyr and data.table syntax in package properly --- NAMESPACE | 5 +++++ R/get_eurostat.R | 2 +- R/get_eurostat_async.R | 8 ++++++-- R/get_eurostat_link.R | 12 +++++++----- R/get_eurostat_sdmx.R | 8 ++++---- R/tidy_eurostat.R | 8 ++++++-- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 0039a9e8..b99ee081 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -83,8 +83,10 @@ importFrom(regions,recode_nuts) importFrom(regions,validate_geo_code) importFrom(regions,validate_nuts_regions) importFrom(rlang,"!!") +importFrom(rlang,.data) importFrom(rlang,sym) importFrom(stats,na.omit) +importFrom(stats,setNames) importFrom(stringi,stri_extract_first_regex) importFrom(stringi,stri_replace_all_fixed) importFrom(stringi,stri_replace_all_regex) @@ -100,7 +102,10 @@ importFrom(utils,download.file) importFrom(utils,hasName) importFrom(utils,menu) importFrom(utils,person) +importFrom(utils,read.csv) +importFrom(utils,read.delim) importFrom(utils,toBibtex) +importFrom(utils,unzip) importFrom(xml2,read_xml) importFrom(xml2,xml_find_all) importFrom(xml2,xml_text) diff --git a/R/get_eurostat.R b/R/get_eurostat.R index fbefcd8d..52ca0f71 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -68,7 +68,7 @@ #' @param legacy.data.output Use legacy column names and data object structure. #' Default is FALSE. If TRUE, the object will try to emulate the naming #' conventions of eurostat package version 3.7.x and earlier. -#' @param verbose Output messages when downloading data? Default is `TRUE`. +#' @param verbose Output messages when downloading data. Default is `TRUE`. #' @inheritDotParams get_eurostat_json #' #' @inherit eurostat-package references diff --git a/R/get_eurostat_async.R b/R/get_eurostat_async.R index b5b19781..a55775f7 100644 --- a/R/get_eurostat_async.R +++ b/R/get_eurostat_async.R @@ -11,6 +11,7 @@ #' @inheritParams get_eurostat #' @importFrom data.table %chin% #' @importFrom httr GET content status_code +#' @importFrom utils unzip #' #' @return A data.frame containing the SDMX-CSV data. #' @@ -29,6 +30,9 @@ get_eurostat_async <- function( compressed = TRUE, verbose = TRUE ) { + # Fix for NSE NOTEs using data.table + OBS_FLAG = TIME_PERIOD = OBS_VALUE = NULL + stopifnot(nzchar(id)) lang <- check_lang(lang) agency <- tolower(agency) @@ -92,9 +96,9 @@ get_eurostat_async <- function( if (compressed) { is_zip <- identical(readBin(tfile, what = "raw", n = 2), charToRaw("PK")) if (is_zip) { - zip_list <- unzip(tfile, list = TRUE) + zip_list <- utils::unzip(tfile, list = TRUE) if (nrow(zip_list) == 0) stop("Empty ZIP archive received") - csv_file <- unzip(tfile, exdir = tempdir())[1] + csv_file <- utils::unzip(tfile, exdir = tempdir())[1] } else { if (verbose) message("Expected ZIP archive, but received CSV instead - continuing anyway.") csv_file <- tfile diff --git a/R/get_eurostat_link.R b/R/get_eurostat_link.R index ff28150e..ad84107c 100644 --- a/R/get_eurostat_link.R +++ b/R/get_eurostat_link.R @@ -8,6 +8,8 @@ #' @inheritParams get_eurostat #' @return Tibble for all supported formats #' @importFrom purrr map_dfr +#' @importFrom utils read.delim read.csv +#' @importFrom stats setNames #' @export get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { if (!is.character(link) || length(link) != 1) stop("Provide a valid Eurostat link.") @@ -19,8 +21,8 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { } download_content <- function(link) { - resp <- httr2::request(link) |> - httr2::req_user_agent("eurostat-r-client") |> + resp <- httr2::request(link) %>% + httr2::req_user_agent("eurostat-r-client") %>% httr2::req_perform() httr2::resp_check_status(resp) @@ -50,7 +52,7 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { if (verbose) message("Downloading TSV...") text_content <- download_content(link) - df <- read.delim( + df <- utils::read.delim( text = text_content, sep = "\t", check.names = FALSE, @@ -83,7 +85,7 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { }))[1] if (is.na(data_start)) stop("Could not detect valid SDMX-CSV header for Comext dataset.") clean_text <- paste(lines[data_start:length(lines)], collapse = "\n") - return(as_tibble_safe(read.csv(text = clean_text, check.names = FALSE, row.names = NULL))) + return(as_tibble_safe(utils::read.csv(text = clean_text, check.names = FALSE, row.names = NULL))) } # SDMX-ML 2.1 StructureSpecific @@ -114,7 +116,7 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { series_nodes <- xml2::xml_find_all(doc, ".//g:Series", ns) result <- purrr::map_dfr(series_nodes, function(series) { key_nodes <- xml2::xml_find_all(series, ".//g:Value", ns) - key_vals <- setNames(xml2::xml_attr(key_nodes, "value"), xml2::xml_attr(key_nodes, "id")) + key_vals <- stats::setNames(xml2::xml_attr(key_nodes, "value"), xml2::xml_attr(key_nodes, "id")) obs_nodes <- xml2::xml_find_all(series, ".//g:Obs", ns) purrr::map_dfr(obs_nodes, function(obs) { data.frame(as.list(key_vals), time = xml2::xml_attr(xml2::xml_find_first(obs, ".//g:ObsDimension", ns), "value"), value = as.numeric(xml2::xml_attr(xml2::xml_find_first(obs, ".//g:ObsValue", ns), "value")), stringsAsFactors = FALSE) diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 4ea7a51a..576a68a3 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -12,7 +12,7 @@ #' (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW" #' #' @importFrom curl curl_download -#' @importFrom utils download.file +#' @importFrom utils download.file read.csv #' @importFrom readr read_tsv cols col_character #' @importFrom data.table fread #' @@ -154,7 +154,7 @@ get_eurostat_sdmx <- function( curl::curl_download(url = url, destfile = tfile) - dat <- read.csv(tfile, colClasses = "character", row.names = NULL, check.names = FALSE) + dat <- utils::read.csv(tfile, colClasses = "character", row.names = NULL, check.names = FALSE) if (!keepFlags) { col_names <- names(dat) dat <- dat[setdiff(col_names, "OBS_FLAG")] @@ -354,7 +354,7 @@ data_filtering_on_dimension <- function(agency, id, filters) { dimension_id_upper <- dimension_df$dimension_id_upper if (!rlang::is_empty(setdiff(filter_names, dimension_id_upper))) { - stop(paste0("Use valid filter dimensions in the correct order: ", paste(dimension_id, collapse = "."))) + stop(paste0("Use valid filter dimensions in the correct order: ", paste(dimension_df$dimension_id, collapse = "."))) } # Assumes that dimensions are listed in the order of their positions @@ -417,7 +417,7 @@ get_codelist_id <- function(agency, id) { # # } -label_eurostat_sdmx <- function(x, agency, id, lang = "en") { +label_eurostat_sdmx <- function(x, agency, id, lang = "en", verbose = TRUE) { # how many columns there are that can be labeled with a codelist dimension_df <- get_codelist_id(agency = agency, id = id) resource <- "codelist" diff --git a/R/tidy_eurostat.R b/R/tidy_eurostat.R index 92dad346..d3bdfaf1 100755 --- a/R/tidy_eurostat.R +++ b/R/tidy_eurostat.R @@ -273,7 +273,7 @@ convert_time_col <- function(x, time_format) { #' @importFrom dplyr filter #' @importFrom data.table setDT melt .SD := setcolorder #' @importFrom stats na.omit -#' +#' @importFrom rlang .data #' #' @keywords internal utilities database tidy_eurostat_sdmx_csv <- function(dat, @@ -281,7 +281,11 @@ tidy_eurostat_sdmx_csv <- function(dat, select_time = NULL, keepFlags = FALSE) { - dat <- dplyr::filter(dat, !is.na(OBS_VALUE)) + # Fix for NSE NOTEs using data.table + OBS_FLAG = OBS_VALUE = NULL + + dat <- dat %>% + dplyr::filter(!is.na(.data$OBS_VALUE)) if (keepFlags == FALSE) { # Remove OBS_FLAG column From 2e78c54b0894c6b8f22804395582be57cf7669a9 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 16:50:47 +0300 Subject: [PATCH 22/62] Recreate data files with UTF-8 encoding --- data/ea_countries.rda | Bin 781 -> 783 bytes data/efta_countries.rda | Bin 323 -> 342 bytes data/eu_candidate_countries.rda | Bin 608 -> 625 bytes data/eu_countries.rda | Bin 971 -> 982 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/ea_countries.rda b/data/ea_countries.rda index 9ded67e0779f4b113a9f65fbb36cf57825337ac1..8dbfd74104824c852871c87d6caabbc2934c9a96 100644 GIT binary patch literal 783 zcmV+q1MvJpT4*^jL0KkKS>d9vCIAAEfBpadOaK65=-@x^UvR(w|KLC%0RUiVAOaWw z00aO5zyjT>ZDnW-G!)W$rpQK~pk!!d(?Dp(i1dsNO#?$CA)^UIX*E40+JFrnpa1{{ zjDTnW0047FjSVn>X@NA*217#-28>3A2*?1DK>{WknG?~YQz7a!4Ky@lVq`P{h&0ng z+*RjbBK6n;n^)+9LPskZ>CoPXb7$kDpeMA*MPTv)Gj z-NgNEeodz>*Q;PcuBz5S6F8KNCpDpR_bpdS9YQXJ5Ga0Flnv@3#KonQadzHMP?=O( z#3K85H4-C0Qvh`U=t%=e2^b0d7SZMg2?h*|+9dbvMTZ1&IMddj92F^ut8y3SI0w~> znjH}$6>s!&`x)h^ z$sMA8L&0tfOD zZQF50j_Q%Cpcd=T7=HXng%lm&!~|KXq@Ll_%mw?VWOpT;w&}X9Min`| N?ntK!5*##E1i({XT{Hjy literal 781 zcmV+o1M>VrT4*^jL0KkKSgj^bh1m0-Y9eZF{7-=i(3~(N%&H+eIHO6SkgBNCW|*9fOkO4s3L z%eC8XSJh3m$ENQ`8C?Ag>JasPJk+pV{$cB#-Y?Ya6rvGGHsFE-u0DAMrLnD?ku0d8 zvEnC8e(S1Kl9YuM=hluw=?H!xve78{i20yH5?Bc#z5F=yg+=)o7G>A+0Up&1hy>3TBL6G2H}&C!&o(L}9kS*JM~XN7W|$0{j4RH~d- z;jWbHh0Ha=sRry!P;ruM+{12>R=&Odkr7JjFe$KTm_eE7BuR&#kkNffBQWbJAVB{i z7Txx!vEouU_JaL0z&>82LW&PKW&$kU@+a#&8Mb4@YDWyUm*P2<=*Xt!nqx-e*1K)? zNlg(w)y7slti*ar-&o*vJ@rd*ls*Z*(?3;4T| LDZ+$?et5>1DtU3K diff --git a/data/efta_countries.rda b/data/efta_countries.rda index 784fbdbfb3d0d7fa60cfa41d9d91c3742c05af88..8c61df74cd4e9c5e2916da673354e0725286ae4d 100644 GIT binary patch literal 342 zcmV-c0jd5%T4*^jL0KkKSr%P7Qvd+r|J48cNPqwXh(HZxop}p6fmG2;B%!Vh{Y(P(pfT@sj3f}qAS7yD%`c-u>M6?$ zLY^Rv9|NNSXhA?Sk;FD{^cK`Gf(QmoFJumo;4m|#j{MUc&TwQPG>3RHkTCOTQb00E!?GH3t*Gy+L9Dtc;uiIi#TF#}BujT#zc$-UM# zq-Yk%RXDDAAy={iwS?R8>d&Z@$nvxlSXKJT(1m*;c}veloY?S#O~-`{!n{!);YJ}=ze8kL zq!>UgLS~C(D;XD??gDI5#|Zhph=AC^5*?dC10FXOMHG+N$1C4NwIVS_a^eT` zWs#A_EX6@u5^TCltC$l-gb8~~e%rVErrRE5Qb4L_Jk)J_9t%E6!VjgF0 Vq&9Lo^L`)V?ntK!5+3i!#{iKHh$a94 diff --git a/data/eu_candidate_countries.rda b/data/eu_candidate_countries.rda index 99fa08c34eb8fd98f637d28af77950807c12ea32..37bcd0841d8a9fc06a727884da018ac32f7d8dae 100644 GIT binary patch delta 614 zcmV-s0-62b1n~qMLRx4!F+o`-Q(0RK3j5o2Ty z6hn8t5>uh4x8jLY;VHbZ(r5!r5SWro7ziN<2ulkAriR8eHpZJ{17(K0NG8x!ThCVa z30#Z@?`T!AFC2@Q`<;GoId~o4I2AXZ&}kh_1VS`OMX{EU@j@bhgDD7mBFx1Ly|y16HJ3uXaSN z*Ozj0x~!`x>s39femTmOnLuCOg}nU9#YAyruuQP>wxqIWQi@ip#j+d7wo%iw#i4*K zSQtC^O66tpnhOSh7%bz)RB@v?!c3+&DcEEI;qvFK+Jl&j`Z{)+7#%{ipsOtqZV+l?!)z~HjBS_@-~cEE1Fb%n zFx&J1+v>#c^-5hoAwjWK7b%Z|8;q+2Qb54VIIuFNF^kGDC@(R0BvXY61+*UGK;a_? AWB>pF delta 597 zcmV-b0;>J-1mFZ5LRx4!F+o`-Q(1Jyb^t zG5`Po0000000000)7qeEC!mUXBhZgTXf!kij3Wfepa2>J0T`M9;h+E-4F-V005mky zAQ~Dp$N`8N01_yH8U;M2$~{j_O*T_aGGuKa)6~#a4`eu?8x4?g?u3Nbo|?*5NkHB}pxU^Z zayc-ft;p>G*AgleN(eLwZ%SP}t*I7%Wayo2tXV^e6q9aU;lFq;tIQp%1!*9BF=7CK zBS9q6y(3Yylm;AsCR0H)27i8OW;O*P59sPf!;mROn|1|J6_HyuJ#z($3(kR5P*sYA z8-yB^cqt1t^^nsIT;(M02J$B-SOTM(#{8Uh@1he2LC7zbFUWbT2XjhtY7upjMdgiI z8il!P`SEnB8_iI~R_crcugJ6r^#mIphB3FGPyk@VPj==omaVm53pU@I$fPG-fN*#z j$j;U9&q7mDrZ7NI{WMAmtB1pY4Mig2MI>5ItR8~FP? diff --git a/data/eu_countries.rda b/data/eu_countries.rda index 4684146a11f8593d477945900af0baca665a8766..761a93e021226b63ca3913c31c8957855a4494fd 100644 GIT binary patch literal 982 zcmV;{11bDMT4*^jL0KkKS?NSVlmG)pfBgUcNC*I9=-@x^-*CVG|L{O00RVJpAOai! z00B?~o7=d|y4!B6PSBJX{Led2|_dp zDd89>^){2z00E;w8V95R27myOBoU&X)jdWDrV|rQGbm&<^d^}x2AC#I7}V1tz|d)s z(?*66G8z~oLrj`910ciz27*aJPXwD&N2v7xHi!TK1Jr2H0000_%@ZozT1Zv+Aqq`+ zXn;u@tN{D8&IlLk=hvbGv4$R zEZ1F7s)~tOiGpiy42X0xF1V8-Q<79l?H*vf(nwdqBn=4z29N@XgaC`|2Enu@+Y)UF zHX*QWHrf*y+Zcn5M5#bW?=Vh}2df3$a?rL#EOk3nyZOxxAlC3Ot*HVAH=>F#$o8c~ zH~77NOwp!`Xqj40((I+s(qIN06p5u>(H+!D5$GZ?^%@3+~C)-TysNeNZRT-2XkXJeE2*``_91!F@kGW z{a7?OW7lOYJkb`B3VjS(r3?|F6EtLlG>^XV9B6S3S&aCu^#!U@=Ipv8Ljkm(rJO2o zDU&uEIdj_p{P$#PUxbJ>$%8n?5k>fsXIKL_n@bVW+2q9}^ejIKZArZumRWeRt}69U zL`tDgF)JzxjdNshfOO(0nM|Vg*qHB*u2ZdM=i9YTB?-`>3Q|uCL{ig+VG{Z(3NBS= zBP}3Y7)`RmXcZ`q%1&^;Dn~74C)A{j%32SA*+nN+g<;8f)RB0lGRy^UPA9nqx(P%W zqnvFsA1ka9S_EB^n#WuZ7hjUWW_^78={;r;%~Z0NE#fpUggN8^K^OS?^QUo+vbz~k$_xdm`fTN zp@7vU#7_t`qmz^a{jp7miV2G4{f^0-)7U|7o)&_7Z>XO~s`URCaz!{$km*E1 El+lc`oB#j- literal 971 zcmV;+12p_XT4*^jL0KkKSn`SfBgUcNC*I9@Ss2M-*CVG|L{O00RVJpAOai! z00B?~o?D&VS7&kCxV$9^G}36)6U?dVW~ZqAMv0@*FcZ=0WG03XdJPeiPf$t`qBK*` z3Vx(%rkZ+zr>USc%@ZJG000dDl+r1v@}8zm82|tP00001paJRt<3m8vrbe1H$P7#n z01W}4G{_7IkR*_#*rtuCrY4V2&;SO2$Y{_2000i>sx3kk5)~Mn0W9|dNVIKQV&@Y< zLf-{$7{Cs8Oh;BXXSCw*O%Lix0ThrPgpw(+lCcRa>T6qwjocg@FKVk{dlD`RmW=%i zhL7mbe=?VqRw5yzf-8E4ClaYSdQB#I)M?kZ2%8i}Axc81?2LMu?er2%>w;%#o?;Tb zLNlr$I*|`mJP`1tL8OIQFvAfH%r39L2)UfynEq2RJ&VKa?~#|B?`D~#1Ws#k-cMHP!Eh} zY3oTl5i69$RJ$G?BwmuZ8NPQ|itAgAi6eZ3qw*5co6$hLso1GVl;#-_hH5DinW~ya zg_`ih2OOrAq_ma1H?10^RZLgIbGGfeR4O8>D=Hb!6BFP%xT&T?4XVz@s}swxl&(lA zg-VgxNzNAvk>*z^KFX3WQjmNGlqP-ZN|p(cAclfyrEC}%X`bdSkp^Qht}3RkY)_F2 z#IZLu-?AXFM8j1@PBn$3RYaIUM;;*=j54BV-8JpyU~0^$v1m^eOgTbmA5R-6bxd_p zIR|?-fr3gEGB5SfaEZ4j4~XuN_fCwk5rGCP%$sccVv$moVR;RQ{7VG*l5{MRioUor zt+a}RKvXnb*V7}W<9bL)8qQKLV&+ogygc)8R-D?lI>h-%z$`M%C6x=|fZ42AkdRo` zsw`NfMIK|(27)d63i(H57}AACO!uF-9VO1GakiKXduUG-(rOTNWWxcS2QwC0&+ Date: Mon, 8 Sep 2025 16:51:28 +0300 Subject: [PATCH 23/62] Use verbose=FALSE in test, add data from 2024 --- .../test_03_get_eurostat_geospatial.R | 176 +++++++++++++----- 1 file changed, 128 insertions(+), 48 deletions(-) diff --git a/tests/testthat/test_03_get_eurostat_geospatial.R b/tests/testthat/test_03_get_eurostat_geospatial.R index 6d3f5893..638e1c2a 100755 --- a/tests/testthat/test_03_get_eurostat_geospatial.R +++ b/tests/testthat/test_03_get_eurostat_geospatial.R @@ -41,13 +41,6 @@ test_that("get_eurostat_geospatial messages", { skip_if_offline() skip_if(!giscoR::gisco_check_access(), "No access to GISCO") - - # Deprecations - expect_message( - get_eurostat_geospatial(make_valid = TRUE), - "'make_valid' argument has been deprecated" - ) - expect_message( spdf <- get_eurostat_geospatial(output_class = "spdf"), "'spdf' output deprecated. Switching to sf output" @@ -63,11 +56,11 @@ test_that("get_eurostat_geospatial messages", { test_that("get_eurostat_geospatial nuts levels", { skip_if_not_installed(pkg = "sf") # From internal data with default args - expect_message(all <- get_eurostat_geospatial(nuts_level = "all"), "eurostat") - expect_message(n0 <- get_eurostat_geospatial(nuts_level = "0"), "eurostat") - expect_message(n1 <- get_eurostat_geospatial(nuts_level = "1"), "eurostat") - expect_message(n2 <- get_eurostat_geospatial(nuts_level = "2"), "eurostat") - expect_message(n3 <- get_eurostat_geospatial(nuts_level = "3"), "eurostat") + expect_message(all <- get_eurostat_geospatial(nuts_level = "all", verbose = FALSE), "eurostat") + expect_message(n0 <- get_eurostat_geospatial(nuts_level = "0", verbose = FALSE), "eurostat") + expect_message(n1 <- get_eurostat_geospatial(nuts_level = "1", verbose = FALSE), "eurostat") + expect_message(n2 <- get_eurostat_geospatial(nuts_level = "2", verbose = FALSE), "eurostat") + expect_message(n3 <- get_eurostat_geospatial(nuts_level = "3", verbose = FALSE), "eurostat") expect_gt(nrow(all), nrow(n3)) expect_gt(nrow(n3), nrow(n2)) @@ -291,12 +284,12 @@ test_that("Check column names", { ) - cached <- get_eurostat_geospatial() + cached <- get_eurostat_geospatial(verbose = FALSE) expect_s3_class(cached, "sf") expect_identical(names(cached), col_order) # df - cached_df <- get_eurostat_geospatial(output_class = "df") + cached_df <- get_eurostat_geospatial(output_class = "df", verbose = FALSE) expect_s3_class(cached_df, "data.frame") expect_identical(names(cached_df), col_order[-length(col_order)]) }) @@ -324,21 +317,24 @@ test_that("Check column names POLYGONS from GISCO", { # df poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, - resolution = 20, year = 2003 + resolution = 20, year = 2003, + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) # Polygons 2006 - poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2006) + poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2006, + verbose = FALSE) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) # df poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, - resolution = 60, year = 2006 + resolution = 60, year = 2006, + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -346,28 +342,32 @@ test_that("Check column names POLYGONS from GISCO", { # Polygons 2010 - poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2010) + poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2010, + verbose = FALSE) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) # df poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, - resolution = 60, year = 2010 + resolution = 60, year = 2010, + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) # Polygons 2013 - poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2013) + poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2013, + verbose = FALSE) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) # df poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, - resolution = 60, year = 2013 + resolution = 60, year = 2013, + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -376,7 +376,8 @@ test_that("Check column names POLYGONS from GISCO", { # Polygons 2016 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2016, - update_cache = TRUE + update_cache = TRUE, + verbose = FALSE ) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) @@ -385,25 +386,44 @@ test_that("Check column names POLYGONS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2016, - update_cache = TRUE + update_cache = TRUE, + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) # Polygons 2021 - poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2021) + poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2021, + verbose = FALSE) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) # df poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, - resolution = 60, year = 2021 + resolution = 60, year = 2021, + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) + + # Polygons 2024 + poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2024, + verbose = FALSE) + expect_s3_class(poly, "sf") + expect_identical(names(poly), col_order) + + # df + poly_df <- get_eurostat_geospatial( + output_class = "df", nuts_level = 0, + resolution = 60, year = 2024, + verbose = FALSE + ) + + expect_s3_class(poly_df, "data.frame") + expect_identical(names(poly_df), col_order[-length(col_order)]) }) test_that("Check column names LABELS from GISCO", { @@ -423,7 +443,8 @@ test_that("Check column names LABELS from GISCO", { # Labels 2003 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 20, year = 2003, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) @@ -432,7 +453,8 @@ test_that("Check column names LABELS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 20, year = 2003, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -441,7 +463,8 @@ test_that("Check column names LABELS from GISCO", { # Labels 2006 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2006, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) @@ -450,7 +473,8 @@ test_that("Check column names LABELS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2006, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -460,7 +484,8 @@ test_that("Check column names LABELS from GISCO", { # Labels 2010 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2010, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) @@ -469,7 +494,8 @@ test_that("Check column names LABELS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2010, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -478,7 +504,8 @@ test_that("Check column names LABELS from GISCO", { # Labels 2013 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2013, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) @@ -487,7 +514,8 @@ test_that("Check column names LABELS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2013, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -496,7 +524,8 @@ test_that("Check column names LABELS from GISCO", { # Labels 2016 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2016, - update_cache = TRUE, spatialtype = "LB" + update_cache = TRUE, spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) @@ -506,7 +535,8 @@ test_that("Check column names LABELS from GISCO", { output_class = "df", nuts_level = 0, resolution = 60, year = 2016, update_cache = TRUE, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -515,7 +545,8 @@ test_that("Check column names LABELS from GISCO", { # Labels 2021 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2021, - spatialtype = "LB" + spatialtype = "LB", + verbose = FALSE ) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) @@ -529,6 +560,25 @@ test_that("Check column names LABELS from GISCO", { expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) + + # Labels 2024 + poly <- get_eurostat_geospatial( + nuts_level = 0, resolution = 60, year = 2024, + spatialtype = "LB", + verbose = FALSE + ) + expect_s3_class(poly, "sf") + expect_identical(names(poly), col_order) + + # df + poly_df <- get_eurostat_geospatial( + output_class = "df", nuts_level = 0, + resolution = 60, year = 2024, + spatialtype = "LB" + ) + + expect_s3_class(poly_df, "data.frame") + expect_identical(names(poly_df), col_order[-length(col_order)]) }) @@ -543,7 +593,8 @@ test_that("Check column names BORDERS from GISCO", { # BORDERS 2003 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 20, year = 2003, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly, "sf") @@ -551,7 +602,8 @@ test_that("Check column names BORDERS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 20, year = 2003, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -559,7 +611,8 @@ test_that("Check column names BORDERS from GISCO", { # BORDERS 2006 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2006, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly, "sf") @@ -567,7 +620,8 @@ test_that("Check column names BORDERS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2006, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -576,7 +630,8 @@ test_that("Check column names BORDERS from GISCO", { # BORDERS 2010 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2010, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly, "sf") @@ -584,7 +639,8 @@ test_that("Check column names BORDERS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2010, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -592,7 +648,8 @@ test_that("Check column names BORDERS from GISCO", { # BORDERS 2013 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2013, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly, "sf") @@ -600,7 +657,8 @@ test_that("Check column names BORDERS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2013, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -608,7 +666,8 @@ test_that("Check column names BORDERS from GISCO", { # BORDERS 2016 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2016, - update_cache = TRUE, spatialtype = "BN" + update_cache = TRUE, spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly, "sf") @@ -617,7 +676,8 @@ test_that("Check column names BORDERS from GISCO", { output_class = "df", nuts_level = 0, resolution = 60, year = 2016, update_cache = TRUE, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") @@ -625,7 +685,8 @@ test_that("Check column names BORDERS from GISCO", { # BORDERS 2021 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2021, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly, "sf") @@ -633,8 +694,27 @@ test_that("Check column names BORDERS from GISCO", { poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2021, - spatialtype = "BN" + spatialtype = "BN", + verbose = FALSE ) expect_s3_class(poly_df, "data.frame") + + # BORDERS 2024 + poly <- get_eurostat_geospatial( + nuts_level = 0, resolution = 60, year = 2024, + spatialtype = "BN", + verbose = FALSE + ) + expect_s3_class(poly, "sf") + + # df + poly_df <- get_eurostat_geospatial( + output_class = "df", nuts_level = 0, + resolution = 60, year = 2024, + spatialtype = "BN", + verbose = FALSE + ) + + expect_s3_class(poly_df, "data.frame") }) From b0725ee11462b968013ce792e9f93dc96ed8a4d4 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 16:52:01 +0300 Subject: [PATCH 24/62] Remove test for case that is no longer used "Notes by country: Hungary, Latvia and Sweden have reported a number of deaths where the week of death is unknown. The respective data for the unknown week of death, previously published online under the code TIME=YYYYW99, are now only available within the respective Excel file published below (concept's annexes)." --- tests/testthat/test_07_misc.R | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/testthat/test_07_misc.R b/tests/testthat/test_07_misc.R index ff70d6b7..8eac5b01 100755 --- a/tests/testthat/test_07_misc.R +++ b/tests/testthat/test_07_misc.R @@ -28,21 +28,6 @@ test_that("Duplicated with fix_duplicated does not give an error", { ), "P5", all = TRUE) }) -test_that("Get eurotime2date error message related to W99 values", { - skip_on_cran() - skip_if_offline() - expect_warning( - get_eurostat( - "demo_r_mweek3", - filters = list( - sex = "F", - age = "TOTAL", - geo = "BG" - ) - ) - ) -}) - test_that("cut_to_classes works", { skip_on_cran() skip_if_offline() From b1b6a38a83e4a71b62a6b6cc7116c577d0772e08 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 16:52:14 +0300 Subject: [PATCH 25/62] Update documentation --- man/get_eurostat.Rd | 2 +- man/get_eurostat_async.Rd | 2 +- man/get_eurostat_geospatial.Rd | 2 +- man/get_eurostat_link.Rd | 2 +- man/get_eurostat_sdmx.Rd | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index 619a81c5..282984ba 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -92,7 +92,7 @@ On Windows requires that RTools is installed.} Default is FALSE. If TRUE, the object will try to emulate the naming conventions of eurostat package version 3.7.x and earlier.} -\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} +\item{verbose}{Output messages when downloading data. Default is \code{TRUE}.} \item{...}{ Arguments passed on to \code{\link[=get_eurostat_json]{get_eurostat_json}} diff --git a/man/get_eurostat_async.Rd b/man/get_eurostat_async.Rd index a8d07785..d008d66e 100644 --- a/man/get_eurostat_async.Rd +++ b/man/get_eurostat_async.Rd @@ -66,7 +66,7 @@ conventions of eurostat package version 3.7.x and earlier.} \item{compressed}{Logical. Download data in compressed format? Default is TRUE.} -\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} +\item{verbose}{Output messages when downloading data. Default is \code{TRUE}.} } \value{ A data.frame containing the SDMX-CSV data. diff --git a/man/get_eurostat_geospatial.Rd b/man/get_eurostat_geospatial.Rd index cac4c106..a319de7a 100755 --- a/man/get_eurostat_geospatial.Rd +++ b/man/get_eurostat_geospatial.Rd @@ -63,7 +63,7 @@ directory by using this argument. The cache directory can also be set with \item "3857" - Pseudo-Mercator }} -\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} +\item{verbose}{Output messages when downloading data. Default is \code{TRUE}.} \item{...}{ Arguments passed on to \code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts}} diff --git a/man/get_eurostat_link.Rd b/man/get_eurostat_link.Rd index e852d3ea..d9e9030d 100644 --- a/man/get_eurostat_link.Rd +++ b/man/get_eurostat_link.Rd @@ -11,7 +11,7 @@ get_eurostat_link(link, destfile = NULL, verbose = TRUE) \item{destfile}{Optional file path for saving raw files (only for Excel)} -\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} +\item{verbose}{Output messages when downloading data. Default is \code{TRUE}.} } \value{ Tibble for all supported formats diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index 3e24531a..3932cfe6 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -74,7 +74,7 @@ conventions of eurostat package version 3.7.x and earlier.} \item{max_wait}{Integer. Max time to wait in seconds. Default is 60 seconds.} -\item{verbose}{Output messages when downloading data? Default is \code{TRUE}.} +\item{verbose}{Output messages when downloading data. Default is \code{TRUE}.} } \description{ Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} From 734f071f735503e8c81d6852cfb8ac3c3c1e894d Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 19:41:43 +0300 Subject: [PATCH 26/62] Use detail=dataonly to not confuse csv readers with flag column --- R/get_eurostat_sdmx.R | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 576a68a3..2863c99c 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -12,8 +12,8 @@ #' (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW" #' #' @importFrom curl curl_download -#' @importFrom utils download.file read.csv -#' @importFrom readr read_tsv cols col_character +#' @importFrom utils download.file +#' @importFrom readr read_csv cols col_character #' @importFrom data.table fread #' #' @export @@ -92,7 +92,8 @@ get_eurostat_sdmx <- function( key, "?", "format=SDMX-CSV", - compressed + compressed, + "&detail=dataonly" ) res <- httr::GET(url) @@ -154,7 +155,7 @@ get_eurostat_sdmx <- function( curl::curl_download(url = url, destfile = tfile) - dat <- utils::read.csv(tfile, colClasses = "character", row.names = NULL, check.names = FALSE) + dat <- readr::read_csv(tfile, progress = verbose, show_col_types = verbose) if (!keepFlags) { col_names <- names(dat) dat <- dat[setdiff(col_names, "OBS_FLAG")] From 328011937d6161a1c4bee86509840ffd82d996c9 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 19:46:11 +0300 Subject: [PATCH 27/62] Use actions/checkout@v5 --- .github/workflows/check-full.yaml | 2 +- .github/workflows/check-standard.yaml | 2 +- .github/workflows/test-coverage.yaml | 2 +- .github/workflows/tidy_code.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-full.yaml b/.github/workflows/check-full.yaml index 7c6cbcf2..7a47123a 100644 --- a/.github/workflows/check-full.yaml +++ b/.github/workflows/check-full.yaml @@ -27,7 +27,7 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: r-lib/actions/setup-pandoc@v2 diff --git a/.github/workflows/check-standard.yaml b/.github/workflows/check-standard.yaml index 8ef4f9e5..b60da2fb 100644 --- a/.github/workflows/check-standard.yaml +++ b/.github/workflows/check-standard.yaml @@ -15,7 +15,7 @@ jobs: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: r-lib/actions/setup-r@v2 with: diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 8cd78cdc..6aa95277 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -16,7 +16,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: r-lib/actions/setup-r@v2 with: diff --git a/.github/workflows/tidy_code.yaml b/.github/workflows/tidy_code.yaml index 0e99e011..48cb262a 100644 --- a/.github/workflows/tidy_code.yaml +++ b/.github/workflows/tidy_code.yaml @@ -16,7 +16,7 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: r-lib/actions/setup-pandoc@v2 From 772f35bb4ab227aca46bbfa2f91d484da2e9ac42 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 19:51:52 +0300 Subject: [PATCH 28/62] Acknowledgement --- README.Rmd | 2 ++ README.md | 32 ++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.Rmd b/README.Rmd index e45d6ccb..33ff09ff 100755 --- a/README.Rmd +++ b/README.Rmd @@ -116,6 +116,8 @@ Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantan We are grateful to all [contributors](https://github.com/ropengov/eurostat/graphs/contributors), including Daniel Antal, Joona Lehtomäki, Francois Briatte, and Oliver Reiter, and for the [Eurostat](https://ec.europa.eu/eurostat/) open data portal! This project is part of [rOpenGov](https://ropengov.org). +This project has received funding from the European Union under grant No 101095295 (OpenMUSE), the FIN-CLARIAH research infrastructure and the Strategic Research Council's YOUNG program by the Research Council of Finland (decisions 345630, 358720, 367756, 352604). + ### Disclaimer This package is in no way officially related to or endorsed by Eurostat. diff --git a/README.md b/README.md index 77c3263d..89bf95e4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ + [![rOG-badge](https://ropengov.github.io/rogtemplate/reference/figures/ropengov-badge.svg)](https://ropengov.org/) @@ -23,15 +24,25 @@ GitHub](https://img.shields.io/github/stars/ropengov/eurostat.svg?style=social)] [![Follow](https://img.shields.io/twitter/follow/ropengov.svg?style=social)](https://twitter.com/ropengov) + + + + + + + + + + # eurostat R package @@ -81,14 +92,14 @@ passengers <- search_eurostat("passenger transport") knitr::kable(head(passengers)) ``` -| title | code | type | last.update.of.data | last.table.structure.change | data.start | data.end | values | hierarchy | -|:---------------------------------------------------------------------------------------------|:-----------------|:--------|:--------------------|:----------------------------|:-----------|:---------|--------:|----------:| -| Air passenger transport | enps_avia_pa | dataset | 13.03.2023 | 13.03.2023 | 2005 | 2021 | 406 | 6 | -| Modal split of air, sea and inland passenger transport | tran_hv_ms_psmod | dataset | 29.06.2023 | 29.06.2023 | 2008 | 2021 | 2100 | 4 | -| Modal split of inland passenger transport | tran_hv_psmod | dataset | 29.06.2023 | 29.06.2023 | 1990 | 2021 | 4219 | 4 | -| Volume of passenger transport relative to GDP | tran_hv_pstra | dataset | 11.08.2023 | 29.06.2023 | 1990 | 2021 | 969 | 4 | -| Maritime passenger transport performed in the Exclusive Economic Zone (EEZ) of the countries | mar_tp_pa | dataset | 21.02.2023 | 21.02.2023 | 2005 | 2021 | 1752 | 4 | -| Air passenger transport by reporting country | avia_paoc | dataset | 04.12.2023 | 28.11.2023 | 1993 | 2023-Q3 | 2482969 | 5 | +| title | code | type | last.update.of.data | last.table.structure.change | data.start | data.end | values | hierarchy | +|:---|:---|:---|:---|:---|:---|:---|---:|---:| +| Air passenger transport - ENP-South countries | enps_avia_pa | dataset | 12.05.2025 | 12.05.2025 | 2005 | 2023 | 480 | 6 | +| Air passenger transport by type of schedule, transport coverage and country | avia_paoc | dataset | 18.08.2025 | 18.08.2025 | 1993 | 2025-Q2 | 2519348 | 5 | +| Air passenger transport by type of schedule, transport coverage and main airports | avia_paoa | dataset | 18.08.2025 | 18.08.2025 | 1993 | 2025-Q2 | 20557100 | 5 | +| Air passenger transport between reporting and partner countries by type of schedule | avia_paocc | dataset | 18.08.2025 | 18.08.2025 | 1993 | 2025-Q2 | 10484757 | 5 | +| Air passenger transport between main airports and partner reporting countries | avia_paoac | dataset | 18.08.2025 | 18.08.2025 | 1993 | 2025-Q2 | 20133126 | 5 | +| Air passenger transport by aircraft model, distance bands and transport coverage | avia_paodis | dataset | 16.06.2025 | 06.12.2024 | 2008 | 2023 | 852432 | 5 | See the [Tutorial](https://ropengov.github.io/eurostat/articles/articles/eurostat_tutorial.html) @@ -138,6 +149,11 @@ including Daniel Antal, Joona Lehtomäki, Francois Briatte, and Oliver Reiter, and for the [Eurostat](https://ec.europa.eu/eurostat/) open data portal! This project is part of [rOpenGov](https://ropengov.org). +This project has received funding from the European Union under grant No +101095295 (OpenMUSE), the FIN-CLARIAH research infrastructure and the +Strategic Research Council’s YOUNG program by the Research Council of +Finland (decisions 345630, 358720, 367756, 352604). + ### Disclaimer This package is in no way officially related to or endorsed by Eurostat. From edb73690ed03d9db4a69289e4661455982ad5a92 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 8 Sep 2025 19:52:28 +0300 Subject: [PATCH 29/62] Use checkout@v5 --- .github/workflows/rogtemplate-gh-pages.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rogtemplate-gh-pages.yaml b/.github/workflows/rogtemplate-gh-pages.yaml index cb0ed15c..027815f9 100644 --- a/.github/workflows/rogtemplate-gh-pages.yaml +++ b/.github/workflows/rogtemplate-gh-pages.yaml @@ -19,7 +19,7 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: r-lib/actions/setup-pandoc@v2 From 1b6d664a0e67bc6cc92bad5cd0a6944080b78f3f Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:10:28 +0300 Subject: [PATCH 30/62] Version bump --- DESCRIPTION | 7 ++++--- NEWS.md | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ec6a7e64..8b15b6c0 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: eurostat Title: Tools for Eurostat Open Data -Version: 4.1.0.9002 -Date: 2025-09-08 +Version: 4.1.0.9003 +Date: 2025-09-16 Authors@R: c( person("Leo", "Lahti", , "leo.lahti@iki.fi", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5537-637X")), @@ -54,7 +54,8 @@ Imports: tibble, tidyr (>= 1.0.0), xml2, - data.table (>= 1.14.8) + data.table (>= 1.14.8), + lifecycle Suggests: giscoR, knitr, diff --git a/NEWS.md b/NEWS.md index 58d4217b..207e0338 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# eurostat 4.1.0.9000 +# eurostat 4.1.0.9003 ## Major updates From 664f18ffdaf14233a69290fbbaaa120494983ab3 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:11:04 +0300 Subject: [PATCH 31/62] Re-render README --- README.Rmd | 10 ++++------ README.md | 58 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/README.Rmd b/README.Rmd index 33ff09ff..4365f9f6 100755 --- a/README.Rmd +++ b/README.Rmd @@ -106,13 +106,11 @@ Contributions are very welcome: ### Acknowledgements -**Kindly cite this package** by citing the following R Journal article: +**Kindly cite this work** as follows: -[Lahti L.](https://github.com/antagomir), Huovari J., Kainu M., and Biecek P. (2017). Retrieval and analysis of Eurostat open data with the eurostat package. [The R Journal 9(1), pp. 385-392](https://journal.r-project.org/archive/2017/RJ-2017-019/index.html). doi: [10.32614/RJ-2017-019](https://doi.org/10.32614/RJ-2017-019). - -In addition, please provide a citation to the specific software version used: - -Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data [Computer software]. R package version 4.0.0.9003. https://github.com/rOpenGov/eurostat +```{r, comment = "", highlight=FALSE} +print(citation("eurostat"), bibtex = TRUE) +``` We are grateful to all [contributors](https://github.com/ropengov/eurostat/graphs/contributors), including Daniel Antal, Joona Lehtomäki, Francois Briatte, and Oliver Reiter, and for the [Eurostat](https://ec.europa.eu/eurostat/) open data portal! This project is part of [rOpenGov](https://ropengov.org). diff --git a/README.md b/README.md index 89bf95e4..dfc45436 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,10 @@ knitr::kable(head(passengers)) | title | code | type | last.update.of.data | last.table.structure.change | data.start | data.end | values | hierarchy | |:---|:---|:---|:---|:---|:---|:---|---:|---:| | Air passenger transport - ENP-South countries | enps_avia_pa | dataset | 12.05.2025 | 12.05.2025 | 2005 | 2023 | 480 | 6 | -| Air passenger transport by type of schedule, transport coverage and country | avia_paoc | dataset | 18.08.2025 | 18.08.2025 | 1993 | 2025-Q2 | 2519348 | 5 | -| Air passenger transport by type of schedule, transport coverage and main airports | avia_paoa | dataset | 18.08.2025 | 18.08.2025 | 1993 | 2025-Q2 | 20557100 | 5 | -| Air passenger transport between reporting and partner countries by type of schedule | avia_paocc | dataset | 18.08.2025 | 18.08.2025 | 1993 | 2025-Q2 | 10484757 | 5 | -| Air passenger transport between main airports and partner reporting countries | avia_paoac | dataset | 18.08.2025 | 18.08.2025 | 1993 | 2025-Q2 | 20133126 | 5 | +| Air passenger transport by type of schedule, transport coverage and country | avia_paoc | dataset | 15.09.2025 | 15.09.2025 | 1993 | 2025-Q2 | 2524910 | 5 | +| Air passenger transport by type of schedule, transport coverage and main airports | avia_paoa | dataset | 15.09.2025 | 15.09.2025 | 1993 | 2025-Q2 | 20609595 | 5 | +| Air passenger transport between reporting and partner countries by type of schedule | avia_paocc | dataset | 15.09.2025 | 15.09.2025 | 1993 | 2025-Q2 | 10518367 | 5 | +| Air passenger transport between main airports and partner reporting countries | avia_paoac | dataset | 15.09.2025 | 15.09.2025 | 1993 | 2025-Q2 | 20189218 | 5 | | Air passenger transport by aircraft model, distance bands and transport coverage | avia_paodis | dataset | 16.06.2025 | 06.12.2024 | 2008 | 2023 | 852432 | 5 | See the @@ -127,21 +127,51 @@ Contributions are very welcome: ### Acknowledgements -**Kindly cite this package** by citing the following R Journal article: +**Kindly cite this work** as follows: -[Lahti L.](https://github.com/antagomir), Huovari J., Kainu M., and -Biecek P. (2017). Retrieval and analysis of Eurostat open data with the -eurostat package. [The R Journal 9(1), -pp. 385-392](https://journal.r-project.org/archive/2017/RJ-2017-019/index.html). -doi: [10.32614/RJ-2017-019](https://doi.org/10.32614/RJ-2017-019). +``` text +print(citation("eurostat"), bibtex = TRUE) +Kindly cite this package by citing the following R Journal article: + + Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and + analysis of Eurostat open data with the eurostat package. The R + Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 + +A BibTeX entry for LaTeX users is + + @Article{10.32614/RJ-2017-019, + title = {Retrieval and Analysis of Eurostat Open Data with the eurostat Package}, + author = {Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek}, + journal = {The R Journal}, + volume = {9}, + number = {1}, + pages = {385--392}, + year = {2017}, + doi = {10.32614/RJ-2017-019}, + url = {https://doi.org/10.32614/RJ-2017-019}, + } In addition, please provide a citation to the specific software version used: -Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -and Kantanen P. (2023). eurostat: Tools for Eurostat Open Data -\[Computer software\]. R package version 4.0.0.9003. - + Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, + Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + doi:10.32614/CRAN.package.eurostat + , R package version + 4.1.0.9003, . + +A BibTeX entry for LaTeX users is + + @Misc{R-eurostat, + title = {eurostat: Tools for Eurostat Open Data}, + doi = {10.32614/CRAN.package.eurostat}, + author = {Leo Lahti and Janne Huovari and Markus Kainu and Przemyslaw Biecek and Diego Hernangomez and Daniel Antal and Pyry Kantanen}, + url = {https://github.com/rOpenGov/eurostat}, + type = {Computer software}, + year = {2025}, + note = {R package version 4.1.0.9003}, + } +``` We are grateful to all [contributors](https://github.com/ropengov/eurostat/graphs/contributors), From 9d357d546614c56ff670d7e004a0742cf51ea4d9 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:11:25 +0300 Subject: [PATCH 32/62] Update year --- R/eurostat-package.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/eurostat-package.R b/R/eurostat-package.R index 2cd59699..06267ac4 100755 --- a/R/eurostat-package.R +++ b/R/eurostat-package.R @@ -12,7 +12,7 @@ #' | **Package** | eurostat | #' | **Type** | Package | #' | **Version** | `r packageVersion("eurostat")` | -#' | **Date** | 2014-2024 | +#' | **Date** | 2014-2025 | #' | **License** | `r as.character(utils::packageDescription("eurostat")["License"])`| #' | **LazyLoad** | yes | #' From 40c2865badbc3922a922daa53693b343f26b9657 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:11:53 +0300 Subject: [PATCH 33/62] Use na.rm in data.table::melt --- R/tidy_eurostat.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/tidy_eurostat.R b/R/tidy_eurostat.R index d3bdfaf1..2807b836 100755 --- a/R/tidy_eurostat.R +++ b/R/tidy_eurostat.R @@ -107,8 +107,8 @@ tidy_eurostat <- function(dat, dat <- data.table::melt(data = dat, measure.vars = setdiff(names(dat), cnames1), variable.name = cnames2, - value.name = "values") %>% - na.omit(cols = "values") + value.name = "values", na.rm = TRUE)# %>% + # stats::na.omit(cols = "values") # NEW CODE: data.table # should be using S3 method for data.table here... From 6179335855fd943f3b3e679f0ef33a8b80d991c4 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:12:09 +0300 Subject: [PATCH 34/62] Update CITATION to new template --- inst/CITATION | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/inst/CITATION b/inst/CITATION index ca7e261b..36b23c4d 100755 --- a/inst/CITATION +++ b/inst/CITATION @@ -1,9 +1,11 @@ -citHeader("Kindly cite the eurostat R package as follows:") - year <- sub("-.*", "", meta$Date) -vers <- paste("R package version", meta$Version) +version_note <- paste("R package version", meta$Version) +pkg <- meta$Package +title <- gsub("'", "", meta$Title) +doi <- paste0("10.32614/CRAN.package.", pkg) bibentry("Article", + header = "Kindly cite this package by citing the following R Journal article:", key = "10.32614/RJ-2017-019", title = "Retrieval and Analysis of Eurostat Open Data with the eurostat Package", author = c( @@ -28,8 +30,10 @@ bibentry("Article", ) bibentry(bibtype = "Misc", - key = "eurostat", + header = "In addition, please provide a citation to the specific software version used:", + key = paste0("R-", pkg), title = "eurostat: Tools for Eurostat Open Data", + doi = doi, author = c( person("Leo", "Lahti"), person("Janne", "Huovari"), @@ -42,13 +46,5 @@ bibentry(bibtype = "Misc", url = "https://github.com/rOpenGov/eurostat", type = "Computer software", year = year, - note = vers, - textVersion = - paste0( - "Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., and Kantanen P. (", - sub("-.*", "", meta$Date), - "). eurostat: Tools for Eurostat Open Data [Computer software]. ", - vers, - ". https://github.com/rOpenGov/eurostat" - ) -) \ No newline at end of file + note = version_note +) From c69ea0cbdde8c90b0cb54a2672362a675b9ddb79 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:12:54 +0300 Subject: [PATCH 35/62] Declare functions as experimental --- R/get_eurostat_link.R | 31 +++-- R/get_eurostat_local.R | 5 +- R/get_eurostat_sdmx.R | 179 ++++++++++++------------- man/figures/lifecycle-deprecated.svg | 21 +++ man/figures/lifecycle-experimental.svg | 21 +++ man/figures/lifecycle-stable.svg | 29 ++++ man/figures/lifecycle-superseded.svg | 21 +++ man/get_eurostat_async.Rd | 5 +- man/get_eurostat_sdmx.Rd | 7 +- 9 files changed, 210 insertions(+), 109 deletions(-) create mode 100644 man/figures/lifecycle-deprecated.svg create mode 100644 man/figures/lifecycle-experimental.svg create mode 100644 man/figures/lifecycle-stable.svg create mode 100644 man/figures/lifecycle-superseded.svg diff --git a/R/get_eurostat_link.R b/R/get_eurostat_link.R index ad84107c..a4c0c6bf 100644 --- a/R/get_eurostat_link.R +++ b/R/get_eurostat_link.R @@ -1,5 +1,8 @@ -#' Download Eurostat Data from API Link (robust, no list-columns, async-aware) +#' @title Download Eurostat Data from API Link (robust, no list-columns, async-aware) #' +#' @description +#' `r lifecycle::badge('experimental')` +#' #' Supports TSV, SDMX-CSV, SDMX-ML, JSON-stat, and Spreadsheet (.xlsx) formats. #' Always returns a tibble where possible. #' @@ -10,6 +13,8 @@ #' @importFrom purrr map_dfr #' @importFrom utils read.delim read.csv #' @importFrom stats setNames +#' @importFrom httr2 request req_user_agent req_perform resp_check_status +#' @importFrom httr2 resp_body_raw resp_content_type url_parse #' @export get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { if (!is.character(link) || length(link) != 1) stop("Provide a valid Eurostat link.") @@ -38,17 +43,19 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { raw } + + url_parsed <- httr2::url_parse(link) # Comext DS-prefixed datasets must use correct base if (grepl("/comext/dissemination", link)) { - dataset_match <- regmatches(link, regexpr("/ds-[0-9]+", tolower(link))) - if (length(dataset_match) == 0) { + dataset_match <- grepl("/ds-[0-9]+", url_parsed$path) + if (!dataset_match) { stop("Comext API requires DS-prefixed dataset codes (e.g., ds-056120).") } } # TSV format - if (grepl("format=TSV", link, ignore.case = TRUE) || grepl("\\.tsv", link, ignore.case = TRUE)) { + if ("format" %in% attributes(url_parsed$query) && url_parsed$query$format == "TSV") { if (verbose) message("Downloading TSV...") text_content <- download_content(link) @@ -75,8 +82,8 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { } # SDMX-CSV - if (grepl("format=csvdata", link, ignore.case = TRUE) || grepl("\\.csv", link, ignore.case = TRUE)) { - if (verbose) message("Downloading SDMX-CSV 1.0...") + if ("format" %in% attributes(url_parsed$query)$names && url_parsed$query$format == "csvdata") { + if (verbose) message(paste0("Downloading SDMX-CSV ", url_parsed$query$formatVersion, "...")) text_content <- download_content(link) lines <- strsplit(text_content, "\n", fixed = TRUE)[[1]] data_start <- which(sapply(lines, function(l) { @@ -89,9 +96,9 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { } # SDMX-ML 2.1 StructureSpecific - if (grepl("format=structurespecificdata", link, ignore.case = TRUE) && grepl("formatVersion=2.1", link, ignore.case = TRUE)) { + if ("format" %in% attributes(url_parsed$query)$names && url_parsed$query$format == "structurespecificdata") { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") - if (verbose) message("Downloading SDMX-ML 2.1 StructureSpecific...") + if (verbose) message(paste("Downloading SDMX-ML", url_parsed$query$formatVersion, url_parsed$query$format, "...")) xml_text <- download_content(link) doc <- xml2::read_xml(xml_text) ns <- xml2::xml_ns(doc) @@ -107,9 +114,9 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { } # SDMX-ML 2.1 GenericData - if (grepl("format=genericdata", link, ignore.case = TRUE)) { + if ("format" %in% attributes(url_parsed$query)$names && url_parsed$query$format == "genericdata") { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") - if (verbose) message("Downloading SDMX-ML 2.1 GenericData...") + if (verbose) message(paste("Downloading SDMX-ML", url_parsed$query$formatVersion, url_parsed$query$format, "...")) xml_text <- download_content(link) doc <- xml2::read_xml(xml_text) ns <- xml2::xml_ns(doc) @@ -128,7 +135,7 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { # SDMX-ML 3.0 StructureSpecific if (grepl("/sdmx/3.0/data", link) && (grepl("\\.xml", link, ignore.case = TRUE) || !grepl("format=", link, ignore.case = TRUE))) { if (!requireNamespace("xml2", quietly = TRUE)) stop("Please install 'xml2' for XML parsing.") - if (verbose) message("Downloading SDMX-ML 3.0 StructureSpecific...") + if (verbose) message(paste("Downloading SDMX-ML", url_parsed$query$formatVersion, url_parsed$query$format, "...")) xml_text <- download_content(link) doc <- xml2::read_xml(xml_text) ns <- xml2::xml_ns(doc) @@ -154,7 +161,7 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { } # JSON-stat handling - if (grepl("format=JSON", link, ignore.case = TRUE)) { + if ("format" %in% attributes(url_parsed$query)$names && url_parsed$query$format == "JSON") { if (verbose) message("Downloading JSON-stat 2.0...") json_data <- jsonlite::fromJSON(link) value_data <- json_data$value diff --git a/R/get_eurostat_local.R b/R/get_eurostat_local.R index 6c98ef5a..9c3c84fc 100644 --- a/R/get_eurostat_local.R +++ b/R/get_eurostat_local.R @@ -1,5 +1,8 @@ #' @title Read Local SDMX-CSV files -#' @description Read compressed or uncompressed SDMX-CSV files +#' @description +#' `r lifecycle::badge('experimental')` +#' Read compressed or uncompressed SDMX-CSV files +#' #' @details #' This implementation is experimental. It uses only data.table methods to read #' and wrangle data files. diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 2863c99c..7d45b624 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -1,20 +1,30 @@ #' @title Get Eurostat Data from SDMX 2.1 API #' #' @description -#' Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} +#' `r lifecycle::badge('experimental')` +#' +#' Download data sets from Eurostat using the same logic as `get_eurostat()` +#' function. #' #' @details -#' Download data from SDMX API +#' This function is experimental because while it works as intended and is +#' useful in the same way as other get_ functions in the package, we would +#' like to test it for a while and listen to user feedback before deciding on +#' what is the best way to interact with SDMX APIs. #' #' @inheritParams get_eurostat #' @inheritParams get_eurostat_async #' @param agency Either "Eurostat" (default), "Eurostat_comext" #' (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW" +#' @param use.data.table Use data.table to process files? Default is FALSE. +#' If data.table is used, data will be downloaded as a TSV file and +#' processed using [tidy_eurostat()] #' #' @importFrom curl curl_download #' @importFrom utils download.file -#' @importFrom readr read_csv cols col_character +#' @importFrom readr read_csv read_tsv cols col_character #' @importFrom data.table fread +#' @importFrom httr2 request req_url_path_append req_url_query req_perform #' #' @export get_eurostat_sdmx <- function( @@ -23,7 +33,7 @@ get_eurostat_sdmx <- function( filters = NULL, type = "code", lang = "en", - use.data.table, + use.data.table = FALSE, agency = "Eurostat", compressed = TRUE, keepFlags = FALSE, @@ -47,26 +57,9 @@ get_eurostat_sdmx <- function( agency <- tolower(agency) - # agencyID <- switch( - # agency, - # eurostat = "ESTAT", - # eurostat_comext = "ESTAT", - # comp = "COMP", - # empl = "EMPL", - # grow = "GROW" - # ) - api_base_uri <- build_api_base_uri(agency) agencyID <- build_agencyID(agency) - # api_base_uri <- switch( - # agency, - # eurostat = "https://ec.europa.eu/eurostat/api/dissemination", - # eurostat_comext = "https://ec.europa.eu/eurostat/api/comext/dissemination", - # comp = "https://webgate.ec.europa.eu/comp/redisstat/api/dissemination", - # empl = "https://webgate.ec.europa.eu/empl/redisstat/api/dissemination", - # grow = "https://webgate.ec.europa.eu/grow/redisstat/api/dissemination") - if (is.null(api_base_uri)) stop("Use valid agency") if (is.null(filters) && agency == "eurostat_comext") { @@ -78,82 +71,88 @@ get_eurostat_sdmx <- function( # The identifier of the dataflow reference flowRef <- id key <- data_filtering_on_dimension(agency, id, filters) - compressed_flag <- compressed - compressed <- if (compressed_flag) "&compressed=true" else "&compressed=false" - - - url <- paste0( - api_base_uri, - "/sdmx/2.1/", - resource, - "/", - flowRef, - "/", - key, - "?", - "format=SDMX-CSV", - compressed, - "&detail=dataonly" - ) - - res <- httr::GET(url) - #print(res) - ctype <- httr::headers(res)[["content-type"]] - #print(ctype) - - if (grepl("xml", ctype, ignore.case = TRUE)) { - content <- httr::content(res, as = "parsed", encoding = "UTF-8") - async_id <- xml2::xml_text(xml2::xml_find_first(content, ".//id")) - if (!is.na(async_id) && nzchar(async_id)) { - if (verbose) message("Async mode triggered. Downloading via async ID: ", async_id) - return(get_eurostat_async( - id = async_id, - time_format = time_format, - type = type, - lang = lang, - use.data.table = use.data.table, - agency = agency, - keepFlags = keepFlags, - legacy.data.output = legacy.data.output, - wait = wait, - max_wait = max_wait, - compressed = compressed_flag - )) - } else { - message(" Response was XML, but no async found. Treating as sync (unexpected).") - } - } else if (grepl("csv", ctype, ignore.case = TRUE)) { - if (verbose) message("Synchronous mode: CSV data returned directly.") + compressed_string <- if (compressed) "&compressed=true" else "&compressed=false" + + if (use.data.table) { + tfile <- tempfile() + on.exit(unlink(tfile)) + + httr2::request(api_base_uri) %>% + httr2::req_url_path_append("sdmx", "2.1", resource, id, key) %>% + httr2::req_url_query(format = "TSV", compressed = compressed, detail = "dataonly") %>% + httr2::req_perform(path = tfile) + + dat <- readr::read_tsv(tfile, progress = verbose, show_col_types = verbose) + dat2 <- data.table::fread(tfile, na.strings = c(":"), verbose = verbose) + # Columns containing NA's don't play well with data.table::melt in + # tidy_eurostat + # This turns logical columns (containing NA's) into integers (NA_integer_) + offending_cols <- names(dat2[, .SD, .SDcols = anyNA]) + dat2 <- dat2[ , (offending_cols) := lapply(.SD, as.integer), .SDcols = offending_cols] + + dat <- tidy_eurostat(dat2, use.data.table = use.data.table) + + return(dat) } else { - if (verbose) message("Unexpected content type: ", ctype) + res <- httr2::request(api_base_uri) %>% + httr2::req_url_path_append("sdmx", "2.1", resource, id, key) %>% + httr2::req_url_query(format = "SDMX-CSV", compressed = compressed, detail = "dataonly") } + # url <- paste0( + # api_base_uri, + # "/sdmx/2.1/", + # resource, + # "/", + # flowRef, + # "/", + # key, + # "?", + # "format=SDMX-CSV", + # compressed, + # "&detail=dataonly" + # ) + # res_old <- httr::GET(url) + # res <- res %>% + # req_headers() + # + # ctype_old <- httr::headers(res_old)[["content-type"]] + # ctype <- res %>% + # httr2::req_headers() + + #print(ctype) - # content <- httr::content(res, as = "parsed", encoding = "UTF-8") - # - # # Check for async ID - # async_id <- xml2::xml_text(xml2::xml_find_first(content, ".//id")) - # - # if (!is.na(async_id) && nzchar(async_id)) { - # message("Async mode triggered. Downloading via async ID: ", async_id) - # return(get_eurostat_async( - # id = async_id, - # time_format = time_format, - # type = type, - # lang = lang, - # use.data.table = use.data.table, - # agency = agency, - # keepFlags = keepFlags, - # legacy.data.output = legacy.data.output, - # wait = wait, - # max_wait = max_wait - # )) + # if (grepl("xml", ctype, ignore.case = TRUE)) { + # content <- httr::content(res, as = "parsed", encoding = "UTF-8") + # async_id <- xml2::xml_text(xml2::xml_find_first(content, ".//id")) + # if (!is.na(async_id) && nzchar(async_id)) { + # if (verbose) message("Async mode triggered. Downloading via async ID: ", async_id) + # return(get_eurostat_async( + # id = async_id, + # time_format = time_format, + # type = type, + # lang = lang, + # use.data.table = use.data.table, + # agency = agency, + # keepFlags = keepFlags, + # legacy.data.output = legacy.data.output, + # wait = wait, + # max_wait = max_wait, + # compressed = compressed_flag + # )) + # } else { + # message(" Response was XML, but no async found. Treating as sync (unexpected).") + # } + # } else if (grepl("csv", ctype, ignore.case = TRUE)) { + # if (verbose) message("Synchronous mode: CSV data returned directly.") + # } else { + # if (verbose) message("Unexpected content type: ", ctype) # } tfile <- tempfile() on.exit(unlink(tfile)) - - curl::curl_download(url = url, destfile = tfile) + + httr2::req_perform(res, path = tfile) dat <- readr::read_csv(tfile, progress = verbose, show_col_types = verbose) if (!keepFlags) { @@ -161,8 +160,6 @@ get_eurostat_sdmx <- function( dat <- dat[setdiff(col_names, "OBS_FLAG")] } - # return(references_resolution(api_base_uri, resource = "datastructure", agencyID = agencyID, resourceID = id)) - if (identical(type, "label")){ dat <- label_eurostat_sdmx(x = dat, agency = agency, diff --git a/man/figures/lifecycle-deprecated.svg b/man/figures/lifecycle-deprecated.svg new file mode 100644 index 00000000..b61c57c3 --- /dev/null +++ b/man/figures/lifecycle-deprecated.svg @@ -0,0 +1,21 @@ + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/man/figures/lifecycle-experimental.svg b/man/figures/lifecycle-experimental.svg new file mode 100644 index 00000000..5d88fc2c --- /dev/null +++ b/man/figures/lifecycle-experimental.svg @@ -0,0 +1,21 @@ + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/man/figures/lifecycle-stable.svg b/man/figures/lifecycle-stable.svg new file mode 100644 index 00000000..9bf21e76 --- /dev/null +++ b/man/figures/lifecycle-stable.svg @@ -0,0 +1,29 @@ + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/man/figures/lifecycle-superseded.svg b/man/figures/lifecycle-superseded.svg new file mode 100644 index 00000000..db8d757f --- /dev/null +++ b/man/figures/lifecycle-superseded.svg @@ -0,0 +1,21 @@ + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + diff --git a/man/get_eurostat_async.Rd b/man/get_eurostat_async.Rd index d008d66e..6643d1fb 100644 --- a/man/get_eurostat_async.Rd +++ b/man/get_eurostat_async.Rd @@ -42,8 +42,9 @@ labels as values and codes as names.} \item{lang}{2-letter language code, default is "\code{en}" (English), other options are "\code{fr}" (French) and "\code{de}" (German). Used for labeling datasets.} -\item{use.data.table}{Use faster data.table functions? Default is FALSE. -On Windows requires that RTools is installed.} +\item{use.data.table}{Use data.table to process files? Default is FALSE. +If data.table is used, data will be downloaded as a TSV file and +processed using \code{\link[=tidy_eurostat]{tidy_eurostat()}}} \item{agency}{Either "Eurostat" (default), "Eurostat_comext" (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW"} diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index 3932cfe6..ebf6e7b1 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -10,7 +10,7 @@ get_eurostat_sdmx( filters = NULL, type = "code", lang = "en", - use.data.table, + use.data.table = FALSE, agency = "Eurostat", compressed = TRUE, keepFlags = FALSE, @@ -50,8 +50,9 @@ labels as values and codes as names.} \item{lang}{2-letter language code, default is "\code{en}" (English), other options are "\code{fr}" (French) and "\code{de}" (German). Used for labeling datasets.} -\item{use.data.table}{Use faster data.table functions? Default is FALSE. -On Windows requires that RTools is installed.} +\item{use.data.table}{Use data.table to process files? Default is FALSE. +If data.table is used, data will be downloaded as a TSV file and +processed using \code{\link[=tidy_eurostat]{tidy_eurostat()}}} \item{agency}{Either "Eurostat" (default), "Eurostat_comext" (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW"} From c959c34021da15272385c8ab691252aacded3a23 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:12:59 +0300 Subject: [PATCH 36/62] Declare imports --- NAMESPACE | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index b99ee081..37d70af6 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -61,9 +61,13 @@ importFrom(httr2,req_error) importFrom(httr2,req_perform) importFrom(httr2,req_proxy) importFrom(httr2,req_retry) +importFrom(httr2,req_url_path_append) +importFrom(httr2,req_url_query) importFrom(httr2,req_user_agent) importFrom(httr2,request) importFrom(httr2,resp_body_json) +importFrom(httr2,resp_body_raw) +importFrom(httr2,resp_check_status) importFrom(httr2,resp_content_type) importFrom(httr2,resp_is_error) importFrom(httr2,url_build) @@ -78,6 +82,7 @@ importFrom(lubridate,ymd) importFrom(purrr,map_dfr) importFrom(readr,col_character) importFrom(readr,cols) +importFrom(readr,read_csv) importFrom(readr,read_tsv) importFrom(regions,recode_nuts) importFrom(regions,validate_geo_code) From 50806a5a8aec535933e348146bcac755b9490957 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:13:09 +0300 Subject: [PATCH 37/62] Tests for get_eurostat_sdmx --- tests/testthat/test_10_sdmx.R | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/testthat/test_10_sdmx.R diff --git a/tests/testthat/test_10_sdmx.R b/tests/testthat/test_10_sdmx.R new file mode 100644 index 00000000..59f2748a --- /dev/null +++ b/tests/testthat/test_10_sdmx.R @@ -0,0 +1,28 @@ +test_that("get_eurostat_sdmx works", { + skip_on_cran() + skip_if_offline() + + prodcom <- get_eurostat_sdmx(id = "ds-059327", agency = "eurostat_comext", + filters = + list( + FREQ = c("A"), + REPORTER = "FR", + PARTNER = "US", + PRODUCT = c("291"), + FLOW = "2", + INDICATORS = "VALUE_EUR"), verbose = FALSE) + + expect_equal(unique(prodcom$freq), "A") + + prodcom2 <- get_eurostat_sdmx(id = "ds-059327", agency = "eurostat_comext", + filters = + list( + FREQ = c("M"), + REPORTER = "FR", + PARTNER = "US", + PRODUCT = c("291"), + FLOW = "2", + INDICATORS = "VALUE_EUR"), verbose = FALSE, use.data.table = TRUE) + + expect_equal(unique(prodcom2$freq), "M") +}) From 3b1d25ff009e62e406a3ab36b4836b42dea86d54 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:43:42 +0300 Subject: [PATCH 38/62] Move lifecycle from imports to suggests as recommended by lifecycle docs: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "lifecycle::badge() is run by roxygen at build time so you don’t need to add lifecycle to Imports: just to use the badges. However, it’s still good practice to add to Suggests: so that it will be available to package developers" --- DESCRIPTION | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8b15b6c0..3f70e317 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -54,14 +54,14 @@ Imports: tibble, tidyr (>= 1.0.0), xml2, - data.table (>= 1.14.8), - lifecycle + data.table (>= 1.14.8) Suggests: giscoR, knitr, rmarkdown, sf, - testthat (>= 3.0.0) + testthat (>= 3.0.0), + lifecycle VignetteBuilder: knitr Config/Needs/website: ggplot2, tmap, styler, sessioninfo, From 04ec5524c907fe0cd3fde6b0988091ae5cc19fb6 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 17:44:04 +0300 Subject: [PATCH 39/62] Update docs --- man/eurostat-package.Rd | 18 +++++++++++------- man/eurotime2date.Rd | 14 +++++++++----- man/get_eurostat.Rd | 14 +++++++++----- man/get_eurostat_dic.Rd | 14 +++++++++----- man/get_eurostat_json.Rd | 14 +++++++++----- man/get_eurostat_link.Rd | 2 ++ man/get_eurostat_local.Rd | 1 + man/get_eurostat_raw.Rd | 14 +++++++++----- man/get_eurostat_sdmx.Rd | 10 ++++++++-- man/get_eurostat_toc.Rd | 14 +++++++++----- man/search_eurostat.Rd | 14 +++++++++----- man/tidy_eurostat.Rd | 14 +++++++++----- man/tidy_eurostat_sdmx_csv.Rd | 14 +++++++++----- 13 files changed, 103 insertions(+), 54 deletions(-) diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index ab4a48ce..da02d126 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -15,8 +15,8 @@ utilities. \tab \cr \strong{Package} \tab eurostat \cr \strong{Type} \tab Package \cr - \strong{Version} \tab 4.1.0.9002 \cr - \strong{Date} \tab 2014-2024 \cr + \strong{Version} \tab 4.1.0.9003 \cr + \strong{Date} \tab 2014-2025 \cr \strong{License} \tab BSD_2_clause + file LICENSE \cr \strong{LazyLoad} \tab yes \cr } @@ -348,16 +348,20 @@ library(eurostat) \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{Kindly cite this package by citing the following R Journal article: Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9002. - https://github.com/rOpenGov/eurostat +In addition, please provide a citation to the specific software version +used: + + Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, + Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + doi:10.32614/CRAN.package.eurostat + , R package version + 4.1.0.9003, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/eurotime2date.Rd b/man/eurotime2date.Rd index fdc0b920..e7c1eb9a 100755 --- a/man/eurotime2date.Rd +++ b/man/eurotime2date.Rd @@ -46,16 +46,20 @@ get_eurostat( \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{# Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{# Kindly cite this package by citing the following R Journal article: # # Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and # analysis of Eurostat open data with the eurostat package. The R # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # -# Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9002. -# https://github.com/rOpenGov/eurostat +# In addition, please provide a citation to the specific software version +# used: +# +# Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, +# Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." +# doi:10.32614/CRAN.package.eurostat +# , R package version +# 4.1.0.9003, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index 282984ba..c62c4272 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -377,16 +377,20 @@ dd3_filter <- get_eurostat("AVIA_GOR_ME", \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{Kindly cite this package by citing the following R Journal article: Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9002. - https://github.com/rOpenGov/eurostat +In addition, please provide a citation to the specific software version +used: + + Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, + Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + doi:10.32614/CRAN.package.eurostat + , R package version + 4.1.0.9003, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_dic.Rd b/man/get_eurostat_dic.Rd index a9b44276..67b9a37f 100755 --- a/man/get_eurostat_dic.Rd +++ b/man/get_eurostat_dic.Rd @@ -36,16 +36,20 @@ get_eurostat_dic("crop_pro", lang = "fr") \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{# Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{# Kindly cite this package by citing the following R Journal article: # # Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and # analysis of Eurostat open data with the eurostat package. The R # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # -# Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9002. -# https://github.com/rOpenGov/eurostat +# In addition, please provide a citation to the specific software version +# used: +# +# Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, +# Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." +# doi:10.32614/CRAN.package.eurostat +# , R package version +# 4.1.0.9003, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 88f52ed7..0d32c95c 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -309,16 +309,20 @@ dd <- get_eurostat("nama_10_gdp", \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{Kindly cite this package by citing the following R Journal article: Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9002. - https://github.com/rOpenGov/eurostat +In addition, please provide a citation to the specific software version +used: + + Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, + Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + doi:10.32614/CRAN.package.eurostat + , R package version + 4.1.0.9003, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_link.Rd b/man/get_eurostat_link.Rd index d9e9030d..6e39959b 100644 --- a/man/get_eurostat_link.Rd +++ b/man/get_eurostat_link.Rd @@ -17,6 +17,8 @@ get_eurostat_link(link, destfile = NULL, verbose = TRUE) Tibble for all supported formats } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + Supports TSV, SDMX-CSV, SDMX-ML, JSON-stat, and Spreadsheet (.xlsx) formats. Always returns a tibble where possible. } diff --git a/man/get_eurostat_local.Rd b/man/get_eurostat_local.Rd index 36a8ccb2..c10c4554 100644 --- a/man/get_eurostat_local.Rd +++ b/man/get_eurostat_local.Rd @@ -62,6 +62,7 @@ Default is FALSE. If TRUE, the object will try to emulate the naming conventions of eurostat package version 3.7.x and earlier.} } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} Read compressed or uncompressed SDMX-CSV files } \details{ diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index 934cce8c..b99e107b 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -116,16 +116,20 @@ eurostat:::get_eurostat_raw("educ_iste") \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{# Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{# Kindly cite this package by citing the following R Journal article: # # Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and # analysis of Eurostat open data with the eurostat package. The R # Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 # -# Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., -# and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data -# [Computer software]. R package version 4.1.0.9002. -# https://github.com/rOpenGov/eurostat +# In addition, please provide a citation to the specific software version +# used: +# +# Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, +# Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." +# doi:10.32614/CRAN.package.eurostat +# , R package version +# 4.1.0.9003, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index ebf6e7b1..322498d0 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -78,8 +78,14 @@ conventions of eurostat package version 3.7.x and earlier.} \item{verbose}{Output messages when downloading data. Default is \code{TRUE}.} } \description{ -Download data sets from Eurostat \url{https://ec.europa.eu/eurostat} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +Download data sets from Eurostat using the same logic as \code{get_eurostat()} +function. } \details{ -Download data from SDMX API +This function is experimental because while it works as intended and is +useful in the same way as other get_ functions in the package, we would +like to test it for a while and listen to user feedback before deciding on +what is the best way to interact with SDMX APIs. } diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index 099eb7b9..46055967 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -83,16 +83,20 @@ tmp$data.end <- lubridate::as_date( \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{Kindly cite this package by citing the following R Journal article: Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9002. - https://github.com/rOpenGov/eurostat +In addition, please provide a citation to the specific software version +used: + + Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, + Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + doi:10.32614/CRAN.package.eurostat + , R package version + 4.1.0.9003, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index 107c67c0..1909cb40 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -91,16 +91,20 @@ tmp <- search_eurostat("Live births (total) by NUTS 3 region", fixed = TRUE) \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{Kindly cite this package by citing the following R Journal article: Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9002. - https://github.com/rOpenGov/eurostat +In addition, please provide a citation to the specific software version +used: + + Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, + Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + doi:10.32614/CRAN.package.eurostat + , R package version + 4.1.0.9003, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat.Rd b/man/tidy_eurostat.Rd index 62b3ed09..4f965e46 100755 --- a/man/tidy_eurostat.Rd +++ b/man/tidy_eurostat.Rd @@ -71,16 +71,20 @@ get_eurostat("AVIA_GOR_ME", \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{Kindly cite this package by citing the following R Journal article: Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9002. - https://github.com/rOpenGov/eurostat +In addition, please provide a citation to the specific software version +used: + + Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, + Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + doi:10.32614/CRAN.package.eurostat + , R package version + 4.1.0.9003, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat_sdmx_csv.Rd b/man/tidy_eurostat_sdmx_csv.Rd index 0465bdca..e2a822a7 100644 --- a/man/tidy_eurostat_sdmx_csv.Rd +++ b/man/tidy_eurostat_sdmx_csv.Rd @@ -58,16 +58,20 @@ determined outside this function, in \code{get_eurostat_local()}. \references{ See \code{citation("eurostat")}: -\if{html}{\out{
}}\preformatted{Kindly cite the eurostat R package as follows: +\if{html}{\out{
}}\preformatted{Kindly cite this package by citing the following R Journal article: Lahti L., Huovari J., Kainu M., and Biecek P. (2017). Retrieval and analysis of Eurostat open data with the eurostat package. The R Journal 9(1), pp. 385-392. doi: 10.32614/RJ-2017-019 - Lahti, L., Huovari J., Kainu M., Biecek P., Hernangomez D., Antal D., - and Kantanen P. (2025). eurostat: Tools for Eurostat Open Data - [Computer software]. R package version 4.1.0.9002. - https://github.com/rOpenGov/eurostat +In addition, please provide a citation to the specific software version +used: + + Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, + Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + doi:10.32614/CRAN.package.eurostat + , R package version + 4.1.0.9003, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set From ee20cb7fa9049f0c4eec056e781d360891a14f83 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 16 Sep 2025 19:57:29 +0300 Subject: [PATCH 40/62] Remove httr from imports --- DESCRIPTION | 3 +-- NAMESPACE | 6 ++--- R/get_eurostat_async.R | 24 ++++++++++++------- R/get_eurostat_json.R | 13 ---------- R/get_eurostat_link.R | 2 +- R/get_eurostat_sdmx.R | 50 --------------------------------------- man/get_eurostat_async.Rd | 1 + 7 files changed, 21 insertions(+), 78 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3f70e317..5510aad5 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -38,8 +38,7 @@ Imports: curl, digest, dplyr, - httr2 (>= 0.2.3), - httr, + httr2 (>= 1.1.2), purrr, ISOweek, jsonlite, diff --git a/NAMESPACE b/NAMESPACE index 37d70af6..46fdbb46 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -53,9 +53,6 @@ importFrom(dplyr,coalesce) importFrom(dplyr,filter) importFrom(dplyr,inner_join) importFrom(dplyr,mutate) -importFrom(httr,GET) -importFrom(httr,content) -importFrom(httr,status_code) importFrom(httr2,"%>%") importFrom(httr2,req_error) importFrom(httr2,req_perform) @@ -67,9 +64,11 @@ importFrom(httr2,req_user_agent) importFrom(httr2,request) importFrom(httr2,resp_body_json) importFrom(httr2,resp_body_raw) +importFrom(httr2,resp_body_xml) importFrom(httr2,resp_check_status) importFrom(httr2,resp_content_type) importFrom(httr2,resp_is_error) +importFrom(httr2,resp_status) importFrom(httr2,url_build) importFrom(httr2,url_parse) importFrom(jsonlite,fromJSON) @@ -113,4 +112,5 @@ importFrom(utils,toBibtex) importFrom(utils,unzip) importFrom(xml2,read_xml) importFrom(xml2,xml_find_all) +importFrom(xml2,xml_find_first) importFrom(xml2,xml_text) diff --git a/R/get_eurostat_async.R b/R/get_eurostat_async.R index a55775f7..523a8787 100644 --- a/R/get_eurostat_async.R +++ b/R/get_eurostat_async.R @@ -1,6 +1,7 @@ #' @title Get Asynchronous Eurostat SDMX Data by ID #' #' @description +#' `r lifecycle::badge('experimental')` #' Polls Eurostat's async API for the status of a submitted request and downloads the data when available. #' #' @inheritParams get_eurostat_sdmx @@ -10,8 +11,11 @@ #' @param compressed Logical. Download data in compressed format? Default is TRUE. #' @inheritParams get_eurostat #' @importFrom data.table %chin% -#' @importFrom httr GET content status_code +#' @importFrom httr2 request req_user_agent req_perform resp_body_xml +#' @importFrom httr2 resp_status resp_body_raw #' @importFrom utils unzip +#' @importFrom xml2 xml_find_first +#' @importFrom stats setNames #' #' @return A data.frame containing the SDMX-CSV data. #' @@ -50,11 +54,13 @@ get_eurostat_async <- function( status_check <- function(t) { Sys.sleep(wait) - res <- httr::GET(status_url) - content <- httr::content(res, as = "parsed", encoding = "UTF-8") + resp <- httr2::request(status_url) %>% + httr2::req_user_agent("https://github.com/rOpenGov/eurostat") %>% + httr2::req_perform() + content <- httr2::resp_body_xml(resp) xml2::xml_text(xml2::xml_find_first(content, ".//status")) } - + statuses <- vapply(wait_times, status_check, FUN.VALUE = character(1)) status_flags <- stats::setNames(seq_along(statuses), statuses) @@ -74,17 +80,17 @@ get_eurostat_async <- function( # Enhanced download with retries and HTTP checks download_attempt <- function(url, destfile, retries = 3, delay = 5) { for (i in seq_len(retries)) { - res <- httr::GET(url) - if (httr::status_code(res) == 200) { - writeBin(httr::content(res, "raw"), destfile) + resp <- httr2::request(url) %>% httr2::req_perform() + if (httr2::resp_status(resp) == 200) { + writeBin(httr2::resp_body_raw(resp), destfile) return(TRUE) } if (i < retries) { - if (verbose) message("Download failed (HTTP ", httr::status_code(res), "). Retrying...") + if (verbose) message("Download failed (HTTP ", httr2::resp_status(resp), "). Retrying...") Sys.sleep(delay) } } - stop("Download failed after ", retries, " attempts. Last HTTP status: ", httr::status_code(res)) + stop("Download failed after ", retries, " attempts. Last HTTP status: ", httr2::resp_status(resp)) } tryCatch( diff --git a/R/get_eurostat_json.R b/R/get_eurostat_json.R index 4d1bb222..c6aff5df 100755 --- a/R/get_eurostat_json.R +++ b/R/get_eurostat_json.R @@ -130,10 +130,6 @@ get_eurostat_json <- function(id, # construct url url <- eurostat_json_url(id = id, filters = filters, lang = lang) - # set user agent with version - # ua <- httr::user_agent(paste0("eurostat_", - # packageDescription("eurostat", fields = "Version"))) - # ua <- httr::user_agent("https://github.com/rOpenGov/eurostat") if (proxy == TRUE) { # Check if "..." has arguments needed for proxy @@ -166,13 +162,6 @@ get_eurostat_json <- function(id, httr2::req_error(is_error = function(resp) FALSE) %>% httr2::req_perform() - # RETRY GET 3 times - # resp <- httr::RETRY(verb = "GET", - # url = url, - # times = 3, - # terminate_on = c(404), - # ua) - # Source: httr vignette "Best practices for API packages" [httr_vignette] if (httr2::resp_content_type(resp) != "application/json") { stop("API did not return json", call. = FALSE) @@ -227,8 +216,6 @@ get_eurostat_json <- function(id, ) } - #status <- httr::status_code(resp) - # check status and get json jdat <- result # no need to download same data twice diff --git a/R/get_eurostat_link.R b/R/get_eurostat_link.R index a4c0c6bf..28682731 100644 --- a/R/get_eurostat_link.R +++ b/R/get_eurostat_link.R @@ -27,7 +27,7 @@ get_eurostat_link <- function(link, destfile = NULL, verbose = TRUE) { download_content <- function(link) { resp <- httr2::request(link) %>% - httr2::req_user_agent("eurostat-r-client") %>% + httr2::req_user_agent("https://github.com/rOpenGov/eurostat") %>% httr2::req_perform() httr2::resp_check_status(resp) diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 7d45b624..9dbd782d 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -99,56 +99,6 @@ get_eurostat_sdmx <- function( httr2::req_url_query(format = "SDMX-CSV", compressed = compressed, detail = "dataonly") } - # url <- paste0( - # api_base_uri, - # "/sdmx/2.1/", - # resource, - # "/", - # flowRef, - # "/", - # key, - # "?", - # "format=SDMX-CSV", - # compressed, - # "&detail=dataonly" - # ) - # res_old <- httr::GET(url) - # res <- res %>% - # req_headers() - # - # ctype_old <- httr::headers(res_old)[["content-type"]] - # ctype <- res %>% - # httr2::req_headers() - - #print(ctype) - - # if (grepl("xml", ctype, ignore.case = TRUE)) { - # content <- httr::content(res, as = "parsed", encoding = "UTF-8") - # async_id <- xml2::xml_text(xml2::xml_find_first(content, ".//id")) - # if (!is.na(async_id) && nzchar(async_id)) { - # if (verbose) message("Async mode triggered. Downloading via async ID: ", async_id) - # return(get_eurostat_async( - # id = async_id, - # time_format = time_format, - # type = type, - # lang = lang, - # use.data.table = use.data.table, - # agency = agency, - # keepFlags = keepFlags, - # legacy.data.output = legacy.data.output, - # wait = wait, - # max_wait = max_wait, - # compressed = compressed_flag - # )) - # } else { - # message(" Response was XML, but no async found. Treating as sync (unexpected).") - # } - # } else if (grepl("csv", ctype, ignore.case = TRUE)) { - # if (verbose) message("Synchronous mode: CSV data returned directly.") - # } else { - # if (verbose) message("Unexpected content type: ", ctype) - # } - tfile <- tempfile() on.exit(unlink(tfile)) diff --git a/man/get_eurostat_async.Rd b/man/get_eurostat_async.Rd index 6643d1fb..08e2d9fe 100644 --- a/man/get_eurostat_async.Rd +++ b/man/get_eurostat_async.Rd @@ -73,5 +73,6 @@ conventions of eurostat package version 3.7.x and earlier.} A data.frame containing the SDMX-CSV data. } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} Polls Eurostat's async API for the status of a submitted request and downloads the data when available. } From 2e6ca619932350ae7cd9c28d30684d9f5241f3c2 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Thu, 16 Oct 2025 17:28:28 +0300 Subject: [PATCH 41/62] SDMX metadata functions, version bump --- DESCRIPTION | 7 +- NAMESPACE | 2 + NEWS.md | 3 +- R/get_eurostat_sdmx.R | 329 ++++++++++++++++++++++++++++++---- man/eurostat-package.Rd | 4 +- man/eurotime2date.Rd | 2 +- man/get_eurostat.Rd | 2 +- man/get_eurostat_dic.Rd | 2 +- man/get_eurostat_json.Rd | 2 +- man/get_eurostat_raw.Rd | 2 +- man/get_eurostat_toc.Rd | 2 +- man/om_dataset.Rd | 15 ++ man/search_eurostat.Rd | 2 +- man/tidy_eurostat.Rd | 2 +- man/tidy_eurostat_sdmx_csv.Rd | 2 +- 15 files changed, 329 insertions(+), 49 deletions(-) create mode 100644 man/om_dataset.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 5510aad5..323c2a11 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: eurostat Title: Tools for Eurostat Open Data -Version: 4.1.0.9003 -Date: 2025-09-16 +Version: 4.1.0.9004 +Date: 2025-10-16 Authors@R: c( person("Leo", "Lahti", , "leo.lahti@iki.fi", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5537-637X")), @@ -53,7 +53,8 @@ Imports: tibble, tidyr (>= 1.0.0), xml2, - data.table (>= 1.14.8) + data.table (>= 1.14.8), + dataset (>= 0.4.0) Suggests: giscoR, knitr, diff --git a/NAMESPACE b/NAMESPACE index 46fdbb46..d5bab610 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -46,6 +46,8 @@ importFrom(data.table,fread) importFrom(data.table,melt) importFrom(data.table,setDT) importFrom(data.table,setcolorder) +importFrom(dataset,dataset_df) +importFrom(dataset,dublincore) importFrom(digest,digest) importFrom(dplyr,"%>%") importFrom(dplyr,case_when) diff --git a/NEWS.md b/NEWS.md index 207e0338..387155cf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,10 +1,11 @@ -# eurostat 4.1.0.9003 +# eurostat 4.1.0.9004 ## Major updates * Add new function `get_eurostat_sdmx()` for implementing functionalities that are specifically targeted for the new SDMX API. It is accompanied by other helper functions that are related to fetching metadata from the SDMX API: `build_api_base_uri()`, `build_agencyID()`, `data_filtering_on_dimension()`, `get_codelist_id()` and `label_eurostat_sdmx()` * Add new function `get_eurostat_local()` for reading local SDMX-CSV (.csv) and compressed SDMX-CSV (.csv.gz) files downloaded manually from the Eurostat website. This function relies only on `data.table` functions as it is expected that locally stored files may be larger and require more efficient handling with regards to RAM and processing time constrains. `tidy_eurostat_sdmx()` and `parse_filename()` are helper functions related to this function. * New `legacy_data_format()` function and `legacy.data.output` attribute in `get_eurostat()` and `get_eurostat_local()` transforms modern API naming conventions into the format that was used in the old API and eurostat package versions 3.8.* and prior. It is a helper function to other functions. +* Add internal functions for retrieving SDMX metadata ## Minor updates diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 9dbd782d..1dfbef4e 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -130,28 +130,195 @@ get_eurostat_sdmx <- function( } -extract_metadata <- function(id, agency = "Eurostat") { +get_sdmx_codelist <- function(codelist_id, agency = "Eurostat", type = "list", lang = NULL) { + lang <- eurostat:::check_lang(lang) + + api_base_uri <- build_api_base_uri(agency) + + xml_url <- paste0( + api_base_uri, + "/sdmx/2.1/codelist/estat/", + codelist_id) + + xml_object <- xml2::read_xml(xml_url) + + if (identical(type, "raw")) { + return(xml_object) + } + + namespaces <- xml2::xml_ns(xml_object) + + codelists <- xml2::xml_find_first(xml_object, ".//s:Codelists", namespaces) + + code_nodes <- xml2::xml_find_all(codelists, ".//s:Code", namespaces) + + id <- c() + name <- c() + + for (node in code_nodes) { + id <- c(id, xml2::xml_attr(node, "id")) + name <- c(name, xml2::xml_text(xml2::xml_find_first(node, sprintf(".//c:Name[@xml:lang='%s']", lang)))) + } + + df <- data.frame( + id = id, + name = name + ) + + return(df) +} +get_sdmx_conceptscheme <- function(id, agency = "Eurostat", type = "list", lang = NULL) { + + lang <- eurostat:::check_lang(lang) + api_base_uri <- build_api_base_uri(agency) + + xml_url <- paste0( + api_base_uri, + "/sdmx/2.1/conceptscheme/estat/", + id) + + xml_object <- xml2::read_xml(xml_url) + + if (identical(type, "raw")) { + return(xml_object) + } + + namespaces <- xml2::xml_ns(xml_object) + + # Continue with dimensions and concept extraction + data_structure_components <- xml2::xml_find_first(xml_object, ".//s:Concepts", namespaces) + + concept_nodes <- xml2::xml_find_all(data_structure_components, ".//s:Concept", namespaces) + + # metadata <- xml2::as_list(concept_nodes) + + id <- c() + urn <- c() + name <- c() + core_representation <- c() + version <- c() + + for (node in concept_nodes) { + id <- c(id, xml2::xml_attr(node, "id")) + urn <- c(urn, xml2::xml_attr(node, "urn")) + name <- c(name, xml2::xml_text(xml2::xml_find_first(node, sprintf(".//c:Name[@xml:lang='%s']", lang)))) + if (is.na(xml2::xml_attr(xml2::xml_find_first(node, ".//Ref"), "class"))) { + core_representation <- c(core_representation, xml2::xml_attr(xml2::xml_find_first(node, ".//s:TextFormat"), "textType")) + } else { + core_representation <- c(core_representation, xml2::xml_attr(xml2::xml_find_first(node, ".//Ref"), "class")) + } + version <- c(version, xml2::xml_attr(xml2::xml_find_first(node, ".//Ref"), "version")) + } + + metadata_returnable <- data.frame( + id = id, + urn = urn, + name = name, + core_representation = core_representation, + version = version + ) + + return(metadata_returnable) +} - data_structure_definition_url <- paste0( +get_sdmx_dsd <- function(id, agency = "Eurostat", type = "list", lang = NULL) { + api_base_uri <- build_api_base_uri(agency) + + dsd_url <- paste0( api_base_uri, - "/sdmx/2.1/dataflow/estat/", + "/sdmx/2.1/datastructure/estat/", id) + + xml_object <- xml2::read_xml(dsd_url) + + if (identical(type, "raw")) { + return(xml_object) + } + + # Define namespaces + namespaces <- xml2::xml_ns(xml_object) + + # Extract Header information + header <- xml2::xml_find_first(xml_object, ".//m:Header", namespaces) + header_id <- xml2::xml_text(xml2::xml_find_first(header, ".//m:ID", namespaces)) + prepared <- substr(xml2::xml_text(xml2::xml_find_first(header, ".//m:Prepared", namespaces)),1,10) + sender_id <- xml2::xml_attr(xml2::xml_find_first(header, ".//m:Sender", namespaces), "id") + + + # Continue with dimensions and concept extraction + data_structure_components <- xml2::xml_find_first(xml_object, ".//s:DataStructureComponents", namespaces) + + dimension_nodes <- xml2::xml_find_all(data_structure_components, ".//s:Dimension | .//s:TimeDimension | .//s:AttributeList/s:Attribute | .//s:MeasureList/s:PrimaryMeasure", namespaces) + + metadata_returnable <- list() + + metadata <- xml2::as_list(dimension_nodes) + + id <- c() + urn <- c() + concept_identity_class <- c() + local_representation <- c() + local_representation_version <- c() + + for (node in dimension_nodes) { + id <- c(id, xml2::xml_attr(node, "id")) + urn <- c(urn, xml2::xml_attr(node, "urn")) + concept_identity_class <- c(concept_identity_class, xml2::xml_attr(xml_find_first(node, ".//Ref"), "class")) + if (is.na(xml2::xml_attr(xml2::xml_find_first(node, ".//Ref"), "class"))) { + local_representation <- c(local_representation, xml2::xml_attr(xml2::xml_find_first(node, ".//s:TextFormat"), "textType")) + } else { + local_representation <- c(local_representation, xml2::xml_attr(xml2::xml_find_first(node, ".//Ref"), "class")) + } + local_representation_version <- c(local_representation_version, xml2::xml_attr(xml2::xml_find_first(node, ".//s:Enumeration/Ref"), "version")) + } + + metadata_returnable <- data.frame( + id = id, + urn = urn, + concept_identity_class = concept_identity_class, + local_representation = local_representation, + local_representation_version = local_representation_version + ) + + return(metadata_returnable) +} - dsd_xml <- xml2::read_xml(data_structure_definition_url) +get_sdmx_dataflow <- function(id, agency = "Eurostat", type = "list", lang = NULL) { + + api_base_uri <- build_api_base_uri(agency) + + dataflow_url <- paste0( + api_base_uri, + "/sdmx/2.1/dataflow/estat/", + id) + + # With DSD + # dataflow_url <- paste0( + # api_base_uri, + # "/sdmx/2.1/dataflow/estat/", + # id, + # "/", + # "?references=children") + + xml_object <- xml2::read_xml(dataflow_url) + + if (identical(type, "raw")) { + return(xml_object) + } # Define namespaces - namespaces <- xml2::xml_ns(dsd_xml) + namespaces <- xml2::xml_ns(xml_object) # Extract Header information - header <- xml2::xml_find_first(dsd_xml, ".//m:Header", namespaces) + header <- xml2::xml_find_first(xml_object, ".//m:Header", namespaces) header_id <- xml2::xml_text(xml2::xml_find_first(header, ".//m:ID", namespaces)) prepared <- substr(xml2::xml_text(xml2::xml_find_first(header, ".//m:Prepared", namespaces)),1,10) sender_id <- xml2::xml_attr(xml2::xml_find_first(header, ".//m:Sender", namespaces), "id") # Continue with dataflow and annotations extraction - dataflow <- xml2::xml_find_first(dsd_xml, ".//s:Dataflow", namespaces) + dataflow <- xml2::xml_find_first(xml_object, ".//s:Dataflow", namespaces) dataflow_id <- xml2::xml_attr(dataflow, "id") urn <- xml2::xml_attr(dataflow, "urn") agencyID <- xml2::xml_attr(dataflow, "agencyID") @@ -162,6 +329,11 @@ extract_metadata <- function(id, agency = "Eurostat") { name_de <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='de']", namespaces)) name_en <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='en']", namespaces)) name_fr <- xml2::xml_text(xml2::xml_find_first(dataflow, ".//c:Name[@xml:lang='fr']", namespaces)) + if (!is.null(lang)) { + name <- xml2::xml_text(xml2::xml_find_first(dataflow, sprintf(".//c:Name[@xml:lang='%s']", lang), namespaces)) + } else { + name <- name_en + } source_institutions <- list() doi_details <- NULL @@ -181,40 +353,48 @@ extract_metadata <- function(id, agency = "Eurostat") { } else if (type == "UPDATE_DATA") { update_data_timestamp <- title } else if(type == "SOURCE_INSTITUTIONS"){ - for (text_node in texts_nodes) { - lang <- xml2::xml_attr(text_node, "xml:lang") - text <- xml2::xml_text(text_node) - source_institutions[[lang]] <- text - + if (!is.null(lang)) { + source_institutions <- xml2::xml_text( + xml2::xml_find_all(node, sprintf(".//c:AnnotationText[@xml:lang='%s']", lang)) + ) + } else { + source_institutions <- xml2::xml_text( + xml2::xml_find_all(node, ".//c:AnnotationText[@xml:lang='en']") + ) } - + } + + if (grepl("adms:Identifier", title)) { + title_xml <- xml2::read_xml(title) + doi_url <- xml2::xml_attr(xml2::xml_find_first(title_xml, ".//adms:Identifier"), "rdf:about", xml2::xml_ns(title_xml)) } } - # Extract DOI URL if the annotation contains adms:Identifier - if (grepl("adms:Identifier", title)) { - title_xml <- xml2::read_xml(title) - doi_url <- xml2::xml_attr(xml2::xml_find_first(title_xml, ".//adms:Identifier"), "rdf:about", xml2::xml_ns(title_xml)) - } + # # Extract DOI URL if the annotation contains adms:Identifier + # if (grepl("adms:Identifier", title)) { + # title_xml <- xml2::read_xml(title) + # doi_url <- xml2::xml_attr(xml2::xml_find_first(title_xml, ".//adms:Identifier"), "rdf:about", xml2::xml_ns(title_xml)) + # } metadata <- list( - Name_EN = name_en, - Name_DE = name_de, - Name_FR = name_fr, - DOI_URL = ifelse(exists("doi_url"), eval(doi_url), NA_character_), - DataflowID = dataflow_id, - AgencyID = agencyID, - ID = header_id, - Prepared = prepared, - SenderID = sender_id, - OldestPeriodTimestamp = oldest_period_timestamp, - LatestPeriodTimestamp = latest_period_timestamp, - UpdateDataTimestamp = update_data_timestamp, - SourceInstitutions = source_institutions, - URN = urn, - Version = version, - IsFinal = isFinal + name = name, + name_en = name_en, + name_de = name_de, + name_fr = name_fr, + doi_url = ifelse(exists("doi_url"), eval(doi_url), NA_character_), + dataflow_id = dataflow_id, + agency_id = agencyID, + id = header_id, + prepared = prepared, + sender_id = sender_id, + oldest_period_timestamp = oldest_period_timestamp, + latest_period_timestamp = latest_period_timestamp, + update_data_timestamp = update_data_timestamp, + source_institutions = source_institutions, + urn = urn, + version = version, + is_final = isFinal ) return(metadata) @@ -469,3 +649,84 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en", verbose = TRUE) { } x } + +#' @title Create OM compliant datasets +#' @importFrom dataset dublincore dataset_df +#' @examples +#' enriched_dataset <- om_dataset("nama_10_gdp", lang = "de") +#' +om_dataset <- function(id, agency = "eurostat", lang = "en") { + lang <- check_lang(lang) + + df <- get_eurostat_sdmx(id = id, lang = lang, agency = agency, legacy.data.output = FALSE, verbose = FALSE) + + dataflow <- eurostat:::get_sdmx_dataflow(id = id, agency = agency, type = "list", lang = lang) + + colnames <- names(df) + + if ("geo" %in% colnames) { + geo_dict <- get_sdmx_codelist("geo", lang = lang) + geo_names <- unique(geo_dict$name[geo_dict$id %in% df$geo]) + if (length(geo_names) > 40) { + coverage = "Europe" + } else { + coverage = paste(unique(geo_names), collapse = ", ") + } + } else { + coverage = "Europe" + } + + concept_scheme <- eurostat:::get_sdmx_conceptscheme(id = id, agency = agency, lang = lang) + + dsd <- get_sdmx_dsd(id = id, agency = agency, lang = lang) + + bibentry <- dataset::dublincore( + title = dataflow$name, + creator = person(given = dataflow$source_institutions, role = "cre"), + publisher = dataflow$source_institutions, # I think the publisher is Eurostat... or maybe European Commission? + datasource = dataflow$doi_url, + rights = "Creative Commons Attribution 4.0 International", # could be shortened to CC-BY 4.0 + coverage = coverage, + language = lang) + + df_with_metadata <- add_metadata(df, concept_scheme = concept_scheme, dsd = dsd) + + dataset_enriched <- dataset::dataset_df(df_with_metadata, + dataset_bibentry = bibentry) + + return(dataset_enriched) +} + + +add_metadata <- function(df, concept_scheme, + dsd) { + + # Check required columns in concept_scheme + if (!all(c("id", "name") %in% names(concept_scheme))) { + stop("concept_scheme must contain 'id' and 'name' columns.") + } + + # Make sure we can iterate over the names + df_out <- df + + for (var in names(df)) { + if (var %in% concept_scheme$id) { + + label <- concept_scheme$name[concept_scheme$id == var] + + # Construct metadata URIs + concept_uri <- concept_scheme$urn[concept_scheme$id == var] + namespace <- dsd$urn[dsd$id == var] + + # Apply defined() dynamically + df_out[[var]] <- dataset::defined( + df[[var]], + label = label, + concept = concept_uri, + namespace = namespace + ) + } + } + + return(df_out) +} diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index da02d126..eb4ac25f 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -15,7 +15,7 @@ utilities. \tab \cr \strong{Package} \tab eurostat \cr \strong{Type} \tab Package \cr - \strong{Version} \tab 4.1.0.9003 \cr + \strong{Version} \tab 4.1.0.9004 \cr \strong{Date} \tab 2014-2025 \cr \strong{License} \tab BSD_2_clause + file LICENSE \cr \strong{LazyLoad} \tab yes \cr @@ -361,7 +361,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9003, . + 4.1.0.9004, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/eurotime2date.Rd b/man/eurotime2date.Rd index e7c1eb9a..f9dd0729 100755 --- a/man/eurotime2date.Rd +++ b/man/eurotime2date.Rd @@ -59,7 +59,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9003, . +# 4.1.0.9004, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index c62c4272..5d78463d 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -390,7 +390,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9003, . + 4.1.0.9004, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_dic.Rd b/man/get_eurostat_dic.Rd index 67b9a37f..7a9b573d 100755 --- a/man/get_eurostat_dic.Rd +++ b/man/get_eurostat_dic.Rd @@ -49,7 +49,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9003, . +# 4.1.0.9004, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 0d32c95c..9d8eb6bd 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -322,7 +322,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9003, . + 4.1.0.9004, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index b99e107b..f726d208 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -129,7 +129,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9003, . +# 4.1.0.9004, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index 46055967..eae27eac 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -96,7 +96,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9003, . + 4.1.0.9004, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/om_dataset.Rd b/man/om_dataset.Rd new file mode 100644 index 00000000..ec5cfe48 --- /dev/null +++ b/man/om_dataset.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_eurostat_sdmx.R +\name{om_dataset} +\alias{om_dataset} +\title{Create OM compliant datasets} +\usage{ +om_dataset(id, agency = "eurostat", lang = "en") +} +\description{ +Create OM compliant datasets +} +\examples{ +enriched_dataset <- om_dataset("nama_10_gdp", lang = "de") + +} diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index 1909cb40..2a6d4284 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -104,7 +104,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9003, . + 4.1.0.9004, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat.Rd b/man/tidy_eurostat.Rd index 4f965e46..09c9d730 100755 --- a/man/tidy_eurostat.Rd +++ b/man/tidy_eurostat.Rd @@ -84,7 +84,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9003, . + 4.1.0.9004, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat_sdmx_csv.Rd b/man/tidy_eurostat_sdmx_csv.Rd index e2a822a7..32b6ec48 100644 --- a/man/tidy_eurostat_sdmx_csv.Rd +++ b/man/tidy_eurostat_sdmx_csv.Rd @@ -71,7 +71,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9003, . + 4.1.0.9004, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set From 363da2cffb5e43548993ebfc516ff961b31f770b Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Tue, 4 Nov 2025 15:17:13 +0200 Subject: [PATCH 42/62] Remove RefManageR dependency, minor version bump --- DESCRIPTION | 5 ++-- NAMESPACE | 2 -- NEWS.md | 6 +++++ R/get_bibentry.R | 39 ++++++++++++++++----------- man/cut_to_classes.Rd | 2 +- man/eurostat-package.Rd | 4 +-- man/eurotime2date.Rd | 8 +++--- man/eurotime2num.Rd | 2 +- man/get_bibentry.Rd | 6 ++--- man/get_eurostat.Rd | 8 +++--- man/get_eurostat_async.Rd | 4 +-- man/get_eurostat_dic.Rd | 4 +-- man/get_eurostat_json.Rd | 2 +- man/get_eurostat_local.Rd | 4 +-- man/get_eurostat_raw.Rd | 4 +-- man/get_eurostat_sdmx.Rd | 4 +-- man/get_eurostat_toc.Rd | 4 +-- man/harmonize_country_code.Rd | 2 +- man/search_eurostat.Rd | 4 +-- man/tidy_eurostat.Rd | 6 ++--- man/tidy_eurostat_sdmx_csv.Rd | 6 ++--- tests/testthat/test_01_bibliography.R | 11 -------- 22 files changed, 68 insertions(+), 69 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 323c2a11..010f5d8b 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: eurostat Title: Tools for Eurostat Open Data -Version: 4.1.0.9004 +Version: 4.1.0.9005 Date: 2025-10-16 Authors@R: c( person("Leo", "Lahti", , "leo.lahti@iki.fi", role = c("aut", "cre"), @@ -45,7 +45,6 @@ Imports: lubridate, rappdirs, readr, - RefManageR, regions, rlang, stringi, @@ -74,6 +73,6 @@ MailingList: rOpenGov NeedsCompilation: no Repository: CRAN Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 X-schema.org-isPartOf: http://ropengov.org/ X-schema.org-keywords: ropengov diff --git a/NAMESPACE b/NAMESPACE index d5bab610..7a0b2d03 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -34,8 +34,6 @@ export(set_eurostat_cache_dir) export(validate_geo_code) export(validate_nuts_regions) importFrom(ISOweek,ISOweek2date) -importFrom(RefManageR,BibEntry) -importFrom(RefManageR,toBiblatex) importFrom(classInt,classIntervals) importFrom(countrycode,countrycode) importFrom(curl,curl_download) diff --git a/NEWS.md b/NEWS.md index 387155cf..e64eac09 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# eurostat 4.1.0.9005 + +## Deprecated and defunct + +* Removed RefManageR dependency from the package due to it being unmaintained + # eurostat 4.1.0.9004 ## Major updates diff --git a/R/get_bibentry.R b/R/get_bibentry.R index 0a2fc95e..8a2e8058 100755 --- a/R/get_bibentry.R +++ b/R/get_bibentry.R @@ -14,7 +14,7 @@ #' #' @return a bibentry, Bibtex or Biblatex object. #' -#' @seealso [utils::bibentry] [RefManageR::toBiblatex] +#' @seealso [utils::bibentry] #' #' @examplesIf check_access_to_data() #' \dontrun{ @@ -31,13 +31,12 @@ #' #' @importFrom lubridate dmy year month day #' @importFrom utils toBibtex person -#' @importFrom RefManageR BibEntry toBiblatex #' @importFrom stringr str_glue #' #' @export get_bibentry <- function(code, keywords = NULL, - format = "Biblatex", + format = "bibtex", lang = "en") { if (!any(class(code) %in% c("character", "factor"))) { stop("The code(s) must be added as character vector") @@ -49,10 +48,10 @@ get_bibentry <- function(code, code <- as.character(code) format <- tolower(as.character(format)) - if (!format %in% c("bibentry", "bibtex", "biblatex")) { - warning("The ", format, " is not recognized, will return Biblatex as + if (!format %in% c("bibentry", "bibtex")) { + warning("The ", format, " is not recognized, will return bibtex as default.") - format <- "biblatex" + format <- "bibtex" } toc <- get_eurostat_toc(lang = lang) @@ -66,9 +65,9 @@ get_bibentry <- function(code, lang <- check_lang(lang) lang_long <- switch(lang, - en = "english", - fr = "french", - de = "german") + en = "English", + fr = "French", + de = "German") if (nrow(toc) == 0) { warning(paste( @@ -119,19 +118,28 @@ get_bibentry <- function(code, } else { keyword_entry <- NULL } - - entry <- RefManageR::BibEntry( + + sdmx_dataflow <- get_sdmx_dataflow(id = toc$code[i], agency = "Eurostat") + + unexpanded_doi <- "" + if (!is.na(sdmx_dataflow$doi_url)) { + unexpanded_doi <- gsub("https://doi.org/", "", sdmx_dataflow$doi_url) + unexpanded_doi <- gsub("_", "\\\\_", unexpanded_doi) + } + + entry <- utils::bibentry( bibtype = "misc", key = dataset_key, title = paste0(toc$title[i], " (", dataset_id, ")"), + author = c( + utils::person(given = "Eurostat") + ), url = paste0("https://ec.europa.eu/eurostat/web/products-datasets/product?code=", toc$code[i]), language = lang_long, # date = last_update_date, year = last_update_year, - author = c( - utils::person(given = "Eurostat") - ), + doi = unexpanded_doi, keywords = keyword_entry, urldate = urldate, type = "Dataset", @@ -150,8 +158,7 @@ get_bibentry <- function(code, if (format == "bibtex") { entries <- utils::toBibtex(entries) - } else if (format == "biblatex") { - entries <- RefManageR::toBiblatex(entries) } + # if entry is bibentry then that will be returned instead of bibtex entries } diff --git a/man/cut_to_classes.Rd b/man/cut_to_classes.Rd index eb34cc09..db441b76 100755 --- a/man/cut_to_classes.Rd +++ b/man/cut_to_classes.Rd @@ -38,7 +38,7 @@ Categorises a numeric vector into automatic or manually defined categories and polishes the labels ready for used in mapping with \code{ggplot2}. } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \donttest{ # lp <- get_eurostat("nama_aux_lp") lp <- get_eurostat("nama_10_lp_ulc") diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index eb4ac25f..3f0a94fd 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -15,7 +15,7 @@ utilities. \tab \cr \strong{Package} \tab eurostat \cr \strong{Type} \tab Package \cr - \strong{Version} \tab 4.1.0.9004 \cr + \strong{Version} \tab 4.1.0.9005 \cr \strong{Date} \tab 2014-2025 \cr \strong{License} \tab BSD_2_clause + file LICENSE \cr \strong{LazyLoad} \tab yes \cr @@ -361,7 +361,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9004, . + 4.1.0.9005, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/eurotime2date.Rd b/man/eurotime2date.Rd index f9dd0729..5e0c608b 100755 --- a/man/eurotime2date.Rd +++ b/man/eurotime2date.Rd @@ -14,11 +14,11 @@ the first date of the period (month, quarter or year). If \code{TRUE} the date is the last date of the period.} } \value{ -an object of class \code{\link[=Date]{Date()}}. +an object of class \code{\link[lubridate:date_utils]{lubridate::Date()}}. } \description{ Date conversion from Eurostat time format. A function to -convert Eurostat time values to objects of class \code{\link[=Date]{Date()}} +convert Eurostat time values to objects of class \code{\link[lubridate:date_utils]{lubridate::Date()}} representing calendar dates. } \details{ @@ -26,7 +26,7 @@ Available patterns are YYYY (year), YYYY-SN (semester), YYYY-QN (quarter), YYYY-MM (month), YYYY-WNN (week) and YYYY-MM-DD (day). } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \donttest{ na_q <- get_eurostat("namq_10_pc", time_format = "raw") na_q$TIME_PERIOD <- eurotime2date(x = na_q$TIME_PERIOD) @@ -59,7 +59,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9004, . +# 4.1.0.9005, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/eurotime2num.Rd b/man/eurotime2num.Rd index 5e408d4d..ebc9137a 100755 --- a/man/eurotime2num.Rd +++ b/man/eurotime2num.Rd @@ -21,7 +21,7 @@ a fraction of the year in beginning of the period. Conversion of daily data is not supported. } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \donttest{ na_q <- get_eurostat("namq_10_pc", time_format = "raw") na_q$TIME_PERIOD <- eurotime2num(x = na_q$TIME_PERIOD) diff --git a/man/get_bibentry.Rd b/man/get_bibentry.Rd index 76a800be..30600b0b 100755 --- a/man/get_bibentry.Rd +++ b/man/get_bibentry.Rd @@ -4,7 +4,7 @@ \alias{get_bibentry} \title{Create A Data Bibliography} \usage{ -get_bibentry(code, keywords = NULL, format = "Biblatex", lang = "en") +get_bibentry(code, keywords = NULL, format = "bibtex", lang = "en") } \arguments{ \item{code}{A Eurostat data code or a vector of Eurostat data codes as @@ -55,7 +55,7 @@ in \code{\link[=get_eurostat]{get_eurostat()}} documentation. } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \dontrun{ my_bibliography <- get_bibentry( code = c("tran_hv_frtra", "tec00001"), @@ -70,7 +70,7 @@ in \code{\link[=get_eurostat]{get_eurostat()}} documentation. \dontshow{\}) # examplesIf} } \seealso{ -\link[utils:bibentry]{utils::bibentry} \link[RefManageR:toBiblatex]{RefManageR::toBiblatex} +\link[utils:bibentry]{utils::bibentry} } \author{ Daniel Antal, Przemyslaw Biecek diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index 5d78463d..418842f1 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -29,9 +29,9 @@ known \code{\link[=search_eurostat]{search_eurostat()}} function can be used to of contents.} \item{time_format}{a string giving a type of the conversion of the time column from the -eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +eurostat format. The default argument "\code{date}" converts to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class with the date being the first day of the period. A "\code{date_last}" argument -converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +converts the dataset date to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class object with the difference that the exact date is the last date of the period. Period can be year, semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for more information). @@ -327,7 +327,7 @@ what different arguments do } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \dontrun{ k <- get_eurostat("nama_10_lp_ulc") k <- get_eurostat("nama_10_lp_ulc", time_format = "num") @@ -390,7 +390,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9004, . + 4.1.0.9005, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_async.Rd b/man/get_eurostat_async.Rd index 08e2d9fe..2f9d6094 100644 --- a/man/get_eurostat_async.Rd +++ b/man/get_eurostat_async.Rd @@ -23,9 +23,9 @@ get_eurostat_async( \item{id}{Character. Async request ID from initial SDMX call.} \item{time_format}{a string giving a type of the conversion of the time column from the -eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +eurostat format. The default argument "\code{date}" converts to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class with the date being the first day of the period. A "\code{date_last}" argument -converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +converts the dataset date to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class object with the difference that the exact date is the last date of the period. Period can be year, semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for more information). diff --git a/man/get_eurostat_dic.Rd b/man/get_eurostat_dic.Rd index 7a9b573d..5f9bf8f1 100755 --- a/man/get_eurostat_dic.Rd +++ b/man/get_eurostat_dic.Rd @@ -24,7 +24,7 @@ human-readable labels. To translate codes to labels, use \code{\link[=label_eurostat]{label_eurostat()}}. } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \donttest{ get_eurostat_dic("crop_pro") @@ -49,7 +49,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9004, . +# 4.1.0.9005, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 9d8eb6bd..43f17578 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -322,7 +322,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9004, . + 4.1.0.9005, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_local.Rd b/man/get_eurostat_local.Rd index c10c4554..ea3c0be9 100644 --- a/man/get_eurostat_local.Rd +++ b/man/get_eurostat_local.Rd @@ -19,9 +19,9 @@ get_eurostat_local( \item{file}{file path as string} \item{time_format}{a string giving a type of the conversion of the time column from the -eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +eurostat format. The default argument "\code{date}" converts to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class with the date being the first day of the period. A "\code{date_last}" argument -converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +converts the dataset date to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class object with the difference that the exact date is the last date of the period. Period can be year, semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for more information). diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index f726d208..3ba8dc64 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -107,7 +107,7 @@ consult the following Eurostat documentation: } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \donttest{ eurostat:::get_eurostat_raw("educ_iste") } @@ -129,7 +129,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9004, . +# 4.1.0.9005, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_sdmx.Rd b/man/get_eurostat_sdmx.Rd index 322498d0..012a1e1d 100644 --- a/man/get_eurostat_sdmx.Rd +++ b/man/get_eurostat_sdmx.Rd @@ -26,9 +26,9 @@ known \code{\link[=search_eurostat]{search_eurostat()}} function can be used to of contents.} \item{time_format}{a string giving a type of the conversion of the time column from the -eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +eurostat format. The default argument "\code{date}" converts to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class with the date being the first day of the period. A "\code{date_last}" argument -converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +converts the dataset date to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class object with the difference that the exact date is the last date of the period. Period can be year, semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for more information). diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index eae27eac..a197469b 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -53,7 +53,7 @@ See Eurostat documentation on TOC items: } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \donttest{ tmp <- get_eurostat_toc() head(tmp) @@ -96,7 +96,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9004, . + 4.1.0.9005, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/harmonize_country_code.Rd b/man/harmonize_country_code.Rd index 27e58eb5..bea8fee8 100755 --- a/man/harmonize_country_code.Rd +++ b/man/harmonize_country_code.Rd @@ -20,7 +20,7 @@ United Kingdom. This function turns country codes into to ISO 3166-1 alpha-2. } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \donttest{ lp <- get_eurostat("nama_10_lp_ulc") lp$geo <- harmonize_country_code(lp$geo) diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index 2a6d4284..00f3a17b 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -78,7 +78,7 @@ See Eurostat documentation on TOC items: } \examples{ -\dontshow{if (check_access_to_data()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\dontshow{if (check_access_to_data()) withAutoprint(\{ # examplesIf} \donttest{ tmp <- search_eurostat("education") head(tmp) @@ -104,7 +104,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9004, . + 4.1.0.9005, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat.Rd b/man/tidy_eurostat.Rd index 09c9d730..53ab8700 100755 --- a/man/tidy_eurostat.Rd +++ b/man/tidy_eurostat.Rd @@ -17,9 +17,9 @@ tidy_eurostat( \item{dat}{a data_frame from \code{\link[=get_eurostat_raw]{get_eurostat_raw()}}.} \item{time_format}{a string giving a type of the conversion of the time column from the -eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +eurostat format. The default argument "\code{date}" converts to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class with the date being the first day of the period. A "\code{date_last}" argument -converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +converts the dataset date to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class object with the difference that the exact date is the last date of the period. Period can be year, semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for more information). @@ -84,7 +84,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9004, . + 4.1.0.9005, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat_sdmx_csv.Rd b/man/tidy_eurostat_sdmx_csv.Rd index 32b6ec48..1557a30a 100644 --- a/man/tidy_eurostat_sdmx_csv.Rd +++ b/man/tidy_eurostat_sdmx_csv.Rd @@ -15,9 +15,9 @@ tidy_eurostat_sdmx_csv( \item{dat}{a data_frame from \code{\link[=get_eurostat_raw]{get_eurostat_raw()}}.} \item{time_format}{a string giving a type of the conversion of the time column from the -eurostat format. The default argument "\code{date}" converts to a \code{\link[=Date]{Date()}} class +eurostat format. The default argument "\code{date}" converts to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class with the date being the first day of the period. A "\code{date_last}" argument -converts the dataset date to a \code{\link[=Date]{Date()}} class object with the difference +converts the dataset date to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class object with the difference that the exact date is the last date of the period. Period can be year, semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for more information). @@ -71,7 +71,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9004, . + 4.1.0.9005, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/tests/testthat/test_01_bibliography.R b/tests/testthat/test_01_bibliography.R index 32374ba3..f72bcdd6 100755 --- a/tests/testthat/test_01_bibliography.R +++ b/tests/testthat/test_01_bibliography.R @@ -1,17 +1,6 @@ test_that("Bibentry gives correct results", { skip_on_cran() skip_if_offline() - expect_equal( - class(get_bibentry( - code = c("sts_inpr_a", "nama_10_gdp"), - keywords = list( - c("production", "industry"), - c("GDP") - ), - format = "Biblatex" - )), - "Bibtex" - ) expect_equal( class( From caf15c898ce04858a4587962bdd7be01dfd0ff96 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Thu, 6 Nov 2025 16:56:15 +0200 Subject: [PATCH 43/62] Dev version 4.1.0.9006: Remove experimental functions, fix tests --- DESCRIPTION | 8 +- NAMESPACE | 9 +- NEWS.md | 6 ++ R/get_eurostat.R | 2 +- R/get_eurostat_async.R | 160 ---------------------------------- R/get_eurostat_sdmx.R | 90 ++----------------- man/eurostat-package.Rd | 4 +- man/eurotime2date.Rd | 2 +- man/get_eurostat.Rd | 4 +- man/get_eurostat_async.Rd | 78 ----------------- man/get_eurostat_dic.Rd | 2 +- man/get_eurostat_json.Rd | 2 +- man/get_eurostat_raw.Rd | 2 +- man/get_eurostat_toc.Rd | 2 +- man/om_dataset.Rd | 15 ---- man/search_eurostat.Rd | 2 +- man/tidy_eurostat.Rd | 2 +- man/tidy_eurostat_sdmx_csv.Rd | 2 +- tests/testthat/test_04_get.R | 11 +-- 19 files changed, 37 insertions(+), 366 deletions(-) delete mode 100644 R/get_eurostat_async.R delete mode 100644 man/get_eurostat_async.Rd delete mode 100644 man/om_dataset.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 010f5d8b..362777a6 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: eurostat Title: Tools for Eurostat Open Data -Version: 4.1.0.9005 -Date: 2025-10-16 +Version: 4.1.0.9006 +Date: 2025-11-06 Authors@R: c( person("Leo", "Lahti", , "leo.lahti@iki.fi", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5537-637X")), @@ -20,6 +20,7 @@ Authors@R: c( person("Oliver", "Reiter", role = "ctb"), person("Pyry", "Kantanen", role = "aut", comment = c(ORCID = "0000-0003-2853-2765")), + person("Akewak", "Jeba", role = "ctb"), person("Enrico", "Spinielli", , "enrico.spinielli@gmail.com", role = "ctb", comment = c(ORCID = "0000-0001-8584-9131")) ) @@ -52,8 +53,7 @@ Imports: tibble, tidyr (>= 1.0.0), xml2, - data.table (>= 1.14.8), - dataset (>= 0.4.0) + data.table (>= 1.14.8) Suggests: giscoR, knitr, diff --git a/NAMESPACE b/NAMESPACE index 7a0b2d03..88246810 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,7 +9,6 @@ export(eurotime2date) export(eurotime2num) export(get_bibentry) export(get_eurostat) -export(get_eurostat_async) export(get_eurostat_dic) export(get_eurostat_folder) export(get_eurostat_geospatial) @@ -37,15 +36,12 @@ importFrom(ISOweek,ISOweek2date) importFrom(classInt,classIntervals) importFrom(countrycode,countrycode) importFrom(curl,curl_download) -importFrom(data.table,"%chin%") importFrom(data.table,":=") importFrom(data.table,.SD) importFrom(data.table,fread) importFrom(data.table,melt) importFrom(data.table,setDT) importFrom(data.table,setcolorder) -importFrom(dataset,dataset_df) -importFrom(dataset,dublincore) importFrom(digest,digest) importFrom(dplyr,"%>%") importFrom(dplyr,case_when) @@ -64,11 +60,9 @@ importFrom(httr2,req_user_agent) importFrom(httr2,request) importFrom(httr2,resp_body_json) importFrom(httr2,resp_body_raw) -importFrom(httr2,resp_body_xml) importFrom(httr2,resp_check_status) importFrom(httr2,resp_content_type) importFrom(httr2,resp_is_error) -importFrom(httr2,resp_status) importFrom(httr2,url_build) importFrom(httr2,url_parse) importFrom(jsonlite,fromJSON) @@ -109,8 +103,9 @@ importFrom(utils,person) importFrom(utils,read.csv) importFrom(utils,read.delim) importFrom(utils,toBibtex) -importFrom(utils,unzip) importFrom(xml2,read_xml) +importFrom(xml2,xml_attr) importFrom(xml2,xml_find_all) importFrom(xml2,xml_find_first) +importFrom(xml2,xml_ns) importFrom(xml2,xml_text) diff --git a/NEWS.md b/NEWS.md index e64eac09..4360a9d9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# eurostat 4.1.0.9006 + +## Minor updates + +* Removed experimental functions from the codebase, fixed tests that were pointing at datasets renamed by Eurostat + # eurostat 4.1.0.9005 ## Deprecated and defunct diff --git a/R/get_eurostat.R b/R/get_eurostat.R index 52ca0f71..c38c00e0 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -142,7 +142,7 @@ #' set_eurostat_cache_dir(file.path(tempdir(), "r_cache2")) #' k <- get_eurostat("nama_10_lp_ulc") #' k <- get_eurostat("nama_10_lp_ulc", cache = FALSE) -#' k <- get_eurostat("avia_gonc", select_time = "Y", cache = FALSE) +#' k <- get_eurostat("avia_gooc", select_time = "Y", cache = FALSE) #' #' dd <- get_eurostat("nama_10_gdp", #' filters = list( diff --git a/R/get_eurostat_async.R b/R/get_eurostat_async.R deleted file mode 100644 index 523a8787..00000000 --- a/R/get_eurostat_async.R +++ /dev/null @@ -1,160 +0,0 @@ -#' @title Get Asynchronous Eurostat SDMX Data by ID -#' -#' @description -#' `r lifecycle::badge('experimental')` -#' Polls Eurostat's async API for the status of a submitted request and downloads the data when available. -#' -#' @inheritParams get_eurostat_sdmx -#' @param id Character. Async request ID from initial SDMX call. -#' @param wait Integer. Seconds between status checks. Default is 1 second. -#' @param max_wait Integer. Max time to wait in seconds. Default is 60 seconds. -#' @param compressed Logical. Download data in compressed format? Default is TRUE. -#' @inheritParams get_eurostat -#' @importFrom data.table %chin% -#' @importFrom httr2 request req_user_agent req_perform resp_body_xml -#' @importFrom httr2 resp_status resp_body_raw -#' @importFrom utils unzip -#' @importFrom xml2 xml_find_first -#' @importFrom stats setNames -#' -#' @return A data.frame containing the SDMX-CSV data. -#' -#' @export -get_eurostat_async <- function( - id, - time_format = "date", - type = "code", - lang = "en", - use.data.table, - agency = "Eurostat", - keepFlags = FALSE, - legacy.data.output = FALSE, - wait = 1, - max_wait = 60, - compressed = TRUE, - verbose = TRUE -) { - # Fix for NSE NOTEs using data.table - OBS_FLAG = TIME_PERIOD = OBS_VALUE = NULL - - stopifnot(nzchar(id)) - lang <- check_lang(lang) - agency <- tolower(agency) - api_base_uri <- build_api_base_uri(agency) - agencyID <- build_agencyID(agency) - - stopifnot(!is.null(api_base_uri)) - - status_url <- paste0(api_base_uri, "/1.0/async/status/", id) - data_url <- paste0(api_base_uri, "/1.0/async/data/", id, - "?format=SDMX-CSV", - if (compressed) "&compressed=true" else "&compressed=false") - - wait_times <- seq(wait, max_wait, by = wait) - - status_check <- function(t) { - Sys.sleep(wait) - resp <- httr2::request(status_url) %>% - httr2::req_user_agent("https://github.com/rOpenGov/eurostat") %>% - httr2::req_perform() - content <- httr2::resp_body_xml(resp) - xml2::xml_text(xml2::xml_find_first(content, ".//status")) - } - - statuses <- vapply(wait_times, status_check, FUN.VALUE = character(1)) - - status_flags <- stats::setNames(seq_along(statuses), statuses) - available_pos <- status_flags["AVAILABLE"] - fail_pos <- which(statuses %in% c("EXPIRED", "ERROR", "UNKNOWN_REQUEST"))[1] - - outcome_index <- which(c(length(available_pos) > 0, length(fail_pos) > 0, TRUE))[1] - outcome <- c("success", "fail", "timeout")[outcome_index] - - if (verbose) message(sprintf("Final status after %d seconds: %s", wait_times[length(statuses)], outcome)) - - handlers <- list( - "success" = function() { - tfile <- tempfile() - on.exit(unlink(tfile), add = TRUE) - - # Enhanced download with retries and HTTP checks - download_attempt <- function(url, destfile, retries = 3, delay = 5) { - for (i in seq_len(retries)) { - resp <- httr2::request(url) %>% httr2::req_perform() - if (httr2::resp_status(resp) == 200) { - writeBin(httr2::resp_body_raw(resp), destfile) - return(TRUE) - } - if (i < retries) { - if (verbose) message("Download failed (HTTP ", httr2::resp_status(resp), "). Retrying...") - Sys.sleep(delay) - } - } - stop("Download failed after ", retries, " attempts. Last HTTP status: ", httr2::resp_status(resp)) - } - - tryCatch( - download_attempt(data_url, tfile), - error = function(e) stop("Data download failed: ", e$message) - ) - - # Handle compressed/uncompressed content - if (compressed) { - is_zip <- identical(readBin(tfile, what = "raw", n = 2), charToRaw("PK")) - if (is_zip) { - zip_list <- utils::unzip(tfile, list = TRUE) - if (nrow(zip_list) == 0) stop("Empty ZIP archive received") - csv_file <- utils::unzip(tfile, exdir = tempdir())[1] - } else { - if (verbose) message("Expected ZIP archive, but received CSV instead - continuing anyway.") - csv_file <- tfile - } - } else { - csv_file <- tfile - } - on.exit(unlink(csv_file), add = TRUE) - - # Read data with data.table - dat <- data.table::fread(csv_file, colClasses = "character") - data.table::setnames(dat, toupper(names(dat))) - - # Cleanup flags - if (!keepFlags && "OBS_FLAG" %chin% names(dat)) { - dat[, OBS_FLAG := NULL] - } - - # Convert time format - #dat[, TIME_PERIOD := convert_time_col(TIME_PERIOD, time_format)] - if ("TIME_PERIOD" %in% names(dat)) { - dat[, TIME_PERIOD := convert_time_col(TIME_PERIOD, time_format)] - } else { - warning("'TIME_PERIOD' column not found in the downloaded data.") - } - - # Convert values to numeric - if ("OBS_VALUE" %chin% names(dat)) { - dat[, OBS_VALUE := as.numeric(OBS_VALUE)] - } - - # Apply labels if requested - if (type == "label") { - dat <- label_eurostat_sdmx(dat, agency = agency, id = id, lang = lang) - } - - # Convert to legacy format if needed - if (legacy.data.output) { - dat <- legacy_data_format(dat) - } - - return(dat) - }, - "fail" = function() { - stop("Eurostat async request failed. Final status: ", statuses[fail_pos]) - }, - "timeout" = function() { - stop("Eurostat async request timed out after ", max_wait, " seconds.") - } - ) - - handlers[[outcome]]() -} diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 1dfbef4e..4b50ef35 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -13,12 +13,14 @@ #' what is the best way to interact with SDMX APIs. #' #' @inheritParams get_eurostat -#' @inheritParams get_eurostat_async #' @param agency Either "Eurostat" (default), "Eurostat_comext" #' (for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW" #' @param use.data.table Use data.table to process files? Default is FALSE. #' If data.table is used, data will be downloaded as a TSV file and #' processed using [tidy_eurostat()] +#' @param wait Integer. Seconds between status checks. Default is 1 second. +#' @param max_wait Integer. Max time to wait in seconds. Default is 60 seconds. +#' @param compressed Logical. Download data in compressed format? Default is TRUE. #' #' @importFrom curl curl_download #' @importFrom utils download.file @@ -131,7 +133,7 @@ get_eurostat_sdmx <- function( } get_sdmx_codelist <- function(codelist_id, agency = "Eurostat", type = "list", lang = NULL) { - lang <- eurostat:::check_lang(lang) + lang <- check_lang(lang) api_base_uri <- build_api_base_uri(agency) @@ -170,7 +172,7 @@ get_sdmx_codelist <- function(codelist_id, agency = "Eurostat", type = "list", l get_sdmx_conceptscheme <- function(id, agency = "Eurostat", type = "list", lang = NULL) { - lang <- eurostat:::check_lang(lang) + lang <- check_lang(lang) api_base_uri <- build_api_base_uri(agency) @@ -223,6 +225,7 @@ get_sdmx_conceptscheme <- function(id, agency = "Eurostat", type = "list", lang return(metadata_returnable) } +#' @importFrom xml2 xml_ns xml_find_first xml_text xml_attr xml_find_all get_sdmx_dsd <- function(id, agency = "Eurostat", type = "list", lang = NULL) { api_base_uri <- build_api_base_uri(agency) @@ -649,84 +652,3 @@ label_eurostat_sdmx <- function(x, agency, id, lang = "en", verbose = TRUE) { } x } - -#' @title Create OM compliant datasets -#' @importFrom dataset dublincore dataset_df -#' @examples -#' enriched_dataset <- om_dataset("nama_10_gdp", lang = "de") -#' -om_dataset <- function(id, agency = "eurostat", lang = "en") { - lang <- check_lang(lang) - - df <- get_eurostat_sdmx(id = id, lang = lang, agency = agency, legacy.data.output = FALSE, verbose = FALSE) - - dataflow <- eurostat:::get_sdmx_dataflow(id = id, agency = agency, type = "list", lang = lang) - - colnames <- names(df) - - if ("geo" %in% colnames) { - geo_dict <- get_sdmx_codelist("geo", lang = lang) - geo_names <- unique(geo_dict$name[geo_dict$id %in% df$geo]) - if (length(geo_names) > 40) { - coverage = "Europe" - } else { - coverage = paste(unique(geo_names), collapse = ", ") - } - } else { - coverage = "Europe" - } - - concept_scheme <- eurostat:::get_sdmx_conceptscheme(id = id, agency = agency, lang = lang) - - dsd <- get_sdmx_dsd(id = id, agency = agency, lang = lang) - - bibentry <- dataset::dublincore( - title = dataflow$name, - creator = person(given = dataflow$source_institutions, role = "cre"), - publisher = dataflow$source_institutions, # I think the publisher is Eurostat... or maybe European Commission? - datasource = dataflow$doi_url, - rights = "Creative Commons Attribution 4.0 International", # could be shortened to CC-BY 4.0 - coverage = coverage, - language = lang) - - df_with_metadata <- add_metadata(df, concept_scheme = concept_scheme, dsd = dsd) - - dataset_enriched <- dataset::dataset_df(df_with_metadata, - dataset_bibentry = bibentry) - - return(dataset_enriched) -} - - -add_metadata <- function(df, concept_scheme, - dsd) { - - # Check required columns in concept_scheme - if (!all(c("id", "name") %in% names(concept_scheme))) { - stop("concept_scheme must contain 'id' and 'name' columns.") - } - - # Make sure we can iterate over the names - df_out <- df - - for (var in names(df)) { - if (var %in% concept_scheme$id) { - - label <- concept_scheme$name[concept_scheme$id == var] - - # Construct metadata URIs - concept_uri <- concept_scheme$urn[concept_scheme$id == var] - namespace <- dsd$urn[dsd$id == var] - - # Apply defined() dynamically - df_out[[var]] <- dataset::defined( - df[[var]], - label = label, - concept = concept_uri, - namespace = namespace - ) - } - } - - return(df_out) -} diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index 3f0a94fd..32fba772 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -15,7 +15,7 @@ utilities. \tab \cr \strong{Package} \tab eurostat \cr \strong{Type} \tab Package \cr - \strong{Version} \tab 4.1.0.9005 \cr + \strong{Version} \tab 4.1.0.9006 \cr \strong{Date} \tab 2014-2025 \cr \strong{License} \tab BSD_2_clause + file LICENSE \cr \strong{LazyLoad} \tab yes \cr @@ -361,7 +361,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9005, . + 4.1.0.9006, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/eurotime2date.Rd b/man/eurotime2date.Rd index 5e0c608b..b91f0a4a 100755 --- a/man/eurotime2date.Rd +++ b/man/eurotime2date.Rd @@ -59,7 +59,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9005, . +# 4.1.0.9006, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index 418842f1..63a93f34 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -343,7 +343,7 @@ options(eurostat_update = FALSE) set_eurostat_cache_dir(file.path(tempdir(), "r_cache2")) k <- get_eurostat("nama_10_lp_ulc") k <- get_eurostat("nama_10_lp_ulc", cache = FALSE) -k <- get_eurostat("avia_gonc", select_time = "Y", cache = FALSE) +k <- get_eurostat("avia_gooc", select_time = "Y", cache = FALSE) dd <- get_eurostat("nama_10_gdp", filters = list( @@ -390,7 +390,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9005, . + 4.1.0.9006, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_async.Rd b/man/get_eurostat_async.Rd deleted file mode 100644 index 2f9d6094..00000000 --- a/man/get_eurostat_async.Rd +++ /dev/null @@ -1,78 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_eurostat_async.R -\name{get_eurostat_async} -\alias{get_eurostat_async} -\title{Get Asynchronous Eurostat SDMX Data by ID} -\usage{ -get_eurostat_async( - id, - time_format = "date", - type = "code", - lang = "en", - use.data.table, - agency = "Eurostat", - keepFlags = FALSE, - legacy.data.output = FALSE, - wait = 1, - max_wait = 60, - compressed = TRUE, - verbose = TRUE -) -} -\arguments{ -\item{id}{Character. Async request ID from initial SDMX call.} - -\item{time_format}{a string giving a type of the conversion of the time column from the -eurostat format. The default argument "\code{date}" converts to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class -with the date being the first day of the period. A "\code{date_last}" argument -converts the dataset date to a \code{\link[lubridate:date_utils]{lubridate::Date()}} class object with the difference -that the exact date is the last date of the period. Period can be year, -semester (half year), quarter, month, or week (See \code{\link[=eurotime2date]{eurotime2date()}} for -more information). -Argument "\code{num}" converts the date into a numeric (integer) meaning that -the first day of the year 2000 is close to 2000.01 and the last day of the -year is close to 2000.99 (see \code{\link[=eurotime2num]{eurotime2num()}} for more information). -Using the argument "\code{raw}" preserves the dates as they were in the original -Eurostat data.} - -\item{type}{A type of variables, "\code{code}" (default), "\code{label}" or "\code{both}". -The parameter "\code{both}" will return a data_frame with named vectors, -labels as values and codes as names.} - -\item{lang}{2-letter language code, default is "\code{en}" (English), other -options are "\code{fr}" (French) and "\code{de}" (German). Used for labeling datasets.} - -\item{use.data.table}{Use data.table to process files? Default is FALSE. -If data.table is used, data will be downloaded as a TSV file and -processed using \code{\link[=tidy_eurostat]{tidy_eurostat()}}} - -\item{agency}{Either "Eurostat" (default), "Eurostat_comext" -(for Comext and Prodcom datasets), "COMP", "EMPL" or "GROW"} - -\item{keepFlags}{a logical whether the flags (e.g. "confidential", -"provisional") should be kept in a separate column or if they -can be removed. Default is \code{FALSE}. For flag values see: -\url{https://ec.europa.eu/eurostat/data/database/information}. -Also possible non-real zero "0n" is indicated in flags column. -Flags are not available for eurostat API, so \code{keepFlags} -can not be used with a \code{filters}.} - -\item{legacy.data.output}{Use legacy column names and data object structure. -Default is FALSE. If TRUE, the object will try to emulate the naming -conventions of eurostat package version 3.7.x and earlier.} - -\item{wait}{Integer. Seconds between status checks. Default is 1 second.} - -\item{max_wait}{Integer. Max time to wait in seconds. Default is 60 seconds.} - -\item{compressed}{Logical. Download data in compressed format? Default is TRUE.} - -\item{verbose}{Output messages when downloading data. Default is \code{TRUE}.} -} -\value{ -A data.frame containing the SDMX-CSV data. -} -\description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} -Polls Eurostat's async API for the status of a submitted request and downloads the data when available. -} diff --git a/man/get_eurostat_dic.Rd b/man/get_eurostat_dic.Rd index 5f9bf8f1..bdb782a1 100755 --- a/man/get_eurostat_dic.Rd +++ b/man/get_eurostat_dic.Rd @@ -49,7 +49,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9005, . +# 4.1.0.9006, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 43f17578..4a2861e4 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -322,7 +322,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9005, . + 4.1.0.9006, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index 3ba8dc64..4c634655 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -129,7 +129,7 @@ See \code{citation("eurostat")}: # Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9005, . +# 4.1.0.9006, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index a197469b..b1190a0b 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -96,7 +96,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9005, . + 4.1.0.9006, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/om_dataset.Rd b/man/om_dataset.Rd deleted file mode 100644 index ec5cfe48..00000000 --- a/man/om_dataset.Rd +++ /dev/null @@ -1,15 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_eurostat_sdmx.R -\name{om_dataset} -\alias{om_dataset} -\title{Create OM compliant datasets} -\usage{ -om_dataset(id, agency = "eurostat", lang = "en") -} -\description{ -Create OM compliant datasets -} -\examples{ -enriched_dataset <- om_dataset("nama_10_gdp", lang = "de") - -} diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index 00f3a17b..dfe039c8 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -104,7 +104,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9005, . + 4.1.0.9006, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat.Rd b/man/tidy_eurostat.Rd index 53ab8700..6fc09ef9 100755 --- a/man/tidy_eurostat.Rd +++ b/man/tidy_eurostat.Rd @@ -84,7 +84,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9005, . + 4.1.0.9006, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat_sdmx_csv.Rd b/man/tidy_eurostat_sdmx_csv.Rd index 1557a30a..3f6a9561 100644 --- a/man/tidy_eurostat_sdmx_csv.Rd +++ b/man/tidy_eurostat_sdmx_csv.Rd @@ -71,7 +71,7 @@ used: Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9005, . + 4.1.0.9006, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/tests/testthat/test_04_get.R b/tests/testthat/test_04_get.R index dc525b7d..c82a2cbc 100755 --- a/tests/testthat/test_04_get.R +++ b/tests/testthat/test_04_get.R @@ -21,14 +21,15 @@ test_that("get_eurostat (dissemination API) includes TIME_PERIOD and value", { test_that("get_eurostat (dissemination API) produces a message with multiple select_time", { skip_on_cran() skip_if_offline() - expect_message(get_eurostat(id = "avia_gonc", - select_time = c("A", "M", "Q"))) + expect_message(get_eurostat(id = "avia_gooc", + select_time = c("A", "M", "Q"), + filters = list(TIME_PERIOD = 2001))) }) test_that("get_eurostat (dissemination API) produces an error with imaginary select_time parameters", { skip_on_cran() skip_if_offline() - expect_error(get_eurostat(id = "avia_gonc", + expect_error(get_eurostat(id = "avia_gooc", select_time = c("X", "Y", "Z"))) # sleep for a while to let the API rest Sys.sleep(5) @@ -37,11 +38,11 @@ test_that("get_eurostat (dissemination API) produces an error with imaginary sel test_that("get_eurostat (dissemination API) works correctly with multi-frequency", { skip_on_cran() skip_if_offline() - expect_message(get_eurostat("avia_gonc", + expect_message(get_eurostat("avia_gooc", cache = FALSE, time_format = "date_last") ) - expect_match(as.character(unique(get_eurostat("avia_gonc", + expect_match(as.character(unique(get_eurostat("avia_gooc", select_time = NULL, cache = FALSE)$TIME_PERIOD)), "-01-01") From de31c6515adde3f5f2388cdb02c9e03d0553d09c Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Mon, 2 Feb 2026 15:02:09 +0100 Subject: [PATCH 44/62] Add 2024 as a valid NUTS release year --- R/get_eurostat_geospatial.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_eurostat_geospatial.R b/R/get_eurostat_geospatial.R index 6f7525c6..b459ba74 100755 --- a/R/get_eurostat_geospatial.R +++ b/R/get_eurostat_geospatial.R @@ -18,7 +18,7 @@ #' @param nuts_level Level of NUTS classification of the geospatial data. One of #' "0", "1", "2", "3" or "all" (mimics the original behaviour) #' @param year NUTS release year. One of -#' "2003", "2006", "2010", "2013", "2016" or "2021" +#' "2003", "2006", "2010", "2013", "2016", "2021" or "2024" #' @param cache a logical whether to do caching. Default is `TRUE`. #' @param update_cache a logical whether to update cache. Can be set also with #' `options(eurostat_update = TRUE)` From 13576b3e1ab581c572fb4b06588f3b0da87efb3b Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Mon, 2 Feb 2026 15:09:45 +0100 Subject: [PATCH 45/62] Add 2024 as a valid NUTS release year to Rd --- man/get_eurostat_geospatial.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/get_eurostat_geospatial.Rd b/man/get_eurostat_geospatial.Rd index 8634fa4c..238e5b4c 100755 --- a/man/get_eurostat_geospatial.Rd +++ b/man/get_eurostat_geospatial.Rd @@ -42,7 +42,7 @@ been soft-deprecated, the function would switch to \code{sf}.} "0", "1", "2", "3" or "all" (mimics the original behaviour)} \item{year}{NUTS release year. One of -"2003", "2006", "2010", "2013", "2016" or "2021"} +"2003", "2006", "2010", "2013", "2016", "2021" or "2024"} \item{cache}{a logical whether to do caching. Default is \code{TRUE}.} From 91dcb83941012e512ee136478a8d191109c60096 Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Wed, 18 Feb 2026 11:17:24 +0100 Subject: [PATCH 46/62] Harmonise params with giscoR --- R/get_eurostat_geospatial.R | 63 ++++++++++++++----------------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/R/get_eurostat_geospatial.R b/R/get_eurostat_geospatial.R index b459ba74..eaf07e0a 100755 --- a/R/get_eurostat_geospatial.R +++ b/R/get_eurostat_geospatial.R @@ -9,22 +9,13 @@ #' @param output_class Class of object returned, #' either `sf` `simple features` or `df` (`data_frame`). `spdf` output has #' been soft-deprecated, the function would switch to `sf`. +#' @inheritParams giscoR::gisco_get_nuts #' @param resolution Resolution of the geospatial data. One of #' * "60" (1:60million), #' * "20" (1:20million) #' * "10" (1:10million) #' * "03" (1:3million) or #' * "01" (1:1million). -#' @param nuts_level Level of NUTS classification of the geospatial data. One of -#' "0", "1", "2", "3" or "all" (mimics the original behaviour) -#' @param year NUTS release year. One of -#' "2003", "2006", "2010", "2013", "2016", "2021" or "2024" -#' @param cache a logical whether to do caching. Default is `TRUE`. -#' @param update_cache a logical whether to update cache. Can be set also with -#' `options(eurostat_update = TRUE)` -#' @param cache_dir a path to a cache directory. See -#' [set_eurostat_cache_dir()]. If `NULL` and the cache dir has not been set -#' globally the file would be stored in the [tempdir()]. #' @param crs projection of the map: 4-digit #' [EPSG code](https://spatialreference.org/ref/epsg/). One of: #' * "4326" - WGS84 @@ -33,8 +24,6 @@ #' #' @param make_valid Deprecated #' -#' @inheritDotParams giscoR::gisco_get_nuts -epsg -#' #' @details #' The objects downloaded from GISCO should contain all or some of the #' following variable columns: @@ -118,9 +107,7 @@ #' @export get_eurostat_geospatial <- function(output_class = "sf", resolution = "60", - nuts_level = "all", year = "2016", - cache = TRUE, update_cache = FALSE, - cache_dir = NULL, crs = "4326", + crs = "4326", make_valid = "DEPRECATED", ...) { # nocov start if (!requireNamespace("sf")) { @@ -144,41 +131,43 @@ get_eurostat_geospatial <- function(output_class = "sf", # Leaving only specific validations - rest of call would be handled by giscoR output_class <- match.arg(as.character(output_class), c("sf", "df")) + # ovrewrite default parameters if given + args <- modifyList(formals(giscoR::gisco_get_nuts), list(...)) + args$resolution <- resolution + args$epsg <- crs + # Sanity check for nuts_level - stopifnot(length(nuts_level) == 1L) - nuts_level <- regmatches(nuts_level, regexpr("^(all|[0-9]+)", nuts_level)) - nuts_level <- match.arg(nuts_level, c("all", 0:3)) + stopifnot(length(args$nuts_level) == 1L) + args$nuts_level <- regmatches(args$nuts_level, regexpr("^(all|[0-9]+)", args$nuts_level)) + args$nuts_level <- match.arg(args$nuts_level, c("all", 0:3)) # Performance - If df requested resolution and crs are meaningless. Switching # to 60 and 4326 for speed (except for 2003, no available) if (output_class == "df") { - resolution <- "60" - crs <- "4326" + args$resolution <- "60" + args$epsg <- "4326" - if (as.integer(year) == 2003) resolution <- "20" + if (as.integer(args$year) == 2003) args$resolution <- "20" } - # If cache file requested get the info from the internal dataset - capture_dots <- list(...) - # Check if the pre-set call to the function has been modified on # relevant parameters use_local <- all( - as.character(resolution) == "60", - as.character(year) == "2016", - isFALSE(update_cache), + as.character(args$resolution) == "60", + as.character(args$year) == "2016", + isFALSE(args$update_cache), as.character(crs) == "4326", # Check dots are empty - length(capture_dots) == 0 + length(args) == 0 ) if (use_local) { # Not modified - using dataset included with eurostat package message("Extracting data from eurostat::eurostat_geodata_60_2016") shp <- eurostat::eurostat_geodata_60_2016 - if (nuts_level != "all") { - shp <- shp[shp$LEVL_CODE == nuts_level, ] + if (args$nuts_level != "all") { + shp <- shp[shp$LEVL_CODE == args$nuts_level, ] } } else { # Check if package "giscoR" is installed @@ -197,9 +186,9 @@ get_eurostat_geospatial <- function(output_class = "sf", # Manage cache: Priority is eurostat cache (if set) # If not make use of giscoR default options detect_eurostat_cache <- eur_helper_detect_cache_dir() - if (!is.null(cache_dir)) { + if (!is.null(args$cache_dir)) { # Already set by the user, no need message - cache_dir <- eur_helper_cachedir(cache_dir) + args$cache_dir <- eur_helper_cachedir(args$cache_dir) } else if (identical( detect_eurostat_cache, file.path(tempdir(), "eurostat") @@ -207,18 +196,12 @@ get_eurostat_geospatial <- function(output_class = "sf", # eurostat not set, using default giscoR cache management message("Cache management as per giscoR. see 'giscoR::gisco_get_nuts()'") } else { - cache_dir <- eur_helper_cachedir(cache_dir) + args$cache_dir <- eur_helper_cachedir(args$cache_dir) } # giscoR call with parameters # on input errors giscoR would show warnings, etc - shp <- giscoR::gisco_get_nuts( - resolution = resolution, - nuts_level = nuts_level, year = year, - cache = cache, update_cache = update_cache, - cache_dir = cache_dir, epsg = crs, - ... - ) + shp <- do.call(giscoR::gisco_get_nuts, args) } # Just to capture potential NULL outputs from giscoR - this can happen From 73061de9c65cd55bd457fd5c349ee2dd944f4cae Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Fri, 20 Feb 2026 09:40:23 +0100 Subject: [PATCH 47/62] Keep default nuts_level --- R/get_eurostat_geospatial.R | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/R/get_eurostat_geospatial.R b/R/get_eurostat_geospatial.R index eaf07e0a..ba422022 100755 --- a/R/get_eurostat_geospatial.R +++ b/R/get_eurostat_geospatial.R @@ -16,6 +16,8 @@ #' * "10" (1:10million) #' * "03" (1:3million) or #' * "01" (1:1million). +#' @param nuts_level Level of NUTS classification of the geospatial data. One of +#' "0", "1", "2", "3" or "all" (mimics the original behaviour) #' @param crs projection of the map: 4-digit #' [EPSG code](https://spatialreference.org/ref/epsg/). One of: #' * "4326" - WGS84 @@ -107,6 +109,7 @@ #' @export get_eurostat_geospatial <- function(output_class = "sf", resolution = "60", + nuts_level = "all", crs = "4326", make_valid = "DEPRECATED", ...) { # nocov start @@ -134,6 +137,7 @@ get_eurostat_geospatial <- function(output_class = "sf", # ovrewrite default parameters if given args <- modifyList(formals(giscoR::gisco_get_nuts), list(...)) args$resolution <- resolution + args$nuts_level <- nuts_level args$epsg <- crs # Sanity check for nuts_level From abf9ebbf32d3e56eb458e938759f52ef1e84bdd5 Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Fri, 20 Feb 2026 10:03:27 +0100 Subject: [PATCH 48/62] Fix class of args --- R/get_eurostat_geospatial.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/get_eurostat_geospatial.R b/R/get_eurostat_geospatial.R index ba422022..108a1298 100755 --- a/R/get_eurostat_geospatial.R +++ b/R/get_eurostat_geospatial.R @@ -135,7 +135,8 @@ get_eurostat_geospatial <- function(output_class = "sf", output_class <- match.arg(as.character(output_class), c("sf", "df")) # ovrewrite default parameters if given - args <- modifyList(formals(giscoR::gisco_get_nuts), list(...)) + default_args <- as.list(formals(giscoR::gisco_get_nuts)) + args <- modifyList(default_args, list(...)) args$resolution <- resolution args$nuts_level <- nuts_level args$epsg <- crs From 6c7c7a31a62ef234ffea0c560d194949819cf53f Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Fri, 20 Feb 2026 11:25:21 +0100 Subject: [PATCH 49/62] Add geodata for 2024 --- R/get_eurostat_geospatial.R | 9 ++++--- data-raw/eurostat_geodata_60_2024.R | 35 ++++++++++++++++++++++++++++ data/eurostat_geodata_60_2024.rda | Bin 0 -> 138484 bytes 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 data-raw/eurostat_geodata_60_2024.R create mode 100644 data/eurostat_geodata_60_2024.rda diff --git a/R/get_eurostat_geospatial.R b/R/get_eurostat_geospatial.R index 108a1298..0cfab06e 100755 --- a/R/get_eurostat_geospatial.R +++ b/R/get_eurostat_geospatial.R @@ -160,17 +160,16 @@ get_eurostat_geospatial <- function(output_class = "sf", # relevant parameters use_local <- all( as.character(args$resolution) == "60", - as.character(args$year) == "2016", + as.character(args$year) == "2024", isFALSE(args$update_cache), as.character(crs) == "4326", - # Check dots are empty - length(args) == 0 + length(list(...)) == 0 ) if (use_local) { # Not modified - using dataset included with eurostat package - message("Extracting data from eurostat::eurostat_geodata_60_2016") - shp <- eurostat::eurostat_geodata_60_2016 + message("Extracting data from eurostat::eurostat_geodata_60_2024") + shp <- eurostat::eurostat_geodata_60_2024 if (args$nuts_level != "all") { shp <- shp[shp$LEVL_CODE == args$nuts_level, ] } diff --git a/data-raw/eurostat_geodata_60_2024.R b/data-raw/eurostat_geodata_60_2024.R new file mode 100644 index 00000000..25f8745f --- /dev/null +++ b/data-raw/eurostat_geodata_60_2024.R @@ -0,0 +1,35 @@ +## code to prepare `eurostat_geodata_60_2016` dataset goes here +library(giscoR) +library(tidyverse) + +# Current names internal) +from_gisco <- gisco_get_nuts( + year = 2024, resolution = 60, + epsg = 4326, update_cache = TRUE, + verbose = TRUE +) + +from_gisco$geo <- from_gisco$NUTS_ID +from_gisco$id <- from_gisco$NUTS_ID + +# End + +eurostat_geodata_60_2024 <- from_gisco +unique(sf::st_is_valid(from_gisco)) + +# Sort by level and alphabetically +eurostat_geodata_60_2024 <- eurostat_geodata_60_2024 %>% + arrange(LEVL_CODE, NUTS_ID) + +# Arrange names in proper order +sfcol <- attr(eurostat_geodata_60_2024, "sf_column") +rest <- c( + "id", "LEVL_CODE", "NUTS_ID", "CNTR_CODE", "NAME_LATN", + "NUTS_NAME", "MOUNT_TYPE", "URBN_TYPE", "COAST_TYPE", + "FID", "geo" +) + +reorder <- intersect(unique(c(rest, sfcol)), names(eurostat_geodata_60_2024)) +eurostat_geodata_60_2024 <- eurostat_geodata_60_2024[, reorder] + +usethis::use_data(eurostat_geodata_60_2024, overwrite = TRUE, compress = "xz") diff --git a/data/eurostat_geodata_60_2024.rda b/data/eurostat_geodata_60_2024.rda new file mode 100644 index 0000000000000000000000000000000000000000..a3f4cac528ca4d0b78523c3a4a6f262746a37b8c GIT binary patch literal 138484 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

3GlL{^VdPEc`6~&}_-T9|dOn(? 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

8Wd7)m6@v;DSfNFnS=iKq@;40S1#Myg>(gl;}Mc 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 G0a;oz@!oR) literal 0 HcmV?d00001 From 8db796b11ddd5b251dc58aaa1de3e2fdd8b02abc Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Fri, 20 Feb 2026 11:54:25 +0100 Subject: [PATCH 50/62] Add docs for eurostat_geodata_60_2024 --- man/eurostat_geodata_60_2024.Rd | 149 ++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100755 man/eurostat_geodata_60_2024.Rd diff --git a/man/eurostat_geodata_60_2024.Rd b/man/eurostat_geodata_60_2024.Rd new file mode 100755 index 00000000..6255bbf3 --- /dev/null +++ b/man/eurostat_geodata_60_2024.Rd @@ -0,0 +1,149 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data_spatial.R +\docType{data} +\name{eurostat_geodata_60_2024} +\alias{eurostat_geodata_60_2024} +\title{Geospatial data of Europe from GISCO in 1:60 million scale from +year 2024} +\format{ +sf object +} +\source{ +Data source: Eurostat via \code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}}. + +© EuroGeographics for the administrative boundaries + +Data downloaded from: +\url{https://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units} +} +\description{ +Geospatial data of Europe from GISCO in 1:60 million scale +from year 2024 +} +\details{ +The dataset contains 2024 observations (rows) and 12 variables (columns). + +The object contains the following columns: +\itemize{ +\item \strong{id}: JSON id code, the same as \strong{NUTS_ID}. See \strong{NUTS_ID} below for +further clarification. +\item \strong{LEVL_CODE}: NUTS level code: 0 (national level), 1 (major +socio-economic regions), 2 (basic regions for the application of regional +policies) or 3 (small regions). +\item \strong{NUTS_ID}: NUTS ID code, consisting of country code and numbers (1 for +NUTS 1, 2 for NUTS 2 and 3 for NUTS 3) +\item \strong{CNTR_CODE}: Country code: two-letter ISO code (ISO 3166 alpha-2), except +in the case of Greece (EL). +\item \strong{NAME_LATN}: NUTS name in local language, transliterated to Latin script +\item \strong{NUTS_NAME}: NUTS name in local language, in local script. +\item \strong{MOUNT_TYPE}: Mountain typology for NUTS 3 regions. +\itemize{ +\item 1: "where more than 50 \% of the surface is covered by topographic +mountain areas" +\item 2: "in which more than 50 \% of the regional population lives in +topographic mountain areas" +\item 3: "where more than 50 \% of the surface is covered by topographic +mountain areas and where more than 50 \% of the regional population lives +in these mountain areas" +\item 4: non-mountain region / other region +\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 and +non-EU countries) +} +\item \strong{URBN_TYPE}: Urban-rural typology for NUTS 3 regions. +\itemize{ +\item 1: predominantly urban region +\item 2: intermediate region +\item 3: predominantly rural region +\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 +regions) +} +\item \strong{COAST_TYPE}: Coastal typology for NUTS 3 regions. +\itemize{ +\item 1: coastal (on coast) +\item 2: coastal (>= 50\% of population living within 50km of the coastline) +\item 3: non-coastal region +\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 +regions) +} +\item \strong{FID}: Same as NUTS_ID. +\item \strong{geo}: Same as NUTS_ID, added for for easier joins with dplyr. However, +it is recommended to use other identical fields for this purpose. +\item \strong{geometry}: geospatial information. +} + +Dataset updated: 2023-06-29. For a more recent version, please use +\code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}} function. +} +\examples{ + +eurostat_geodata_60_2024 <- eurostat::eurostat_geodata_60_2024 + +# Manipulate and plot +if (require(sf)) { + library(sf) + # Filter NUTS3 from select countries like in a regular data frame + example_nuts <- subset(eurostat_geodata_60_2024, LEVL_CODE == 3 & + CNTR_CODE \%in\% c("DK", "DE", "PL")) + + plot(example_nuts["CNTR_CODE"]) +} + +} +\references{ +The following copyright notice is provided for end user convenience. +Please check up-to-date copyright information from the eurostat website: +\href{https://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units}{GISCO: Geographical information and maps - Administrative units/statistical units} + +"In addition to the +\href{https://ec.europa.eu/eurostat/web/main/about/policies/copyright}{general copyright and licence policy} +applicable to the whole Eurostat website, the following +specific provisions apply to the datasets you are downloading. The download +and usage of these data is subject to the acceptance of the following +clauses: +\enumerate{ +\item The Commission agrees to grant the non-exclusive and not transferable +right to use and process the Eurostat/GISCO geographical data downloaded +from this page (the "data"). +\item The permission to use the data is granted on condition that: +\enumerate{ +\item the data will not be used for commercial purposes; +\item the source will be acknowledged. A copyright notice, as specified +below, will have to be visible on any printed or electronic publication +using the data downloaded from this page. +} +} +\subsection{Copyright notice}{ + +When data downloaded from this page is used in any printed or electronic +publication, in addition to any other provisions applicable to the whole +Eurostat website, data source will have to be acknowledged in the legend of +the map and in the introductory page of the publication with the following +copyright notice: + +EN: © EuroGeographics for the administrative boundaries + +FR: © EuroGeographics pour les limites administratives + +DE: © EuroGeographics bezüglich der Verwaltungsgrenzen + +For publications in languages other than English, French or German, the +translation of the copyright notice in the language of the publication shall +be used. + +If you intend to use the data commercially, please contact EuroGeographics +for information regarding their licence agreements." +} +} +\seealso{ +\code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}} and +\href{https://ec.europa.eu/eurostat/web/products-manuals-and-guidelines/-/ks-gq-18-008}{Eurostat. (2019). Methodological manual on territorial typologies -- 2018 edition. Manuals and guidelines.} + +Other datasets: +\code{\link{eu_countries}}, +\code{\link{tgs00026}} + +Other geospatial: +\code{\link{get_eurostat_geospatial}()} +} +\concept{datasets} +\concept{geospatial} From cf7edcac755b06663bd55bf35de8a98c85d087ea Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Fri, 20 Feb 2026 13:23:51 +0100 Subject: [PATCH 51/62] Update missing dataset code --- R/get_eurostat.R | 2 +- man/get_eurostat.Rd | 2 +- tests/testthat/test_04_get.R | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/get_eurostat.R b/R/get_eurostat.R index 013fea38..4b5658f2 100755 --- a/R/get_eurostat.R +++ b/R/get_eurostat.R @@ -138,7 +138,7 @@ #' set_eurostat_cache_dir(file.path(tempdir(), "r_cache2")) #' k <- get_eurostat("nama_10_lp_ulc") #' k <- get_eurostat("nama_10_lp_ulc", cache = FALSE) -#' k <- get_eurostat("avia_gonc", select_time = "Y", cache = FALSE) +#' k <- get_eurostat("avia_gooc", select_time = "Y", cache = FALSE) #' #' dd <- get_eurostat("nama_10_gdp", #' filters = list( diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index 1e0ee2a8..f054e185 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -335,7 +335,7 @@ options(eurostat_update = FALSE) set_eurostat_cache_dir(file.path(tempdir(), "r_cache2")) k <- get_eurostat("nama_10_lp_ulc") k <- get_eurostat("nama_10_lp_ulc", cache = FALSE) -k <- get_eurostat("avia_gonc", select_time = "Y", cache = FALSE) +k <- get_eurostat("avia_gooc", select_time = "Y", cache = FALSE) dd <- get_eurostat("nama_10_gdp", filters = list( diff --git a/tests/testthat/test_04_get.R b/tests/testthat/test_04_get.R index dc525b7d..679e324c 100755 --- a/tests/testthat/test_04_get.R +++ b/tests/testthat/test_04_get.R @@ -21,14 +21,14 @@ test_that("get_eurostat (dissemination API) includes TIME_PERIOD and value", { test_that("get_eurostat (dissemination API) produces a message with multiple select_time", { skip_on_cran() skip_if_offline() - expect_message(get_eurostat(id = "avia_gonc", + expect_message(get_eurostat(id = "avia_gooc", select_time = c("A", "M", "Q"))) }) test_that("get_eurostat (dissemination API) produces an error with imaginary select_time parameters", { skip_on_cran() skip_if_offline() - expect_error(get_eurostat(id = "avia_gonc", + expect_error(get_eurostat(id = "avia_gooc", select_time = c("X", "Y", "Z"))) # sleep for a while to let the API rest Sys.sleep(5) @@ -37,11 +37,11 @@ test_that("get_eurostat (dissemination API) produces an error with imaginary sel test_that("get_eurostat (dissemination API) works correctly with multi-frequency", { skip_on_cran() skip_if_offline() - expect_message(get_eurostat("avia_gonc", + expect_message(get_eurostat("avia_gooc", cache = FALSE, time_format = "date_last") ) - expect_match(as.character(unique(get_eurostat("avia_gonc", + expect_match(as.character(unique(get_eurostat("avia_gooc", select_time = NULL, cache = FALSE)$TIME_PERIOD)), "-01-01") From f0cccef8ece7094fe0d8aa6a0144ed245d4f18b1 Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Fri, 20 Feb 2026 13:23:51 +0100 Subject: [PATCH 52/62] Update missing dataset code --- tests/testthat/test_04_get.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/testthat/test_04_get.R b/tests/testthat/test_04_get.R index c82a2cbc..679e324c 100755 --- a/tests/testthat/test_04_get.R +++ b/tests/testthat/test_04_get.R @@ -22,8 +22,7 @@ test_that("get_eurostat (dissemination API) produces a message with multiple sel skip_on_cran() skip_if_offline() expect_message(get_eurostat(id = "avia_gooc", - select_time = c("A", "M", "Q"), - filters = list(TIME_PERIOD = 2001))) + select_time = c("A", "M", "Q"))) }) test_that("get_eurostat (dissemination API) produces an error with imaginary select_time parameters", { From a5835333f11d2073a672b7a2acc44495b4a09e1c Mon Sep 17 00:00:00 2001 From: Daniel Loos Date: Wed, 25 Feb 2026 10:33:46 +0100 Subject: [PATCH 53/62] Add new contributor --- DESCRIPTION | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DESCRIPTION b/DESCRIPTION index 362777a6..00796dd6 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -10,6 +10,8 @@ Authors@R: c( person("Markus", "Kainu", role = "aut"), person("Przemyslaw", "Biecek", role = "aut"), person("Daniel", "Antal", role = "ctb"), + person("Daniel", "Loos", role = "ctb", + comment = c(ORCID = "0000-0002-4024-4443")), person("Diego", "Hernangomez", role = "ctb", comment = c(ORCID = "0000-0001-8457-4658")), person("Joona", "Lehtomaki", role = "ctb"), From c24c3a67c4007e2aa1f394b43cdcd9d68323a022 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Wed, 29 Apr 2026 10:54:13 +0300 Subject: [PATCH 54/62] Replace dataset ds-059327 with another COMEXT dataset "The dataset "ds-059327" has been discontinued since 05/03/2026." (http://data.europa.eu/88u/dataset/1o8g23hraiu3f4kz6vpnqw) --- tests/testthat/test_10_sdmx.R | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/testthat/test_10_sdmx.R b/tests/testthat/test_10_sdmx.R index 59f2748a..0b9ca13c 100644 --- a/tests/testthat/test_10_sdmx.R +++ b/tests/testthat/test_10_sdmx.R @@ -1,28 +1,32 @@ test_that("get_eurostat_sdmx works", { skip_on_cran() skip_if_offline() - - prodcom <- get_eurostat_sdmx(id = "ds-059327", agency = "eurostat_comext", - filters = + + # Dimension order for DS-059328: + # Reporter.partner.product.flow.period.indicators + # although it seems that omitting period is no problem? + + prodcom <- get_eurostat_sdmx(id = "DS-059328", agency = "eurostat_comext", + filters = list( - FREQ = c("A"), - REPORTER = "FR", - PARTNER = "US", - PRODUCT = c("291"), - FLOW = "2", + FREQ = c("A"), # Annual + REPORTER = "FR", # France + PARTNER = "US", # United States + PRODUCT = c("122"), # 122-Food and beverages / Processed / Mainly for household consumption + FLOW = "2", # 1-IMPORT, 2-EXPORT INDICATORS = "VALUE_EUR"), verbose = FALSE) - + expect_equal(unique(prodcom$freq), "A") - - prodcom2 <- get_eurostat_sdmx(id = "ds-059327", agency = "eurostat_comext", - filters = + + prodcom2 <- get_eurostat_sdmx(id = "ds-059328", agency = "eurostat_comext", + filters = list( FREQ = c("M"), REPORTER = "FR", PARTNER = "US", - PRODUCT = c("291"), - FLOW = "2", + PRODUCT = c("310"), # 310-Fuels and lubricants / Primary + FLOW = "1", INDICATORS = "VALUE_EUR"), verbose = FALSE, use.data.table = TRUE) - + expect_equal(unique(prodcom2$freq), "M") }) From 1bdfea4ac4157a6d3719b3b3987c295b6a876b69 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Wed, 29 Apr 2026 10:54:36 +0300 Subject: [PATCH 55/62] Replace dataset label with the label currently used in the database --- tests/testthat/test_06_label.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test_06_label.R b/tests/testthat/test_06_label.R index dead5bcd..9619f137 100755 --- a/tests/testthat/test_06_label.R +++ b/tests/testthat/test_06_label.R @@ -2,7 +2,7 @@ test_that("Variable names are labeled", { skip_on_cran() skip_if_offline() expect_equal(label_eurostat_vars(id = "nama_10_lp_ulc", x = "geo", lang = "en"), "Geopolitical entity (reporting)") - expect_equal(label_eurostat_tables("nama_10_lp_ulc"), "Labour productivity and unit labour costs") + expect_equal(label_eurostat_tables("nama_10_lp_ulc"), "Labour productivity and unit labour costs - annual data") expect_true(any(grepl( "_code", @@ -38,7 +38,7 @@ test_that("Countrycodes are labelled for factors", { )), c("Finland", "Germany", "EU28") ) - + # sleep for a while to let the API rest Sys.sleep(5) }) @@ -84,4 +84,4 @@ test_that("wrong code (for this dataset) produces an error", { get_eurostat("road_eqr_trams"), code = "coef" ) ) -}) \ No newline at end of file +}) From e0df5e01ba439b7d55ba54edd29c1b5ebbd379b9 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Wed, 29 Apr 2026 10:54:49 +0300 Subject: [PATCH 56/62] Add test skips if giscoR is not installed --- .../test_03_get_eurostat_geospatial.R | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/testthat/test_03_get_eurostat_geospatial.R b/tests/testthat/test_03_get_eurostat_geospatial.R index 638e1c2a..d98e0282 100755 --- a/tests/testthat/test_03_get_eurostat_geospatial.R +++ b/tests/testthat/test_03_get_eurostat_geospatial.R @@ -54,6 +54,7 @@ test_that("get_eurostat_geospatial messages", { test_that("get_eurostat_geospatial nuts levels", { + skip_if_not_installed(pkg = "giscoR") skip_if_not_installed(pkg = "sf") # From internal data with default args expect_message(all <- get_eurostat_geospatial(nuts_level = "all", verbose = FALSE), "eurostat") @@ -113,6 +114,7 @@ test_that("get_eurostat_geospatial nuts levels", { }) test_that("get_eurostat_geospatial df", { + skip_if_not_installed(pkg = "giscoR") skip_if_not_installed(pkg = "sf") # From internal data with default args expect_message( @@ -274,6 +276,7 @@ test_that("giscoR returns NULL", { test_that("Check column names", { + skip_if_not_installed(pkg = "giscoR") skip_if_not_installed(pkg = "sf") # See https://github.com/rOpenGov/eurostat/issues/240 @@ -408,20 +411,20 @@ test_that("Check column names POLYGONS from GISCO", { expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) - + # Polygons 2024 poly <- get_eurostat_geospatial(nuts_level = 0, resolution = 60, year = 2024, verbose = FALSE) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) - + # df poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2024, verbose = FALSE ) - + expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) }) @@ -560,7 +563,7 @@ test_that("Check column names LABELS from GISCO", { expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) - + # Labels 2024 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2024, @@ -569,14 +572,14 @@ test_that("Check column names LABELS from GISCO", { ) expect_s3_class(poly, "sf") expect_identical(names(poly), col_order) - + # df poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, resolution = 60, year = 2024, spatialtype = "LB" ) - + expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) }) @@ -699,7 +702,7 @@ test_that("Check column names BORDERS from GISCO", { ) expect_s3_class(poly_df, "data.frame") - + # BORDERS 2024 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2024, @@ -707,7 +710,7 @@ test_that("Check column names BORDERS from GISCO", { verbose = FALSE ) expect_s3_class(poly, "sf") - + # df poly_df <- get_eurostat_geospatial( output_class = "df", nuts_level = 0, @@ -715,6 +718,6 @@ test_that("Check column names BORDERS from GISCO", { spatialtype = "BN", verbose = FALSE ) - + expect_s3_class(poly_df, "data.frame") }) From c308fe94a07f4dc8274626e97a120a9aa54b4ae4 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 4 May 2026 11:54:22 +0300 Subject: [PATCH 57/62] Update default geospatial dataset from 2016 to 2024, version bump, manuals --- DESCRIPTION | 6 +- NAMESPACE | 1 + R/data_spatial.R | 94 ++++++++---------- R/get_eurostat_geospatial.R | 159 ++++++++++++++---------------- R/get_eurostat_sdmx.R | 120 +++++++++++----------- data/eurostat_geodata_60_2016.rda | Bin 105292 -> 0 bytes man/eu_countries.Rd | 2 +- man/eurostat-package.Rd | 6 +- man/eurotime2date.Rd | 4 +- man/get_eurostat.Rd | 4 +- man/get_eurostat_dic.Rd | 4 +- man/get_eurostat_geospatial.Rd | 37 +++---- man/get_eurostat_json.Rd | 4 +- man/get_eurostat_raw.Rd | 4 +- man/get_eurostat_toc.Rd | 4 +- man/search_eurostat.Rd | 4 +- man/tgs00026.Rd | 2 +- man/tidy_eurostat.Rd | 4 +- man/tidy_eurostat_sdmx_csv.Rd | 4 +- 19 files changed, 215 insertions(+), 248 deletions(-) delete mode 100644 data/eurostat_geodata_60_2016.rda diff --git a/DESCRIPTION b/DESCRIPTION index 00796dd6..c3cdf70d 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: eurostat Title: Tools for Eurostat Open Data -Version: 4.1.0.9006 -Date: 2025-11-06 +Version: 4.1.0.9007 +Date: 2026-04-29 Authors@R: c( person("Leo", "Lahti", , "leo.lahti@iki.fi", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5537-637X")), @@ -57,7 +57,7 @@ Imports: xml2, data.table (>= 1.14.8) Suggests: - giscoR, + giscoR (>= 1.1.0), knitr, rmarkdown, sf, diff --git a/NAMESPACE b/NAMESPACE index 88246810..610ed62f 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -99,6 +99,7 @@ importFrom(utils,capture.output) importFrom(utils,download.file) importFrom(utils,hasName) importFrom(utils,menu) +importFrom(utils,modifyList) importFrom(utils,person) importFrom(utils,read.csv) importFrom(utils,read.delim) diff --git a/R/data_spatial.R b/R/data_spatial.R index a1aacf8a..bc415e4b 100755 --- a/R/data_spatial.R +++ b/R/data_spatial.R @@ -1,63 +1,49 @@ #' Geospatial data of Europe from GISCO in 1:60 million scale from -#' year 2016 +#' year 2024 #' #' @description Geospatial data of Europe from GISCO in 1:60 million scale -#' from year 2016 +#' from year 2024 #' #' @family datasets #' @family geospatial #' @format sf object #' @docType data -#' @name eurostat_geodata_60_2016 -#' -#' @details -#' The dataset contains 2016 observations (rows) and 12 variables (columns). -#' -#' The object contains the following columns: -#' * **id**: JSON id code, the same as **NUTS_ID**. See **NUTS_ID** below for -#' further clarification. -#' * **LEVL_CODE**: NUTS level code: 0 (national level), 1 (major -#' socio-economic regions), 2 (basic regions for the application of regional -#' policies) or 3 (small regions). -#' * **NUTS_ID**: NUTS ID code, consisting of country code and numbers (1 for -#' NUTS 1, 2 for NUTS 2 and 3 for NUTS 3) -#' * **CNTR_CODE**: Country code: two-letter ISO code (ISO 3166 alpha-2), except -#' in the case of Greece (EL). -#' * **NAME_LATN**: NUTS name in local language, transliterated to Latin script -#' * **NUTS_NAME**: NUTS name in local language, in local script. -#' * **MOUNT_TYPE**: Mountain typology for NUTS 3 regions. -#' * 1: "where more than 50 % of the surface is covered by topographic -#' mountain areas" -#' * 2: "in which more than 50 % of the regional population lives in -#' topographic mountain areas" -#' * 3: "where more than 50 % of the surface is covered by topographic -#' mountain areas and where more than 50 % of the regional population lives -#' in these mountain areas" -#' * 4: non-mountain region / other region -#' * 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 and -#' non-EU countries) -#' * **URBN_TYPE**: Urban-rural typology for NUTS 3 regions. -#' * 1: predominantly urban region -#' * 2: intermediate region -#' * 3: predominantly rural region -#' * 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 -#' regions) -#' * **COAST_TYPE**: Coastal typology for NUTS 3 regions. -#' * 1: coastal (on coast) -#' * 2: coastal (>= 50% of population living within 50km of the coastline) -#' * 3: non-coastal region -#' * 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 -#' regions) -#' * **FID**: Same as NUTS_ID. -#' * **geo**: Same as NUTS_ID, added for for easier joins with dplyr. However, -#' it is recommended to use other identical fields for this purpose. -#' * **geometry**: geospatial information. -#' -#' Dataset updated: 2023-06-29. For a more recent version, please use -#' [giscoR::gisco_get_nuts()] function. +#' @name eurostat_geodata_60_2024 +#' +#' @format The dataset contains 2016 observations (rows) and 12 variables (columns): +#' \describe{ +#' \item{id}{JSON id code, the same as NUTS_ID. See NUTS_ID below for further clarification.} +#' \item{LEVL_CODE}{NUTS level code: 0 (national level), 1 (major socio-economic regions), 2 (basic regions for the application of regional policies) or 3 (small regions).} +#' \item{NUTS_ID}{NUTS ID code, consisting of country code and numbers (1 for NUTS 1, 2 for NUTS 2 and 3 for NUTS 3)} +#' \item{CNTR_CODE}{Country code: two-letter ISO code (ISO 3166 alpha-2), except in the case of Greece (EL).} +#' \item{NAME_LATN}{NUTS name in local language, transliterated to Latin script} +#' \item{NUTS_NAME}{NUTS name in local language, in local script.} +#' \item{MOUNT_TYPE}{Mountain typology for NUTS 3 regions. +#' 1: "where more than 50 % of the surface is covered by topographic mountain areas" +#' 2: "in which more than 50 % of the regional population lives in topographic mountain areas" +#' 3: "where more than 50 % of the surface is covered by topographic mountain areas and where more than 50 % of the regional population lives in these mountain areas" +#' 4: non-mountain region / other region +#' 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 and non-EU countries)} +#' \item{URBN_TYPE}{Urban-rural typology for NUTS 3 regions. +#' 1: predominantly urban region +#' 2: intermediate region +#' 3: predominantly rural region +#' 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 regions)} +#' \item{COAST_TYPE}{Coastal typology for NUTS 3 regions. +#' 1: coastal (on coast) +#' 2: coastal (>= 50% of population living within 50km of the coastline) +#' 3: non-coastal region +#' 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 regions)} +#' \item{FID}{Same as NUTS_ID} +#' \item{geo}{Same as NUTS_ID} +#' \item{geometry}{geospatial information} +#' } +#' +#' Dataset updated: 2023-06-29. For a possibly more recent version, please use +#' \CRANpkg{giscoR} function `gisco_get_nuts()` function. #' #' @source -#' Data source: Eurostat via [giscoR::gisco_get_nuts()]. +#' Data source: Eurostat via giscoR package function `gisco_get_nuts()`. #' #' © EuroGeographics for the administrative boundaries #' @@ -107,22 +93,22 @@ #' If you intend to use the data commercially, please contact EuroGeographics #' for information regarding their licence agreements." #' -#' @seealso [giscoR::gisco_get_nuts()] and +#' @seealso \CRANpkg{giscoR} function `gisco_get_nuts()` and #' [Eurostat. (2019). Methodological manual on territorial typologies -- 2018 edition. Manuals and guidelines.](https://ec.europa.eu/eurostat/web/products-manuals-and-guidelines/-/ks-gq-18-008) #' #' #' @examples #' -#' eurostat_geodata_60_2016 <- eurostat::eurostat_geodata_60_2016 +#' eurostat_geodata_60_2024 <- eurostat::eurostat_geodata_60_2024 #' #' # Manipulate and plot #' if (require(sf)) { #' library(sf) #' # Filter NUTS3 from select countries like in a regular data frame -#' example_nuts <- subset(eurostat_geodata_60_2016, LEVL_CODE == 3 & +#' example_nuts <- subset(eurostat_geodata_60_2024, LEVL_CODE == 3 & #' CNTR_CODE %in% c("DK", "DE", "PL")) #' #' plot(example_nuts["CNTR_CODE"]) #' } #' -NULL +"eurostat_geodata_60_2024" diff --git a/R/get_eurostat_geospatial.R b/R/get_eurostat_geospatial.R index 7cb72194..267258d5 100755 --- a/R/get_eurostat_geospatial.R +++ b/R/get_eurostat_geospatial.R @@ -1,15 +1,15 @@ #' @title Download Geospatial Data from GISCO #' #' @description Downloads either a simple features (sf) or a data_frame -#' of NUTS regions. This function is a wrapper of [giscoR::gisco_get_nuts()]. -#' This function requires to have installed the packages \CRANpkg{sf} and -#' \CRANpkg{giscoR}. +#' of NUTS regions. This function is a wrapper of \CRANpkg{giscoR} function +#' `gisco_get_nuts()`. +#' This function requires to have packages \CRANpkg{sf} and +#' \CRANpkg{giscoR} installed. #' -#' @seealso [giscoR::gisco_get_nuts()] +#' @seealso giscoR package and its functions #' @param output_class Class of object returned, #' either `sf` `simple features` or `df` (`data_frame`). `spdf` output has -#' been soft-deprecated, the function would switch to `sf`. -#' @inheritParams giscoR::gisco_get_nuts +#' been soft-deprecated, the function will switch to `sf`. #' @param resolution Resolution of the geospatial data. One of #' * "60" (1:60million), #' * "20" (1:20million) @@ -25,6 +25,8 @@ #' * "4326" - WGS84 #' * "3035" - ETRS89 / ETRS-LAEA #' * "3857" - Pseudo-Mercator +#' @param ... additional arguments to be passed onto \CRANpkg{giscoR} function +#' `gisco_get_nuts()` #' #' @inheritParams get_eurostat #' @@ -96,6 +98,7 @@ #' resolution = "60", #' nuts_level = "all" #' ) +#' \dontrun{ #' # Downloads dataset from server #' sf2 <- get_eurostat_geospatial( #' output_class = "sf", @@ -107,123 +110,113 @@ #' nuts_level = "0" #' ) #' } +#' } +#' +#' @importFrom utils modifyList #' #' @export get_eurostat_geospatial <- function(output_class = "sf", resolution = "60", - nuts_level = "all", year = "2016", + nuts_level = "all", year = "2024", cache = TRUE, update_cache = FALSE, - cache_dir = NULL, crs = "4326", + cache_dir = NULL, crs = "4326", verbose = TRUE, ...) { - # nocov start - if (!requireNamespace("sf")) { + + # sf is always required + if (!requireNamespace("sf", quietly = TRUE)) { if (verbose) message("'sf' package is required for geospatial functionalities") return(invisible()) } - # nocov end - # Simplified and leaving most of the heavy-lifting to giscoR - # Deprecation messages + has_gisco <- requireNamespace("giscoR", quietly = TRUE) + + # Deprecation handling stopifnot(length(output_class) == 1L) if (output_class == "spdf") { if (verbose) message("'spdf' output deprecated. Switching to sf output") output_class <- "sf" } - # Leaving only specific validations - rest of call would be handled by giscoR output_class <- match.arg(as.character(output_class), c("sf", "df")) - # ovrewrite default parameters if given - default_args <- as.list(formals(giscoR::gisco_get_nuts)) - args <- modifyList(default_args, list(...)) - args$resolution <- resolution - args$nuts_level <- nuts_level - args$epsg <- crs - - # Sanity check for nuts_level - stopifnot(length(args$nuts_level) == 1L) - args$nuts_level <- regmatches(args$nuts_level, regexpr("^(all|[0-9]+)", args$nuts_level)) - args$nuts_level <- match.arg(args$nuts_level, c("all", 0:3)) - - # Performance - If df requested resolution and crs are meaningless. Switching - # to 60 and 4326 for speed (except for 2003, no available) - if (output_class == "df") { - args$resolution <- "60" - args$epsg <- "4326" - - if (as.integer(args$year) == 2003) args$resolution <- "20" - } - - - # Check if the pre-set call to the function has been modified on - # relevant parameters + # ---- Determine whether we should use local data ---- use_local <- all( - as.character(args$resolution) == "60", - as.character(args$year) == "2024", - isFALSE(args$update_cache), + as.character(resolution) == "60", + as.character(year) == "2024", + isFALSE(update_cache), as.character(crs) == "4326", length(list(...)) == 0 ) + # FORCE local if giscoR not available + if (!has_gisco) { + use_local <- TRUE + if (verbose) { + message("Package 'giscoR' not installed: using local dataset only") + } + } + + # ---- LOCAL BRANCH ---- if (use_local) { - # Not modified - using dataset included with eurostat package - message("Extracting data from eurostat::eurostat_geodata_60_2024") + if (verbose) { + message("Extracting data from eurostat::eurostat_geodata_60_2024") + } + shp <- eurostat::eurostat_geodata_60_2024 - if (args$nuts_level != "all") { - shp <- shp[shp$LEVL_CODE == args$nuts_level, ] + + if (nuts_level != "all") { + shp <- shp[shp$LEVL_CODE == nuts_level, ] } + } else { - # Check if package "giscoR" is installed - # nocov start - if (!requireNamespace("giscoR")) { - message("'giscoR' package is required for geospatial functionalities") - return(invisible()) + + # ---- REMOTE BRANCH (safe: giscoR exists here) ---- + + # defaults only accessed AFTER we know giscoR exists + default_args <- as.list(formals(giscoR::gisco_get_nuts)) + args <- utils::modifyList(default_args, list(...)) + + args$resolution <- resolution + args$nuts_level <- nuts_level + args$year <- year + args$epsg <- crs + args$cache <- cache + args$update_cache <- update_cache + args$cache_dir <- cache_dir + + # Sanity check + stopifnot(length(args$nuts_level) == 1L) + args$nuts_level <- regmatches(args$nuts_level, regexpr("^(all|[0-9]+)", args$nuts_level)) + args$nuts_level <- match.arg(args$nuts_level, c("all", 0:3)) + + # Performance tweak + if (output_class == "df") { + args$resolution <- "60" + args$epsg <- "4326" + if (as.integer(args$year) == 2003) args$resolution <- "20" } - # nocov end - - if (verbose) message(paste0( - "Extracting data using giscoR package, please report issues", - " on https://github.com/rOpenGov/giscoR/issues" - )) - - # Manage cache: Priority is eurostat cache (if set) - # If not make use of giscoR default options - detect_eurostat_cache <- eur_helper_detect_cache_dir() - if (!is.null(args$cache_dir)) { - # Already set by the user, no need message - args$cache_dir <- eur_helper_cachedir(args$cache_dir) - } else if (identical( - detect_eurostat_cache, - file.path(tempdir(), "eurostat") - )) { - # eurostat not set, using default giscoR cache management - if (verbose) message("Cache management as per giscoR. see 'giscoR::gisco_get_nuts()'") - } else { - args$cache_dir <- eur_helper_cachedir(args$cache_dir) + + if (verbose) { + message( + "Extracting data using giscoR package, please report issues ", + "on https://github.com/rOpenGov/giscoR/issues" + ) } - # giscoR call with parameters - # on input errors giscoR would show warnings, etc shp <- do.call(giscoR::gisco_get_nuts, args) } - # Just to capture potential NULL outputs from giscoR - this can happen - # on some errors - if (is.null(shp)) { - return(NULL) - } + # Handle NULL safely + if (is.null(shp)) return(NULL) - # Post-data treatments - # Manage col names + # Post-processing shp <- geo_names(shp) - # to df if (output_class == "df") { - # Remove geometry shp <- sf::st_drop_geometry(shp) } - return(shp) + shp } diff --git a/R/get_eurostat_sdmx.R b/R/get_eurostat_sdmx.R index 4b50ef35..fe0ea880 100644 --- a/R/get_eurostat_sdmx.R +++ b/R/get_eurostat_sdmx.R @@ -2,7 +2,7 @@ #' #' @description #' `r lifecycle::badge('experimental')` -#' +#' #' Download data sets from Eurostat using the same logic as `get_eurostat()` #' function. #' @@ -78,32 +78,32 @@ get_eurostat_sdmx <- function( if (use.data.table) { tfile <- tempfile() on.exit(unlink(tfile)) - - httr2::request(api_base_uri) %>% - httr2::req_url_path_append("sdmx", "2.1", resource, id, key) %>% - httr2::req_url_query(format = "TSV", compressed = compressed, detail = "dataonly") %>% + + httr2::request(api_base_uri) %>% + httr2::req_url_path_append("sdmx", "2.1", resource, id, key) %>% + httr2::req_url_query(format = "TSV", compressed = compressed, detail = "dataonly") %>% httr2::req_perform(path = tfile) - + dat <- readr::read_tsv(tfile, progress = verbose, show_col_types = verbose) dat2 <- data.table::fread(tfile, na.strings = c(":"), verbose = verbose) - # Columns containing NA's don't play well with data.table::melt in + # Columns containing NA's don't play well with data.table::melt in # tidy_eurostat # This turns logical columns (containing NA's) into integers (NA_integer_) offending_cols <- names(dat2[, .SD, .SDcols = anyNA]) dat2 <- dat2[ , (offending_cols) := lapply(.SD, as.integer), .SDcols = offending_cols] - + dat <- tidy_eurostat(dat2, use.data.table = use.data.table) - + return(dat) } else { - res <- httr2::request(api_base_uri) %>% - httr2::req_url_path_append("sdmx", "2.1", resource, id, key) %>% + res <- httr2::request(api_base_uri) %>% + httr2::req_url_path_append("sdmx", "2.1", resource, id, key) %>% httr2::req_url_query(format = "SDMX-CSV", compressed = compressed, detail = "dataonly") } tfile <- tempfile() on.exit(unlink(tfile)) - + httr2::req_perform(res, path = tfile) dat <- readr::read_csv(tfile, progress = verbose, show_col_types = verbose) @@ -134,74 +134,74 @@ get_eurostat_sdmx <- function( get_sdmx_codelist <- function(codelist_id, agency = "Eurostat", type = "list", lang = NULL) { lang <- check_lang(lang) - + api_base_uri <- build_api_base_uri(agency) - + xml_url <- paste0( api_base_uri, "/sdmx/2.1/codelist/estat/", codelist_id) - + xml_object <- xml2::read_xml(xml_url) - + if (identical(type, "raw")) { return(xml_object) - } - + } + namespaces <- xml2::xml_ns(xml_object) - + codelists <- xml2::xml_find_first(xml_object, ".//s:Codelists", namespaces) - + code_nodes <- xml2::xml_find_all(codelists, ".//s:Code", namespaces) - + id <- c() name <- c() - + for (node in code_nodes) { id <- c(id, xml2::xml_attr(node, "id")) name <- c(name, xml2::xml_text(xml2::xml_find_first(node, sprintf(".//c:Name[@xml:lang='%s']", lang)))) } - + df <- data.frame( id = id, name = name ) - + return(df) } get_sdmx_conceptscheme <- function(id, agency = "Eurostat", type = "list", lang = NULL) { - + lang <- check_lang(lang) - + api_base_uri <- build_api_base_uri(agency) - + xml_url <- paste0( api_base_uri, "/sdmx/2.1/conceptscheme/estat/", id) - + xml_object <- xml2::read_xml(xml_url) - + if (identical(type, "raw")) { return(xml_object) - } - + } + namespaces <- xml2::xml_ns(xml_object) - + # Continue with dimensions and concept extraction data_structure_components <- xml2::xml_find_first(xml_object, ".//s:Concepts", namespaces) - + concept_nodes <- xml2::xml_find_all(data_structure_components, ".//s:Concept", namespaces) - + # metadata <- xml2::as_list(concept_nodes) - + id <- c() urn <- c() name <- c() core_representation <- c() version <- c() - + for (node in concept_nodes) { id <- c(id, xml2::xml_attr(node, "id")) urn <- c(urn, xml2::xml_attr(node, "urn")) @@ -213,58 +213,58 @@ get_sdmx_conceptscheme <- function(id, agency = "Eurostat", type = "list", lang } version <- c(version, xml2::xml_attr(xml2::xml_find_first(node, ".//Ref"), "version")) } - + metadata_returnable <- data.frame( id = id, - urn = urn, + urn = urn, name = name, core_representation = core_representation, version = version ) - + return(metadata_returnable) } -#' @importFrom xml2 xml_ns xml_find_first xml_text xml_attr xml_find_all +#' @importFrom xml2 xml_ns xml_find_first xml_text xml_attr xml_find_all get_sdmx_dsd <- function(id, agency = "Eurostat", type = "list", lang = NULL) { api_base_uri <- build_api_base_uri(agency) - + dsd_url <- paste0( api_base_uri, "/sdmx/2.1/datastructure/estat/", id) - + xml_object <- xml2::read_xml(dsd_url) - + if (identical(type, "raw")) { return(xml_object) - } - + } + # Define namespaces namespaces <- xml2::xml_ns(xml_object) - + # Extract Header information header <- xml2::xml_find_first(xml_object, ".//m:Header", namespaces) header_id <- xml2::xml_text(xml2::xml_find_first(header, ".//m:ID", namespaces)) prepared <- substr(xml2::xml_text(xml2::xml_find_first(header, ".//m:Prepared", namespaces)),1,10) sender_id <- xml2::xml_attr(xml2::xml_find_first(header, ".//m:Sender", namespaces), "id") - - + + # Continue with dimensions and concept extraction data_structure_components <- xml2::xml_find_first(xml_object, ".//s:DataStructureComponents", namespaces) - + dimension_nodes <- xml2::xml_find_all(data_structure_components, ".//s:Dimension | .//s:TimeDimension | .//s:AttributeList/s:Attribute | .//s:MeasureList/s:PrimaryMeasure", namespaces) - + metadata_returnable <- list() - + metadata <- xml2::as_list(dimension_nodes) - + id <- c() urn <- c() concept_identity_class <- c() local_representation <- c() local_representation_version <- c() - + for (node in dimension_nodes) { id <- c(id, xml2::xml_attr(node, "id")) urn <- c(urn, xml2::xml_attr(node, "urn")) @@ -276,15 +276,15 @@ get_sdmx_dsd <- function(id, agency = "Eurostat", type = "list", lang = NULL) { } local_representation_version <- c(local_representation_version, xml2::xml_attr(xml2::xml_find_first(node, ".//s:Enumeration/Ref"), "version")) } - + metadata_returnable <- data.frame( id = id, - urn = urn, + urn = urn, concept_identity_class = concept_identity_class, local_representation = local_representation, local_representation_version = local_representation_version ) - + return(metadata_returnable) } @@ -296,7 +296,7 @@ get_sdmx_dataflow <- function(id, agency = "Eurostat", type = "list", lang = NUL api_base_uri, "/sdmx/2.1/dataflow/estat/", id) - + # With DSD # dataflow_url <- paste0( # api_base_uri, @@ -306,10 +306,10 @@ get_sdmx_dataflow <- function(id, agency = "Eurostat", type = "list", lang = NUL # "?references=children") xml_object <- xml2::read_xml(dataflow_url) - + if (identical(type, "raw")) { return(xml_object) - } + } # Define namespaces namespaces <- xml2::xml_ns(xml_object) @@ -366,7 +366,7 @@ get_sdmx_dataflow <- function(id, agency = "Eurostat", type = "list", lang = NUL ) } } - + if (grepl("adms:Identifier", title)) { title_xml <- xml2::read_xml(title) doi_url <- xml2::xml_attr(xml2::xml_find_first(title_xml, ".//adms:Identifier"), "rdf:about", xml2::xml_ns(title_xml)) diff --git a/data/eurostat_geodata_60_2016.rda b/data/eurostat_geodata_60_2016.rda deleted file mode 100644 index 0e4532690264b0dfd50977ce2ac0aed12e29fc09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105292 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%0A060VpH{ep)*cUS%4 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


g`on=<&=|GJvXOhhmL* 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 diff --git a/man/eu_countries.Rd b/man/eu_countries.Rd index 8ad9bb7a..86bef990 100755 --- a/man/eu_countries.Rd +++ b/man/eu_countries.Rd @@ -47,7 +47,7 @@ of Greece where EL is used instead of the standard ISO code. } \seealso{ Other datasets: -\code{\link{eurostat_geodata_60_2016}}, +\code{\link{eurostat_geodata_60_2024}}, \code{\link{tgs00026}} } \concept{datasets} diff --git a/man/eurostat-package.Rd b/man/eurostat-package.Rd index 32fba772..becb6a07 100755 --- a/man/eurostat-package.Rd +++ b/man/eurostat-package.Rd @@ -15,7 +15,7 @@ utilities. \tab \cr \strong{Package} \tab eurostat \cr \strong{Type} \tab Package \cr - \strong{Version} \tab 4.1.0.9006 \cr + \strong{Version} \tab 4.1.0.9007 \cr \strong{Date} \tab 2014-2025 \cr \strong{License} \tab BSD_2_clause + file LICENSE \cr \strong{LazyLoad} \tab yes \cr @@ -358,10 +358,10 @@ In addition, please provide a citation to the specific software version used: Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, - Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9006, . + 4.1.0.9007, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/eurotime2date.Rd b/man/eurotime2date.Rd index b91f0a4a..fddedf19 100755 --- a/man/eurotime2date.Rd +++ b/man/eurotime2date.Rd @@ -56,10 +56,10 @@ See \code{citation("eurostat")}: # used: # # Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, -# Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." +# Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9006, . +# 4.1.0.9007, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat.Rd b/man/get_eurostat.Rd index 63a93f34..b6887077 100755 --- a/man/get_eurostat.Rd +++ b/man/get_eurostat.Rd @@ -387,10 +387,10 @@ In addition, please provide a citation to the specific software version used: Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, - Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9006, . + 4.1.0.9007, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_dic.Rd b/man/get_eurostat_dic.Rd index bdb782a1..bc69f693 100755 --- a/man/get_eurostat_dic.Rd +++ b/man/get_eurostat_dic.Rd @@ -46,10 +46,10 @@ See \code{citation("eurostat")}: # used: # # Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, -# Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." +# Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9006, . +# 4.1.0.9007, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_geospatial.Rd b/man/get_eurostat_geospatial.Rd index a319de7a..a387405e 100755 --- a/man/get_eurostat_geospatial.Rd +++ b/man/get_eurostat_geospatial.Rd @@ -15,7 +15,7 @@ get_eurostat_geospatial( output_class = "sf", resolution = "60", nuts_level = "all", - year = "2016", + year = "2024", cache = TRUE, update_cache = FALSE, cache_dir = NULL, @@ -27,7 +27,7 @@ get_eurostat_geospatial( \arguments{ \item{output_class}{Class of object returned, either \code{sf} \verb{simple features} or \code{df} (\code{data_frame}). \code{spdf} output has -been soft-deprecated, the function would switch to \code{sf}.} +been soft-deprecated, the function will switch to \code{sf}.} \item{resolution}{Resolution of the geospatial data. One of \itemize{ @@ -65,33 +65,18 @@ directory by using this argument. The cache directory can also be set with \item{verbose}{Output messages when downloading data. Default is \code{TRUE}.} -\item{...}{ - Arguments passed on to \code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts}} - \describe{ - \item{\code{spatialtype}}{Type of geometry to be returned: -\itemize{ -\item \code{"BN"}: Boundaries - \code{LINESTRING} object. -\item \code{"LB"}: Labels - \code{POINT} object. -\item \code{"RG"}: Regions - \code{MULTIPOLYGON/POLYGON} object. -} - -\strong{Note that} parameters \code{country}, \code{nuts_level} and \code{nuts_id} would be only -applied when \code{spatialtype} is \code{"BN"} or \code{"RG"}.} - \item{\code{country}}{Optional. A character vector of country codes. It could be -either a vector of country names, a vector of ISO3 country codes or a -vector of Eurostat country codes. Mixed types (as \code{c("Italy","ES","FRA")}) -would not work. See also \code{\link[countrycode:countrycode]{countrycode::countrycode()}}.} - \item{\code{nuts_id}}{Optional. A character vector of NUTS IDs.} - }} +\item{...}{additional arguments to be passed onto \CRANpkg{giscoR} function +\code{gisco_get_nuts()}} } \value{ a sf or data_frame } \description{ Downloads either a simple features (sf) or a data_frame -of NUTS regions. This function is a wrapper of \code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}}. -This function requires to have installed the packages \CRANpkg{sf} and -\CRANpkg{giscoR}. +of NUTS regions. This function is a wrapper of \CRANpkg{giscoR} function +\code{gisco_get_nuts()}. +This function requires to have packages \CRANpkg{sf} and +\CRANpkg{giscoR} installed. } \details{ The objects downloaded from GISCO should contain all or some of the @@ -242,6 +227,7 @@ sf <- get_eurostat_geospatial( resolution = "60", nuts_level = "all" ) +\dontrun{ # Downloads dataset from server sf2 <- get_eurostat_geospatial( output_class = "sf", @@ -253,13 +239,14 @@ df <- get_eurostat_geospatial( nuts_level = "0" ) } +} } \seealso{ -\code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}} +giscoR package and its functions Other geospatial: -\code{\link{eurostat_geodata_60_2016}} +\code{\link{eurostat_geodata_60_2024}} } \author{ Markus Kainu \href{mailto:markuskainu@gmail.com}{markuskainu@gmail.com}, Diego Hernangomez diff --git a/man/get_eurostat_json.Rd b/man/get_eurostat_json.Rd index 4a2861e4..eab9ab93 100755 --- a/man/get_eurostat_json.Rd +++ b/man/get_eurostat_json.Rd @@ -319,10 +319,10 @@ In addition, please provide a citation to the specific software version used: Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, - Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9006, . + 4.1.0.9007, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_raw.Rd b/man/get_eurostat_raw.Rd index 4c634655..6f50b79a 100755 --- a/man/get_eurostat_raw.Rd +++ b/man/get_eurostat_raw.Rd @@ -126,10 +126,10 @@ See \code{citation("eurostat")}: # used: # # Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, -# Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." +# Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." # doi:10.32614/CRAN.package.eurostat # , R package version -# 4.1.0.9006, . +# 4.1.0.9007, . # # To see these entries in BibTeX format, use 'print(, # bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/get_eurostat_toc.Rd b/man/get_eurostat_toc.Rd index b1190a0b..d052534d 100755 --- a/man/get_eurostat_toc.Rd +++ b/man/get_eurostat_toc.Rd @@ -93,10 +93,10 @@ In addition, please provide a citation to the specific software version used: Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, - Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9006, . + 4.1.0.9007, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/search_eurostat.Rd b/man/search_eurostat.Rd index dfe039c8..e4270cdb 100755 --- a/man/search_eurostat.Rd +++ b/man/search_eurostat.Rd @@ -101,10 +101,10 @@ In addition, please provide a citation to the specific software version used: Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, - Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9006, . + 4.1.0.9007, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tgs00026.Rd b/man/tgs00026.Rd index 548e1dfc..8dfe183b 100755 --- a/man/tgs00026.Rd +++ b/man/tgs00026.Rd @@ -21,7 +21,7 @@ Data retrieval date: 2022-06-27 \seealso{ Other datasets: \code{\link{eu_countries}}, -\code{\link{eurostat_geodata_60_2016}} +\code{\link{eurostat_geodata_60_2024}} } \concept{datasets} \keyword{datasets} diff --git a/man/tidy_eurostat.Rd b/man/tidy_eurostat.Rd index 6fc09ef9..308c904b 100755 --- a/man/tidy_eurostat.Rd +++ b/man/tidy_eurostat.Rd @@ -81,10 +81,10 @@ In addition, please provide a citation to the specific software version used: Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, - Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9006, . + 4.1.0.9007, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set diff --git a/man/tidy_eurostat_sdmx_csv.Rd b/man/tidy_eurostat_sdmx_csv.Rd index 3f6a9561..3a99abc1 100644 --- a/man/tidy_eurostat_sdmx_csv.Rd +++ b/man/tidy_eurostat_sdmx_csv.Rd @@ -68,10 +68,10 @@ In addition, please provide a citation to the specific software version used: Lahti L, Huovari J, Kainu M, Biecek P, Hernangomez D, Antal D, - Kantanen P (2025). "eurostat: Tools for Eurostat Open Data." + Kantanen P (2026). "eurostat: Tools for Eurostat Open Data." doi:10.32614/CRAN.package.eurostat , R package version - 4.1.0.9006, . + 4.1.0.9007, . To see these entries in BibTeX format, use 'print(, bibtex=TRUE)', 'toBibtex(.)', or set From a8a99da253fec33b52bcc4e1ebe73bb2b0d24901 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 4 May 2026 11:54:41 +0300 Subject: [PATCH 58/62] Replace default dataset with 2024 (from 2016) --- man/eurostat_geodata_60_2016.Rd | 149 -------------------------------- man/eurostat_geodata_60_2024.Rd | 94 ++++++++------------ 2 files changed, 38 insertions(+), 205 deletions(-) delete mode 100755 man/eurostat_geodata_60_2016.Rd mode change 100755 => 100644 man/eurostat_geodata_60_2024.Rd diff --git a/man/eurostat_geodata_60_2016.Rd b/man/eurostat_geodata_60_2016.Rd deleted file mode 100755 index fcce0288..00000000 --- a/man/eurostat_geodata_60_2016.Rd +++ /dev/null @@ -1,149 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data_spatial.R -\docType{data} -\name{eurostat_geodata_60_2016} -\alias{eurostat_geodata_60_2016} -\title{Geospatial data of Europe from GISCO in 1:60 million scale from -year 2016} -\format{ -sf object -} -\source{ -Data source: Eurostat via \code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}}. - -© EuroGeographics for the administrative boundaries - -Data downloaded from: -\url{https://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units} -} -\description{ -Geospatial data of Europe from GISCO in 1:60 million scale -from year 2016 -} -\details{ -The dataset contains 2016 observations (rows) and 12 variables (columns). - -The object contains the following columns: -\itemize{ -\item \strong{id}: JSON id code, the same as \strong{NUTS_ID}. See \strong{NUTS_ID} below for -further clarification. -\item \strong{LEVL_CODE}: NUTS level code: 0 (national level), 1 (major -socio-economic regions), 2 (basic regions for the application of regional -policies) or 3 (small regions). -\item \strong{NUTS_ID}: NUTS ID code, consisting of country code and numbers (1 for -NUTS 1, 2 for NUTS 2 and 3 for NUTS 3) -\item \strong{CNTR_CODE}: Country code: two-letter ISO code (ISO 3166 alpha-2), except -in the case of Greece (EL). -\item \strong{NAME_LATN}: NUTS name in local language, transliterated to Latin script -\item \strong{NUTS_NAME}: NUTS name in local language, in local script. -\item \strong{MOUNT_TYPE}: Mountain typology for NUTS 3 regions. -\itemize{ -\item 1: "where more than 50 \% of the surface is covered by topographic -mountain areas" -\item 2: "in which more than 50 \% of the regional population lives in -topographic mountain areas" -\item 3: "where more than 50 \% of the surface is covered by topographic -mountain areas and where more than 50 \% of the regional population lives -in these mountain areas" -\item 4: non-mountain region / other region -\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 and -non-EU countries) -} -\item \strong{URBN_TYPE}: Urban-rural typology for NUTS 3 regions. -\itemize{ -\item 1: predominantly urban region -\item 2: intermediate region -\item 3: predominantly rural region -\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 -regions) -} -\item \strong{COAST_TYPE}: Coastal typology for NUTS 3 regions. -\itemize{ -\item 1: coastal (on coast) -\item 2: coastal (>= 50\% of population living within 50km of the coastline) -\item 3: non-coastal region -\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 -regions) -} -\item \strong{FID}: Same as NUTS_ID. -\item \strong{geo}: Same as NUTS_ID, added for for easier joins with dplyr. However, -it is recommended to use other identical fields for this purpose. -\item \strong{geometry}: geospatial information. -} - -Dataset updated: 2023-06-29. For a more recent version, please use -\code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}} function. -} -\examples{ - -eurostat_geodata_60_2016 <- eurostat::eurostat_geodata_60_2016 - -# Manipulate and plot -if (require(sf)) { - library(sf) - # Filter NUTS3 from select countries like in a regular data frame - example_nuts <- subset(eurostat_geodata_60_2016, LEVL_CODE == 3 & - CNTR_CODE \%in\% c("DK", "DE", "PL")) - - plot(example_nuts["CNTR_CODE"]) -} - -} -\references{ -The following copyright notice is provided for end user convenience. -Please check up-to-date copyright information from the eurostat website: -\href{https://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units}{GISCO: Geographical information and maps - Administrative units/statistical units} - -"In addition to the -\href{https://ec.europa.eu/eurostat/web/main/about/policies/copyright}{general copyright and licence policy} -applicable to the whole Eurostat website, the following -specific provisions apply to the datasets you are downloading. The download -and usage of these data is subject to the acceptance of the following -clauses: -\enumerate{ -\item The Commission agrees to grant the non-exclusive and not transferable -right to use and process the Eurostat/GISCO geographical data downloaded -from this page (the "data"). -\item The permission to use the data is granted on condition that: -\enumerate{ -\item the data will not be used for commercial purposes; -\item the source will be acknowledged. A copyright notice, as specified -below, will have to be visible on any printed or electronic publication -using the data downloaded from this page. -} -} -\subsection{Copyright notice}{ - -When data downloaded from this page is used in any printed or electronic -publication, in addition to any other provisions applicable to the whole -Eurostat website, data source will have to be acknowledged in the legend of -the map and in the introductory page of the publication with the following -copyright notice: - -EN: © EuroGeographics for the administrative boundaries - -FR: © EuroGeographics pour les limites administratives - -DE: © EuroGeographics bezüglich der Verwaltungsgrenzen - -For publications in languages other than English, French or German, the -translation of the copyright notice in the language of the publication shall -be used. - -If you intend to use the data commercially, please contact EuroGeographics -for information regarding their licence agreements." -} -} -\seealso{ -\code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}} and -\href{https://ec.europa.eu/eurostat/web/products-manuals-and-guidelines/-/ks-gq-18-008}{Eurostat. (2019). Methodological manual on territorial typologies -- 2018 edition. Manuals and guidelines.} - -Other datasets: -\code{\link{eu_countries}}, -\code{\link{tgs00026}} - -Other geospatial: -\code{\link{get_eurostat_geospatial}()} -} -\concept{datasets} -\concept{geospatial} diff --git a/man/eurostat_geodata_60_2024.Rd b/man/eurostat_geodata_60_2024.Rd old mode 100755 new mode 100644 index 6255bbf3..ab01a402 --- a/man/eurostat_geodata_60_2024.Rd +++ b/man/eurostat_geodata_60_2024.Rd @@ -7,73 +7,54 @@ year 2024} \format{ sf object + +The dataset contains 2016 observations (rows) and 12 variables (columns): +\describe{ +\item{id}{JSON id code, the same as NUTS_ID. See NUTS_ID below for further clarification.} +\item{LEVL_CODE}{NUTS level code: 0 (national level), 1 (major socio-economic regions), 2 (basic regions for the application of regional policies) or 3 (small regions).} +\item{NUTS_ID}{NUTS ID code, consisting of country code and numbers (1 for NUTS 1, 2 for NUTS 2 and 3 for NUTS 3)} +\item{CNTR_CODE}{Country code: two-letter ISO code (ISO 3166 alpha-2), except in the case of Greece (EL).} +\item{NAME_LATN}{NUTS name in local language, transliterated to Latin script} +\item{NUTS_NAME}{NUTS name in local language, in local script.} +\item{MOUNT_TYPE}{Mountain typology for NUTS 3 regions. +1: "where more than 50 \% of the surface is covered by topographic mountain areas" +2: "in which more than 50 \% of the regional population lives in topographic mountain areas" +3: "where more than 50 \% of the surface is covered by topographic mountain areas and where more than 50 \% of the regional population lives in these mountain areas" +4: non-mountain region / other region +0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 and non-EU countries)} +\item{URBN_TYPE}{Urban-rural typology for NUTS 3 regions. +1: predominantly urban region +2: intermediate region +3: predominantly rural region +0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 regions)} +\item{COAST_TYPE}{Coastal typology for NUTS 3 regions. +1: coastal (on coast) +2: coastal (>= 50\% of population living within 50km of the coastline) +3: non-coastal region +0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 regions)} +\item{FID}{Same as NUTS_ID} +\item{geo}{Same as NUTS_ID} +\item{geometry}{geospatial information} +} + +Dataset updated: 2023-06-29. For a possibly more recent version, please use +\CRANpkg{giscoR} function \code{gisco_get_nuts()} function. } \source{ -Data source: Eurostat via \code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}}. +Data source: Eurostat via giscoR package function \code{gisco_get_nuts()}. © EuroGeographics for the administrative boundaries Data downloaded from: \url{https://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units} } +\usage{ +eurostat_geodata_60_2024 +} \description{ Geospatial data of Europe from GISCO in 1:60 million scale from year 2024 } -\details{ -The dataset contains 2024 observations (rows) and 12 variables (columns). - -The object contains the following columns: -\itemize{ -\item \strong{id}: JSON id code, the same as \strong{NUTS_ID}. See \strong{NUTS_ID} below for -further clarification. -\item \strong{LEVL_CODE}: NUTS level code: 0 (national level), 1 (major -socio-economic regions), 2 (basic regions for the application of regional -policies) or 3 (small regions). -\item \strong{NUTS_ID}: NUTS ID code, consisting of country code and numbers (1 for -NUTS 1, 2 for NUTS 2 and 3 for NUTS 3) -\item \strong{CNTR_CODE}: Country code: two-letter ISO code (ISO 3166 alpha-2), except -in the case of Greece (EL). -\item \strong{NAME_LATN}: NUTS name in local language, transliterated to Latin script -\item \strong{NUTS_NAME}: NUTS name in local language, in local script. -\item \strong{MOUNT_TYPE}: Mountain typology for NUTS 3 regions. -\itemize{ -\item 1: "where more than 50 \% of the surface is covered by topographic -mountain areas" -\item 2: "in which more than 50 \% of the regional population lives in -topographic mountain areas" -\item 3: "where more than 50 \% of the surface is covered by topographic -mountain areas and where more than 50 \% of the regional population lives -in these mountain areas" -\item 4: non-mountain region / other region -\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 and -non-EU countries) -} -\item \strong{URBN_TYPE}: Urban-rural typology for NUTS 3 regions. -\itemize{ -\item 1: predominantly urban region -\item 2: intermediate region -\item 3: predominantly rural region -\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 -regions) -} -\item \strong{COAST_TYPE}: Coastal typology for NUTS 3 regions. -\itemize{ -\item 1: coastal (on coast) -\item 2: coastal (>= 50\% of population living within 50km of the coastline) -\item 3: non-coastal region -\item 0: no classification provided (e.g. in the case of NUTS 1 and NUTS 2 -regions) -} -\item \strong{FID}: Same as NUTS_ID. -\item \strong{geo}: Same as NUTS_ID, added for for easier joins with dplyr. However, -it is recommended to use other identical fields for this purpose. -\item \strong{geometry}: geospatial information. -} - -Dataset updated: 2023-06-29. For a more recent version, please use -\code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}} function. -} \examples{ eurostat_geodata_60_2024 <- eurostat::eurostat_geodata_60_2024 @@ -135,7 +116,7 @@ for information regarding their licence agreements." } } \seealso{ -\code{\link[giscoR:gisco_get_nuts]{giscoR::gisco_get_nuts()}} and +\CRANpkg{giscoR} function \code{gisco_get_nuts()} and \href{https://ec.europa.eu/eurostat/web/products-manuals-and-guidelines/-/ks-gq-18-008}{Eurostat. (2019). Methodological manual on territorial typologies -- 2018 edition. Manuals and guidelines.} Other datasets: @@ -147,3 +128,4 @@ Other geospatial: } \concept{datasets} \concept{geospatial} +\keyword{datasets} From a940f0e004568d13535a7f0efebbe30b7b630d32 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 4 May 2026 11:54:59 +0300 Subject: [PATCH 59/62] Update tests, snapshot to fix test problems --- .../_snaps/03_get_eurostat_geospatial.md | 1 - tests/testthat/test_01_bibliography.R | 23 +++++--- .../test_03_get_eurostat_geospatial.R | 55 ++++++------------- 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/tests/testthat/_snaps/03_get_eurostat_geospatial.md b/tests/testthat/_snaps/03_get_eurostat_geospatial.md index 93616591..bcaf231a 100644 --- a/tests/testthat/_snaps/03_get_eurostat_geospatial.md +++ b/tests/testthat/_snaps/03_get_eurostat_geospatial.md @@ -18,5 +18,4 @@ a <- get_eurostat_geospatial(nuts_id = "LU", nuts_level = 0) Message Extracting data using giscoR package, please report issues on https://github.com/rOpenGov/giscoR/issues - Cache management as per giscoR. see 'giscoR::gisco_get_nuts()' diff --git a/tests/testthat/test_01_bibliography.R b/tests/testthat/test_01_bibliography.R index f72bcdd6..1327c320 100755 --- a/tests/testthat/test_01_bibliography.R +++ b/tests/testthat/test_01_bibliography.R @@ -1,7 +1,7 @@ test_that("Bibentry gives correct results", { skip_on_cran() skip_if_offline() - + expect_equal( class( suppressWarnings( @@ -12,11 +12,11 @@ test_that("Bibentry gives correct results", { ))), "Bibtex" ) - + expect_error( get_bibentry(code = 123456) ) - + expect_error( get_bibentry( code = c("sts_inpr_a"), @@ -24,21 +24,26 @@ test_that("Bibentry gives correct results", { keywords = "production" ) ) - + + # First cache to get rid of possible vroom problem + cache <- suppressWarnings(get_bibentry( + code = "sts_inpr_b" + )) + # None of the codes not found expect_warning( get_bibentry( code = "sts_inpr_b" - ) + ), "None of the codes were found in the Eurostat table of contents" ) - + # Some of the codes not found expect_warning( get_bibentry( code = c("sts_inpr_b", "sts_inpr_a") ) ) - + expect_warning( get_bibentry( code = c("sts_inpr_a", "nama_10_gdp"), @@ -49,6 +54,6 @@ test_that("Bibentry gives correct results", { format = "character" ) ) - - + + }) diff --git a/tests/testthat/test_03_get_eurostat_geospatial.R b/tests/testthat/test_03_get_eurostat_geospatial.R index d98e0282..9a92370b 100755 --- a/tests/testthat/test_03_get_eurostat_geospatial.R +++ b/tests/testthat/test_03_get_eurostat_geospatial.R @@ -8,27 +8,21 @@ test_that("get_eurostat_geospatial errors", { # Testing argument 'output_class' expect_error(get_eurostat_geospatial(output_class = 0, verbose = FALSE)) expect_error(get_eurostat_geospatial(output_class = "foo", verbose = FALSE)) - expect_error(get_eurostat_geospatial(output_class = "sf", "df", verbose = FALSE)) # Testing argument 'resolution' expect_error(get_eurostat_geospatial(resolution = 12345, verbose = FALSE)) expect_error(get_eurostat_geospatial(resolution = 1:2, verbose = FALSE)) # Testing argument nuts_level - expect_error(get_eurostat_geospatial(nuts_level = 12345, verbose = FALSE)) expect_error(get_eurostat_geospatial(nuts_level = 1:2, verbose = FALSE)) # Testing argument year expect_error(get_eurostat_geospatial(year = 1900, verbose = FALSE)) expect_error(get_eurostat_geospatial(year = c(2003, 2006, verbose = FALSE))) - # Testing argument cache - expect_error(get_eurostat_geospatial(cache = as.logical(NA), year = 2021, verbose = FALSE)) - expect_error(get_eurostat_geospatial(cache = c(TRUE, FALSE), year = 2021, verbose = FALSE)) - # Testing argument CRS expect_error(get_eurostat_geospatial(crs = "north polar stereographic")) - expect_error(get_eurostat_geospatial(crs = c(4326, 3035), verbose = FALSE)) + expect_warning(get_eurostat_geospatial(crs = c(4326, 3035), verbose = FALSE)) # Invalid combinations expect_error(get_eurostat_geospatial(resolution = 60, year = 2003, verbose = FALSE)) @@ -57,11 +51,11 @@ test_that("get_eurostat_geospatial nuts levels", { skip_if_not_installed(pkg = "giscoR") skip_if_not_installed(pkg = "sf") # From internal data with default args - expect_message(all <- get_eurostat_geospatial(nuts_level = "all", verbose = FALSE), "eurostat") - expect_message(n0 <- get_eurostat_geospatial(nuts_level = "0", verbose = FALSE), "eurostat") - expect_message(n1 <- get_eurostat_geospatial(nuts_level = "1", verbose = FALSE), "eurostat") - expect_message(n2 <- get_eurostat_geospatial(nuts_level = "2", verbose = FALSE), "eurostat") - expect_message(n3 <- get_eurostat_geospatial(nuts_level = "3", verbose = FALSE), "eurostat") + expect_message(all <- get_eurostat_geospatial(nuts_level = "all", verbose = TRUE), "eurostat") + expect_message(n0 <- get_eurostat_geospatial(nuts_level = "0", verbose = TRUE), "eurostat") + expect_message(n1 <- get_eurostat_geospatial(nuts_level = "1", verbose = TRUE), "eurostat") + expect_message(n2 <- get_eurostat_geospatial(nuts_level = "2", verbose = TRUE), "eurostat") + expect_message(n3 <- get_eurostat_geospatial(nuts_level = "3", verbose = TRUE), "eurostat") expect_gt(nrow(all), nrow(n3)) expect_gt(nrow(n3), nrow(n2)) @@ -162,7 +156,7 @@ test_that("get_eurostat_geospatial df", { nuts_level = "all", output_class = "df", verbose = TRUE - ), "Extracting data from eurostat::eurostat_geodata_60_2016") + ), "Extracting data from eurostat::eurostat_geodata_60_2024") expect_message(gn0 <- get_eurostat_geospatial( nuts_level = "0", crs = 3035, @@ -254,27 +248,6 @@ test_that("get_eurostat_geospatial cache_dir", { }) -test_that("giscoR returns NULL", { - skip_if_not_installed(pkg = "sf") - skip_if_not_installed(pkg = "giscoR") - skip_on_cran() - skip_if_offline() - skip_if(!giscoR::gisco_check_access(), "No access to GISCO") - skip_if(packageVersion("giscoR") < "0.3.5", "Use latest giscoR release") - - options(giscoR_test_offline = TRUE) - expect_message( - n <- get_eurostat_geospatial( - country = "AT", nuts_level = "0", - update_cache = TRUE - ), - "not reachable" - ) - expect_null(n) - options(giscoR_test_offline = FALSE) -}) - - test_that("Check column names", { skip_if_not_installed(pkg = "giscoR") skip_if_not_installed(pkg = "sf") @@ -304,7 +277,7 @@ test_that("Check column names POLYGONS from GISCO", { skip_on_cran() skip_if_offline() skip_if(!giscoR::gisco_check_access(), "No access to GISCO") - skip_if(packageVersion("giscoR") < "0.3.5", "Use latest giscoR release") + skip_if(packageVersion("giscoR") < "1.1.0", "Use latest giscoR release") col_order <- c( "id", "LEVL_CODE", "NUTS_ID", "CNTR_CODE", "NAME_LATN", @@ -564,6 +537,12 @@ test_that("Check column names LABELS from GISCO", { expect_s3_class(poly_df, "data.frame") expect_identical(names(poly_df), col_order[-length(col_order)]) + names_2024 <- c("id", "LEVL_CODE", "NUTS_ID", "CNTR_CODE", "NAME_LATN", + "NUTS_NAME", "MOUNT_TYPE", "URBN_TYPE", "COAST_TYPE", + "FID", "geo", "LAT", "LON", "NAME_ENGL", "NAME_FREN", + "ISO3_CODE", "SVRG_UN", "CAPT", "EU_STAT", "EFTA_STAT", + "CC_STAT", "NAME_GERM", "geometry") + # Labels 2024 poly <- get_eurostat_geospatial( nuts_level = 0, resolution = 60, year = 2024, @@ -571,7 +550,7 @@ test_that("Check column names LABELS from GISCO", { verbose = FALSE ) expect_s3_class(poly, "sf") - expect_identical(names(poly), col_order) + expect_identical(names(poly), names_2024) # df poly_df <- get_eurostat_geospatial( @@ -581,7 +560,7 @@ test_that("Check column names LABELS from GISCO", { ) expect_s3_class(poly_df, "data.frame") - expect_identical(names(poly_df), col_order[-length(col_order)]) + expect_identical(names(poly_df), names_2024[-length(names_2024)]) }) @@ -591,7 +570,7 @@ test_that("Check column names BORDERS from GISCO", { skip_on_cran() skip_if_offline() skip_if(!giscoR::gisco_check_access(), "No access to GISCO") - skip_if(packageVersion("giscoR") < "0.3.5", "Use latest giscoR release") + skip_if(packageVersion("giscoR") < "1.1.0", "Use latest giscoR release") # BORDERS 2003 poly <- get_eurostat_geospatial( From 8856b2da05e2f9a9b542089a6cdc20fbcbdc47b1 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 4 May 2026 13:39:41 +0300 Subject: [PATCH 60/62] Update workflows with most recent versions --- .github/workflows/check-full.yaml | 9 ++++++--- .github/workflows/check-standard.yaml | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check-full.yaml b/.github/workflows/check-full.yaml index 7a47123a..8c667fdb 100644 --- a/.github/workflows/check-full.yaml +++ b/.github/workflows/check-full.yaml @@ -6,6 +6,9 @@ on: - cron: '0 0 * * 0' name: R-CMD-check (full) + +permissions: read-all + jobs: R-CMD-check: runs-on: ${{ matrix.config.os }} @@ -27,7 +30,7 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-pandoc@v2 @@ -35,7 +38,6 @@ jobs: with: r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} - use-public-rspm: true - uses: r-lib/actions/setup-r-dependencies@v2 with: @@ -44,4 +46,5 @@ jobs: - uses: r-lib/actions/check-r-package@v2 with: - upload-snapshots: true \ No newline at end of file + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' diff --git a/.github/workflows/check-standard.yaml b/.github/workflows/check-standard.yaml index b60da2fb..45f18b19 100644 --- a/.github/workflows/check-standard.yaml +++ b/.github/workflows/check-standard.yaml @@ -8,6 +8,8 @@ on: name: R-CMD-check (standard) +permissions: read-all + jobs: R-CMD-check: runs-on: ubuntu-latest @@ -15,15 +17,16 @@ jobs: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-r@v2 - with: - use-public-rspm: true - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::rcmdcheck needs: check - - uses: r-lib/actions/check-r-package@v2 \ No newline at end of file + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' From 687369388fa285f0de47421d149f909e54e9e627 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 4 May 2026 13:39:52 +0300 Subject: [PATCH 61/62] Stricter data.table version requirement --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index c3cdf70d..f14262bb 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -55,7 +55,7 @@ Imports: tibble, tidyr (>= 1.0.0), xml2, - data.table (>= 1.14.8) + data.table (>= 1.18.2.1) Suggests: giscoR (>= 1.1.0), knitr, From 8d5f1299092fe8f2162376d9f7c95231f51382f1 Mon Sep 17 00:00:00 2001 From: Pyry Kantanen Date: Mon, 4 May 2026 14:26:51 +0300 Subject: [PATCH 62/62] Add new import - R.utils - to enforce it being installed for data.table --- DESCRIPTION | 1 + NAMESPACE | 1 + R/check_access_to_data.R | 2 ++ 3 files changed, 4 insertions(+) diff --git a/DESCRIPTION b/DESCRIPTION index f14262bb..44620fe4 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -46,6 +46,7 @@ Imports: ISOweek, jsonlite, lubridate, + R.utils, rappdirs, readr, regions, diff --git a/NAMESPACE b/NAMESPACE index 610ed62f..61d87b39 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -33,6 +33,7 @@ export(set_eurostat_cache_dir) export(validate_geo_code) export(validate_nuts_regions) importFrom(ISOweek,ISOweek2date) +importFrom(R.utils,isUrl) importFrom(classInt,classIntervals) importFrom(countrycode,countrycode) importFrom(curl,curl_download) diff --git a/R/check_access_to_data.R b/R/check_access_to_data.R index b061d1d2..dedba1ad 100755 --- a/R/check_access_to_data.R +++ b/R/check_access_to_data.R @@ -9,12 +9,14 @@ #' #' @importFrom curl curl_download #' @importFrom utils download.file +#' @importFrom R.utils isUrl #' #' @export check_access_to_data <- function() { temp <- tempfile() http_url <- "https://ec.europa.eu/eurostat/cache/GISCO/distribution/v2/nuts/geojson/NUTS_RG_60M_2006_4326_LEVL_0.geojson" + stopifnot(R.utils::isUrl(http_url)) # If unix use curl::curl_download to test connection # If windows use download.file with default method 'wininet' if (.Platform$OS.type == "unix") {