├── .github ├── .gitignore └── workflows │ ├── lint.yaml │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ └── test-coverage.yaml ├── LICENSE ├── cran-comments.md ├── .lintr ├── _pkgdown.yml ├── .gitignore ├── .Rbuildignore ├── R ├── owidapi-package.R ├── perform_request.R ├── owid_get_metadata.R ├── owid_embed.R ├── helpers.R ├── owid_get_catalog.R ├── owid_render.R ├── owid_search.R └── owid_get.R ├── NAMESPACE ├── tests ├── testthat.R └── testthat │ ├── test-owid_get_catalog.R │ ├── test-owid_embed.R │ ├── test-owid_get_metadata.R │ ├── test-owid_search.R │ ├── test-helpers.R │ └── test-owid_get.R ├── NEWS.md ├── man ├── owid_get_catalog.Rd ├── owidapi-package.Rd ├── owid_get_metadata.Rd ├── owid_embed.Rd ├── figures │ ├── lifecycle-deprecated.svg │ ├── lifecycle-superseded.svg │ ├── lifecycle-experimental.svg │ └── lifecycle-stable.svg ├── owid_search.Rd ├── owid_output.Rd └── owid_get.Rd ├── LICENSE.md ├── DESCRIPTION ├── README.Rmd └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2025 2 | COPYRIGHT HOLDER: Christoph Scheuch 3 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## R CMD check results 2 | 3 | 0 errors | 0 warnings | 0 note 4 | -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults() # see vignette("lintr") 2 | encoding: "UTF-8" 3 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://tidy-intelligence.github.io/r-owidapi/ 2 | template: 3 | bootstrap: 5 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | docs 6 | .Rdata 7 | .httr-oauth 8 | .DS_Store 9 | .quarto 10 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^LICENSE\.md$ 2 | ^README\.Rmd$ 3 | ^_pkgdown\.yml$ 4 | ^docs$ 5 | ^pkgdown$ 6 | ^\.github$ 7 | .lintr 8 | cran-comments.md 9 | ^cran-comments\.md$ 10 | ^CRAN-SUBMISSION$ 11 | -------------------------------------------------------------------------------- /R/owidapi-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @import cli 3 | #' @import httr2 4 | #' @import tibble 5 | #' @import jsonlite 6 | "_PACKAGE" 7 | 8 | ## usethis namespace: start 9 | #' @importFrom lifecycle deprecated 10 | #' @importFrom utils read.csv 11 | #' @importFrom rlang check_installed 12 | ## usethis namespace: end 13 | NULL 14 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(owid_embed) 4 | export(owid_get) 5 | export(owid_get_catalog) 6 | export(owid_get_metadata) 7 | export(owid_output) 8 | export(owid_search) 9 | export(owid_server) 10 | import(cli) 11 | import(httr2) 12 | import(jsonlite) 13 | import(tibble) 14 | importFrom(lifecycle,deprecated) 15 | importFrom(rlang,check_installed) 16 | importFrom(utils,read.csv) 17 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(owidapi) 11 | 12 | test_check("owidapi") 13 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # owidapi (development version) 2 | 3 | # owidapi 0.1.1 4 | 5 | - Encapsulated request logic in internal `perform_request()` function. 6 | - Updated variable parsing & introduced graceful error handling in `owid_get_catalog()` 7 | 8 | # owidapi 0.1.0 9 | 10 | - Initial release with `owid_get()`, `owid_get_metadata()`, `owid_get_catalog()`, and `owid_search()`. 11 | - Includes experimental helpers for shiny apps `owid_output()` and `owid_server()`. 12 | -------------------------------------------------------------------------------- /R/perform_request.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @noRd 3 | perform_request <- function(req, context) { 4 | tryCatch( 5 | { 6 | resp <- req |> 7 | req_user_agent( 8 | "owidapi R package (https://github.com/tidy-intelligence/r-owidapi)" 9 | ) |> 10 | req_perform() 11 | resp 12 | }, 13 | error = function(e) { 14 | cli::cli_abort( 15 | c( 16 | "Failed to retrieve data from Our World in Data.", 17 | "i" = "Error message: {conditionMessage(e)}", 18 | "i" = "Check your internet connection and the dataset or URL." 19 | ), 20 | call = call(context) 21 | ) 22 | } 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /man/owid_get_catalog.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/owid_get_catalog.R 3 | \name{owid_get_catalog} 4 | \alias{owid_get_catalog} 5 | \title{Download data catalog of Our World in Data} 6 | \usage{ 7 | owid_get_catalog(snake_case = TRUE) 8 | } 9 | \arguments{ 10 | \item{snake_case}{Logical. If TRUE (default), converts column names to 11 | lowercase.} 12 | } 13 | \value{ 14 | A tibble containing the OWID catalog. 15 | } 16 | \description{ 17 | Downloads the data catalog of Our World in Data (OWID) hosted on Datasette. 18 | } 19 | \examples{ 20 | \dontshow{if (curl::has_internet()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 21 | \donttest{ 22 | # Download a full table 23 | owid_get_catalog() 24 | } 25 | \dontshow{\}) # examplesIf} 26 | } 27 | -------------------------------------------------------------------------------- /tests/testthat/test-owid_get_catalog.R: -------------------------------------------------------------------------------- 1 | test_that("owid_get_catalog returns expected catalog structure", { 2 | skip_if_offline() 3 | 4 | result <- owid_get_catalog() 5 | 6 | expect_s3_class(result, "tbl_df") 7 | expect_gt(nrow(result), 0) 8 | }) 9 | 10 | test_that("owid_get_catalog handles snake_case", { 11 | skip_if_offline() 12 | 13 | result <- owid_get_catalog(snake_case = FALSE) 14 | 15 | expect_s3_class(result, "tbl_df") 16 | expect_gt(nrow(result), 0) 17 | }) 18 | 19 | test_that("owid_get_catalog handles request errors gracefully", { 20 | with_mocked_bindings( 21 | req_perform = function(...) { 22 | cli_abort("Mocked network error") 23 | }, 24 | { 25 | expect_message( 26 | owid_get_catalog(), 27 | regexp = "Failed to retrieve data from Our World in Data\\." 28 | ) 29 | } 30 | ) 31 | }) 32 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: lint.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: any::lintr, local::. 27 | needs: lint 28 | 29 | - name: Lint 30 | run: lintr::lint_package() 31 | shell: Rscript {0} 32 | env: 33 | LINTR_ERROR_ON_LINT: true 34 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: R-CMD-check.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | R_KEEP_PKG_SOURCE: yes 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::rcmdcheck 28 | needs: check 29 | 30 | - uses: r-lib/actions/check-r-package@v2 31 | with: 32 | upload-snapshots: true 33 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 34 | -------------------------------------------------------------------------------- /man/owidapi-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/owidapi-package.R 3 | \docType{package} 4 | \name{owidapi-package} 5 | \alias{owidapi} 6 | \alias{owidapi-package} 7 | \title{owidapi: Access the Our World in Data Chart API} 8 | \description{ 9 | Retrieve data from the Our World in Data (OWID) Chart API \url{https://docs.owid.io/projects/etl/api/}. OWID provides public access to more than 5,000 charts focusing on global problems such as poverty, disease, hunger, climate change, war, existential risks, and inequality. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/tidy-intelligence/r-owidapi} 15 | \item \url{https://tidy-intelligence.github.io/r-owidapi/} 16 | \item Report bugs at \url{https://github.com/tidy-intelligence/r-owidapi/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Christoph Scheuch \email{christoph@tidy-intelligence.com} (\href{https://orcid.org/0009-0004-0423-6819}{ORCID}) [copyright holder] 22 | 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 Christoph Scheuch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /man/owid_get_metadata.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/owid_get_metadata.R 3 | \name{owid_get_metadata} 4 | \alias{owid_get_metadata} 5 | \title{Download metadata from Our World in Data} 6 | \usage{ 7 | owid_get_metadata(chart_id = NULL, url = NULL) 8 | } 9 | \arguments{ 10 | \item{chart_id}{Character string specifying the chart identifier 11 | (e.g., "life-expectancy"). Not required if \code{url} is provided.} 12 | 13 | \item{url}{Direct URL to an OWID chart. If provided, \code{chart_id} is ignored.} 14 | } 15 | \value{ 16 | A list containing the requested OWID metadata. 17 | } 18 | \description{ 19 | Retrieves the metadata for a data set from Our World in Data (OWID) by 20 | specifying a chart identifier or direct URL. 21 | } 22 | \examples{ 23 | \dontshow{if (curl::has_internet()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 24 | \donttest{ 25 | # Download metadata using a data set 26 | owid_get_metadata("life-expectancy") 27 | 28 | # Download metadata using an url 29 | owid_get_metadata( 30 | url = "https://ourworldindata.org/grapher/civil-liberties-score-fh" 31 | ) 32 | } 33 | \dontshow{\}) # examplesIf} 34 | } 35 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: owidapi 2 | Title: Access the Our World in Data Chart API 3 | Version: 0.1.1.9000 4 | Authors@R: c( 5 | person("Christoph", "Scheuch", , "christoph@tidy-intelligence.com", 6 | role = c("aut", "cre", "cph"), 7 | comment = c(ORCID = "0009-0004-0423-6819")) 8 | ) 9 | Description: Retrieve data from the Our World in Data (OWID) Chart API 10 | . OWID provides public access to 11 | more than 5,000 charts focusing on global problems such as poverty, 12 | disease, hunger, climate change, war, existential risks, and inequality. 13 | License: MIT + file LICENSE 14 | Depends: 15 | R (>= 4.1) 16 | Imports: 17 | cli (>= 3.0.0), 18 | httr2 (>= 1.0.0), 19 | jsonlite (>= 1.0.0), 20 | lifecycle, 21 | tibble, 22 | utils, 23 | rlang 24 | Suggests: 25 | curl, 26 | shiny, 27 | testthat (>= 3.0.0) 28 | URL: https://github.com/tidy-intelligence/r-owidapi, https://tidy-intelligence.github.io/r-owidapi/ 29 | BugReports: https://github.com/tidy-intelligence/r-owidapi/issues 30 | Config/testthat/edition: 3 31 | Encoding: UTF-8 32 | Roxygen: list(markdown = TRUE) 33 | RoxygenNote: 7.3.2 34 | -------------------------------------------------------------------------------- /man/owid_embed.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/owid_embed.R 3 | \name{owid_embed} 4 | \alias{owid_embed} 5 | \title{Embed Our World in Data Chart in HTML} 6 | \usage{ 7 | owid_embed(url, width = "100\%", height = "600px") 8 | } 9 | \arguments{ 10 | \item{url}{A character string containing the URL of the Our World in 11 | Data chart. Must begin with "https://ourworldindata.org/grapher/".} 12 | 13 | \item{width}{A character string specifying the width of the iframe. 14 | Default is "100\%".} 15 | 16 | \item{height}{A character string specifying the height of the iframe. 17 | Default is "600px".} 18 | } 19 | \value{ 20 | A character string containing the HTML iframe to embed the chart. 21 | } 22 | \description{ 23 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} 24 | 25 | Creates HTML code to embed an interactive chart from Our World in Data 26 | into an HTML document using an iframe. 27 | } 28 | \examples{ 29 | owid_embed( 30 | "https://ourworldindata.org/grapher/co2-emissions-per-capita", 31 | width = "90\%", 32 | height = "500px" 33 | ) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /R/owid_get_metadata.R: -------------------------------------------------------------------------------- 1 | #' Download metadata from Our World in Data 2 | #' 3 | #' @description 4 | #' Retrieves the metadata for a data set from Our World in Data (OWID) by 5 | #' specifying a chart identifier or direct URL. 6 | #' 7 | #' @param chart_id Character string specifying the chart identifier 8 | #' (e.g., "life-expectancy"). Not required if `url` is provided. 9 | #' @param url Direct URL to an OWID chart. If provided, `chart_id` is ignored. 10 | #' 11 | #' @return A list containing the requested OWID metadata. 12 | #' 13 | #' @examplesIf curl::has_internet() 14 | #' \donttest{ 15 | #' # Download metadata using a data set 16 | #' owid_get_metadata("life-expectancy") 17 | #' 18 | #' # Download metadata using an url 19 | #' owid_get_metadata( 20 | #' url = "https://ourworldindata.org/grapher/civil-liberties-score-fh" 21 | #' ) 22 | #' } 23 | #' @export 24 | owid_get_metadata <- function( 25 | chart_id = NULL, 26 | url = NULL 27 | ) { 28 | if (is.null(url)) { 29 | base_url <- "https://ourworldindata.org/grapher/" 30 | 31 | req <- request(base_url) |> 32 | req_url_path_append(paste0(chart_id, ".metadata.json")) 33 | } else { 34 | url_prepared <- prepare_url(url, ".metadata.json") 35 | req <- request(url_prepared) 36 | } 37 | 38 | resp <- perform_request(req, "owid_get_metadata") 39 | 40 | metadata <- resp |> 41 | resp_body_json() 42 | 43 | metadata 44 | } 45 | -------------------------------------------------------------------------------- /man/figures/lifecycle-deprecated.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: deprecated 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | deprecated 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-superseded.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: superseded 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | superseded 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-experimental.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: experimental 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | experimental 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-stable.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: stable 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | lifecycle 21 | 22 | 25 | 26 | stable 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /man/owid_search.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/owid_search.R 3 | \name{owid_search} 4 | \alias{owid_search} 5 | \title{Search for keywords in OWID catalog data} 6 | \usage{ 7 | owid_search(data, keywords, columns = NULL) 8 | } 9 | \arguments{ 10 | \item{data}{A data frame, typically obtained from \link{owid_get_catalog}.} 11 | 12 | \item{keywords}{A character vector of one or more keywords to search for. 13 | The search is case-insensitive.} 14 | 15 | \item{columns}{An optional character vector of column names to search within. 16 | If NULL (default), all character and factor columns are searched.} 17 | } 18 | \value{ 19 | A filtered data frame containing only rows that match at least one of 20 | the keywords in at least one of the specified columns. 21 | } 22 | \description{ 23 | This function searches for a vector of keywords within specified columns of 24 | an OWID catalog data frame. If no columns are specified, it 25 | searches all character and factor columns. 26 | } 27 | \examples{ 28 | \dontshow{if (curl::has_internet()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 29 | \donttest{ 30 | # Get the OWID catalog 31 | catalog <- owid_get_catalog() 32 | 33 | # Search for climate or carbon in all text columns 34 | owid_search(catalog, c("climate", "carbon")) 35 | 36 | # Search only in the title column 37 | owid_search(catalog, c("climate", "carbon"), c("title")) 38 | } 39 | \dontshow{\}) # examplesIf} 40 | } 41 | -------------------------------------------------------------------------------- /R/owid_embed.R: -------------------------------------------------------------------------------- 1 | #' Embed Our World in Data Chart in HTML 2 | #' 3 | #' @description 4 | #' `r lifecycle::badge("experimental")` 5 | #' 6 | #' Creates HTML code to embed an interactive chart from Our World in Data 7 | #' into an HTML document using an iframe. 8 | #' 9 | #' @param url A character string containing the URL of the Our World in 10 | #' Data chart. Must begin with "https://ourworldindata.org/grapher/". 11 | #' @param width A character string specifying the width of the iframe. 12 | #' Default is "100%". 13 | #' @param height A character string specifying the height of the iframe. 14 | #' Default is "600px". 15 | #' 16 | #' @return A character string containing the HTML iframe to embed the chart. 17 | #' 18 | #' @examples 19 | #' owid_embed( 20 | #' "https://ourworldindata.org/grapher/co2-emissions-per-capita", 21 | #' width = "90%", 22 | #' height = "500px" 23 | #' ) 24 | #' 25 | #' @export 26 | owid_embed <- function(url, width = "100%", height = "600px") { 27 | if (!grepl("^https://ourworldindata.org/grapher/", url)) { 28 | cli_abort( 29 | "URL must be from Our World in Data (https://ourworldindata.org/grapher/)" 30 | ) 31 | } 32 | 33 | #nolint start 34 | iframe_html <- paste0( 35 | '' 45 | ) 46 | #nolint end 47 | 48 | iframe_html 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Build site 40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 41 | shell: Rscript {0} 42 | 43 | - name: Deploy to GitHub pages 🚀 44 | if: github.event_name != 'pull_request' 45 | uses: JamesIves/github-pages-deploy-action@v4.5.0 46 | with: 47 | clean: false 48 | branch: gh-pages 49 | folder: docs 50 | -------------------------------------------------------------------------------- /tests/testthat/test-owid_embed.R: -------------------------------------------------------------------------------- 1 | test_that("owid_embed generates correct iframe HTML", { 2 | url <- "https://ourworldindata.org/grapher/co2-emissions-per-capita" 3 | result <- owid_embed(url) 4 | 5 | expected <- paste0( 6 | '' 12 | ) 13 | 14 | expect_equal(result, expected) 15 | }) 16 | 17 | test_that("owid_embed allows custom width and height", { 18 | url <- "https://ourworldindata.org/grapher/co2-emissions-per-capita" 19 | result <- owid_embed(url, width = "90%", height = "500px") 20 | 21 | expected <- paste0( 22 | '' 28 | ) 29 | 30 | expect_equal(result, expected) 31 | }) 32 | 33 | test_that("owid_embed rejects invalid URLs", { 34 | invalid_url <- "https://example.com/invalid-graph" 35 | 36 | expect_error( 37 | owid_embed(invalid_url), 38 | "URL must be from Our World in Data" 39 | ) 40 | }) 41 | 42 | test_that("owid_embed works with different graph URLs", { 43 | url <- "https://ourworldindata.org/grapher/gdp-per-capita" 44 | result <- owid_embed(url) 45 | 46 | expected <- paste0( 47 | '' 53 | ) 54 | 55 | expect_equal(result, expected) 56 | }) 57 | -------------------------------------------------------------------------------- /R/helpers.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @noRd 3 | prepare_url <- function(url, ending = ".csv") { 4 | if (grepl(paste0("\\", ending, "\\?"), url)) { 5 | return(url) 6 | } else { 7 | parts <- strsplit(url, "\\?", fixed = FALSE)[[1]] 8 | needs_filtered_param <- grepl("time|country", url, ignore.case = TRUE) 9 | 10 | if (length(parts) == 1) { 11 | base_url <- paste0(url, ending) 12 | 13 | if (needs_filtered_param) { 14 | return(paste0(base_url, "?csvType=filtered")) 15 | } else { 16 | return(base_url) 17 | } 18 | } 19 | 20 | base_url <- paste0(parts[1], ending) 21 | query_params <- parts[-1] 22 | 23 | if ( 24 | needs_filtered_param && 25 | !grepl("csvType=filtered", paste(query_params, collapse = "?")) 26 | ) { 27 | modified_url <- paste0( 28 | base_url, 29 | "?csvType=filtered&", 30 | paste(query_params, collapse = "?") 31 | ) 32 | } else { 33 | modified_url <- paste0( 34 | base_url, 35 | "?", 36 | paste(query_params, collapse = "?") 37 | ) 38 | } 39 | 40 | return(modified_url) 41 | } 42 | } 43 | 44 | #' @keywords internal 45 | #' @noRd 46 | format_date <- function(date) { 47 | if (!is.na(date) && grepl("^\\d{4}-\\d{2}-\\d{2}$", date)) { 48 | date 49 | } else { 50 | as.character(date) 51 | } 52 | } 53 | 54 | #' @keywords internal 55 | #' @noRd 56 | to_snake_case <- function(df) { 57 | convert_string <- function(x) { 58 | x <- gsub("_", " ", x) 59 | x <- gsub("([a-z])([A-Z])", "\\1 \\2", x) 60 | x <- tolower(x) 61 | x <- gsub(" ", "_", x) 62 | } 63 | colnames(df) <- sapply(colnames(df), convert_string) 64 | df 65 | } 66 | -------------------------------------------------------------------------------- /man/owid_output.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/owid_render.R 3 | \name{owid_output} 4 | \alias{owid_output} 5 | \alias{owid_server} 6 | \title{Create OWID chart output elements in Shiny} 7 | \usage{ 8 | owid_output(id) 9 | 10 | owid_server(id, url, width = "100\%", height = "600px") 11 | } 12 | \arguments{ 13 | \item{id}{Character string. The ID of the output element.} 14 | 15 | \item{url}{Character string. The URL of the OWID chart to embed.} 16 | 17 | \item{width}{Character string. The width of the chart (default: "100\%").} 18 | 19 | \item{height}{Character string. The height of the chart (default: "600px").} 20 | } 21 | \value{ 22 | A Shiny HTML output element where the OWID chart will be rendered. 23 | } 24 | \description{ 25 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} 26 | 27 | This function creates an HTML output element for embedding Our World in Data 28 | (OWID) charts in a Shiny application. It should be used in the UI definition 29 | of your Shiny app. 30 | } 31 | \examples{ 32 | \dontshow{if (interactive() & curl::has_internet()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 33 | library(shiny) 34 | 35 | ui <- fluidPage( 36 | owid_output("gdp_chart"), 37 | owid_output("co2_chart") 38 | ) 39 | 40 | server <- function(input, output) { 41 | owid_server( 42 | "gdp_chart", 43 | "https://ourworldindata.org/grapher/gdp-per-capita-worldbank?tab=line" 44 | ) 45 | owid_server( 46 | "co2_chart", 47 | "https://ourworldindata.org/grapher/co2-emissions-per-capita", 48 | height = "500px" 49 | ) 50 | } 51 | 52 | shinyApp(ui = ui, server = server) 53 | \dontshow{\}) # examplesIf} 54 | } 55 | -------------------------------------------------------------------------------- /R/owid_get_catalog.R: -------------------------------------------------------------------------------- 1 | #' Download data catalog of Our World in Data 2 | #' 3 | #' @description 4 | #' Downloads the data catalog of Our World in Data (OWID) hosted on Datasette. 5 | #' 6 | #' @param snake_case Logical. If TRUE (default), converts column names to 7 | #' lowercase. 8 | #' 9 | #' @return A tibble containing the OWID catalog. 10 | #' @examplesIf curl::has_internet() 11 | #' \donttest{ 12 | #' # Download a full table 13 | #' owid_get_catalog() 14 | #' } 15 | #' @export 16 | owid_get_catalog <- function( 17 | snake_case = TRUE 18 | ) { 19 | base_url <- c( 20 | "https://datasette-public.owid.io/owid/charts.csv?_labels=on&_size=max" 21 | ) 22 | 23 | req <- request(base_url) 24 | 25 | tryCatch( 26 | { 27 | resp <- perform_request(req, "owid_get_catalog") 28 | 29 | catalog_raw <- resp |> 30 | resp_body_string() |> 31 | textConnection() |> 32 | read.csv() |> 33 | tibble::as_tibble() 34 | 35 | # Parse logicals 36 | catalog_raw$isInheritanceEnabled <- catalog_raw$isInheritanceEnabled == 37 | "True" 38 | catalog_raw$isIndexable <- catalog_raw$isIndexable == "True" 39 | catalog_raw$isPublished <- catalog_raw$isPublished == "True" 40 | 41 | # Parse dates 42 | catalog_raw$createdAt <- as.Date(catalog_raw$createdAt) 43 | catalog_raw$updatedAt <- as.Date(catalog_raw$updatedAt) 44 | catalog_raw$lastEditedAt <- as.Date(catalog_raw$lastEditedAt) 45 | catalog_raw$publishedAt <- as.Date(catalog_raw$publishedAt) 46 | 47 | if (snake_case) { 48 | catalog <- to_snake_case(catalog_raw) 49 | } else { 50 | catalog <- catalog_raw 51 | } 52 | 53 | catalog 54 | }, 55 | error = function(e) { 56 | cli_alert( 57 | conditionMessage(e) 58 | ) 59 | invisible(NULL) 60 | } 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /R/owid_render.R: -------------------------------------------------------------------------------- 1 | #' Create OWID chart output elements in Shiny 2 | #' 3 | #' @description 4 | #' `r lifecycle::badge("experimental")` 5 | #' 6 | #' This function creates an HTML output element for embedding Our World in Data 7 | #' (OWID) charts in a Shiny application. It should be used in the UI definition 8 | #' of your Shiny app. 9 | #' 10 | #' @param id Character string. The ID to use for the output element. 11 | #' @return A Shiny HTML output element where the OWID chart will be rendered. 12 | #' 13 | #' @examplesIf interactive() & curl::has_internet() 14 | #' library(shiny) 15 | #' 16 | #' ui <- fluidPage( 17 | #' owid_output("gdp_chart"), 18 | #' owid_output("co2_chart") 19 | #' ) 20 | #' 21 | #' server <- function(input, output) { 22 | #' owid_server( 23 | #' "gdp_chart", 24 | #' "https://ourworldindata.org/grapher/gdp-per-capita-worldbank?tab=line" 25 | #' ) 26 | #' owid_server( 27 | #' "co2_chart", 28 | #' "https://ourworldindata.org/grapher/co2-emissions-per-capita", 29 | #' height = "500px" 30 | #' ) 31 | #' } 32 | #' 33 | #' shinyApp(ui = ui, server = server) 34 | #' 35 | #' @export 36 | owid_output <- function(id) { 37 | # nocov start 38 | shiny::htmlOutput(id) 39 | # nocov end 40 | } 41 | 42 | #' @rdname owid_output 43 | #' @param id Character string. The ID of the output element. 44 | #' @param url Character string. The URL of the OWID chart to embed. 45 | #' @param width Character string. The width of the chart (default: "100%"). 46 | #' @param height Character string. The height of the chart (default: "600px"). 47 | #' @export 48 | owid_server <- function(id, url, width = "100%", height = "600px") { 49 | # nocov start 50 | output <- get("output", envir = parent.frame()) 51 | output[[id]] <- shiny::renderUI({ 52 | html_content <- owid_embed(url, width, height) 53 | shiny::HTML(html_content) 54 | }) 55 | # nocov end 56 | } 57 | -------------------------------------------------------------------------------- /R/owid_search.R: -------------------------------------------------------------------------------- 1 | #' Search for keywords in OWID catalog data 2 | #' 3 | #' @description 4 | #' This function searches for a vector of keywords within specified columns of 5 | #' an OWID catalog data frame. If no columns are specified, it 6 | #' searches all character and factor columns. 7 | #' 8 | #' @param data A data frame, typically obtained from \link{owid_get_catalog}. 9 | #' @param keywords A character vector of one or more keywords to search for. 10 | #' The search is case-insensitive. 11 | #' @param columns An optional character vector of column names to search within. 12 | #' If NULL (default), all character and factor columns are searched. 13 | #' 14 | #' @return A filtered data frame containing only rows that match at least one of 15 | #' the keywords in at least one of the specified columns. 16 | #' 17 | #' @examplesIf curl::has_internet() 18 | #' \donttest{ 19 | #' # Get the OWID catalog 20 | #' catalog <- owid_get_catalog() 21 | #' 22 | #' # Search for climate or carbon in all text columns 23 | #' owid_search(catalog, c("climate", "carbon")) 24 | #' 25 | #' # Search only in the title column 26 | #' owid_search(catalog, c("climate", "carbon"), c("title")) 27 | #' } 28 | #' @export 29 | owid_search <- function(data, keywords, columns = NULL) { 30 | if (!is.data.frame(data)) { 31 | cli::cli_abort("Input 'data' must be a data frame") 32 | } 33 | 34 | if (!is.character(keywords) || length(keywords) == 0) { 35 | cli::cli_abort("'keywords' must be a non-empty character vector") 36 | } 37 | 38 | if (is.null(columns)) { 39 | columns <- names(data)[ 40 | sapply(data, function(x) is.character(x) || is.factor(x)) 41 | ] 42 | } else if (!all(columns %in% names(data))) { 43 | cli::cli_abort("Some specified columns do not exist in the data") 44 | } 45 | 46 | match_rows <- apply(data[, columns, drop = FALSE], 1, function(row) { 47 | any(sapply(keywords, function(keyword) { 48 | any(grepl(keyword, row, ignore.case = TRUE)) 49 | })) 50 | }) 51 | 52 | data[match_rows, ] 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::covr, any::xml2 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | cov <- covr::package_coverage( 33 | quiet = FALSE, 34 | clean = FALSE, 35 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 36 | ) 37 | print(cov) 38 | covr::to_cobertura(cov) 39 | shell: Rscript {0} 40 | 41 | - uses: codecov/codecov-action@v4 42 | with: 43 | # Fail if error if not on PR, or if on PR and token is given 44 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 45 | file: ./cobertura.xml 46 | plugin: noop 47 | disable_search: true 48 | token: ${{ secrets.CODECOV_TOKEN }} 49 | 50 | - name: Show testthat output 51 | if: always() 52 | run: | 53 | ## -------------------------------------------------------------------- 54 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 55 | shell: bash 56 | 57 | - name: Upload test results 58 | if: failure() 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: coverage-test-failures 62 | path: ${{ runner.temp }}/package 63 | -------------------------------------------------------------------------------- /tests/testthat/test-owid_get_metadata.R: -------------------------------------------------------------------------------- 1 | test_that("owid_get_metadata works with chart_id parameter", { 2 | skip_if_offline() 3 | 4 | result <- owid_get_metadata("life-expectancy") 5 | 6 | expect_type(result, "list") 7 | expect_true(length(result) > 0) 8 | expect_true(!is.null(result$chart)) 9 | }) 10 | 11 | test_that("owid_get_metadata works with URL parameter", { 12 | skip_if_offline() 13 | 14 | url <- "https://ourworldindata.org/grapher/civil-liberties-score-fh" 15 | result <- owid_get_metadata(url = url) 16 | 17 | expect_type(result, "list") 18 | expect_true(length(result) > 0) 19 | expect_true(!is.null(result$chart)) 20 | }) 21 | 22 | test_that("owid_get_metadata prioritizes url over chart_id", { 23 | skip_if_offline() 24 | 25 | chart_id <- "life-expectancy" 26 | url <- "https://ourworldindata.org/grapher/civil-liberties-score-fh" 27 | 28 | expected_result <- owid_get_metadata(url = url) 29 | actual_result <- owid_get_metadata(chart_id = chart_id, url = url) 30 | 31 | expect_equal(actual_result, expected_result) 32 | }) 33 | 34 | test_that("owid_get_metadata handles invalid chart_id parameter", { 35 | skip_if_offline() 36 | 37 | chart_id <- "non-existent-dataset-123456789" 38 | 39 | expect_error( 40 | owid_get_metadata(chart_id = chart_id), 41 | "Failed to retrieve data from Our World in Data" 42 | ) 43 | }) 44 | 45 | test_that("owid_get_metadata handles invalid URL parameter", { 46 | skip_if_offline() 47 | 48 | url <- "https://ourworldindata.org/grapher/non-existent-dataset-123456789" 49 | 50 | expect_error( 51 | owid_get_metadata(url = url), 52 | "Failed to retrieve data from Our World in Data" 53 | ) 54 | }) 55 | 56 | test_that("owid_get_metadata throws error when both chart_id & url are NULL", { 57 | expect_error( 58 | owid_get_metadata(chart_id = NULL, url = NULL), 59 | "Failed to retrieve data from Our World in Data" 60 | ) 61 | }) 62 | 63 | test_that("owid_get_metadata correctly handles different URL formats", { 64 | skip_if_offline() 65 | 66 | url_with_params <- paste0( 67 | "https://ourworldindata.org/grapher/civil-liberties-score-fh?tab=chart" 68 | ) 69 | result <- owid_get_metadata(url = url_with_params) 70 | 71 | expect_type(result, "list") 72 | expect_true(length(result) > 0) 73 | }) 74 | -------------------------------------------------------------------------------- /tests/testthat/test-owid_search.R: -------------------------------------------------------------------------------- 1 | test_data <- data.frame( 2 | title = c( 3 | "Climate Change", 4 | "Carbon Emissions", 5 | "Global Warming", 6 | "Economic Growth" 7 | ), 8 | description = c( 9 | "Data about climate.", 10 | "Emissions data.", 11 | "Warming trends.", 12 | "GDP statistics." 13 | ), 14 | category = factor(c("Environment", "Environment", "Environment", "Economy")), 15 | stringsAsFactors = FALSE 16 | ) 17 | 18 | test_that("Search finds matching rows across all text columns", { 19 | result <- owid_search(test_data, c("climate", "carbon")) 20 | expect_equal(nrow(result), 2) 21 | expect_true(all(result$title %in% c("Climate Change", "Carbon Emissions"))) 22 | }) 23 | 24 | test_that("Search within specific column works", { 25 | result <- owid_search(test_data, c("climate"), columns = c("title")) 26 | expect_equal(nrow(result), 1) 27 | expect_equal(result$title, "Climate Change") 28 | }) 29 | 30 | test_that("Search is case-insensitive", { 31 | result <- owid_search(test_data, c("CLIMATE")) 32 | expect_equal(nrow(result), 1) 33 | expect_equal(result$title, "Climate Change") 34 | }) 35 | 36 | test_that("Search returns empty data frame if no matches", { 37 | result <- owid_search(test_data, c("biodiversity")) 38 | expect_equal(nrow(result), 0) 39 | }) 40 | 41 | test_that("Function throws error for non-data frame input", { 42 | expect_error( 43 | owid_search( 44 | list(a = 1), 45 | c("climate") 46 | ), 47 | "Input 'data' must be a data frame" 48 | ) 49 | }) 50 | 51 | test_that("Function throws error for invalid keywords", { 52 | expect_error( 53 | owid_search(test_data, NULL), 54 | "'keywords' must be a non-empty character vector" 55 | ) 56 | expect_error( 57 | owid_search(test_data, 123), 58 | "'keywords' must be a non-empty character vector" 59 | ) 60 | }) 61 | 62 | test_that("Function throws error for non-existent columns", { 63 | expect_error( 64 | owid_search( 65 | test_data, 66 | c("climate"), 67 | columns = c("nonexistent") 68 | ), 69 | "Some specified columns do not exist in the data" 70 | ) 71 | }) 72 | 73 | test_that("Search works with factor columns", { 74 | result <- owid_search(test_data, c("environment"), columns = c("category")) 75 | expect_equal(nrow(result), 3) 76 | expect_true(all(result$category == "Environment")) 77 | }) 78 | -------------------------------------------------------------------------------- /man/owid_get.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/owid_get.R 3 | \name{owid_get} 4 | \alias{owid_get} 5 | \title{Download data from Our World in Data} 6 | \usage{ 7 | owid_get( 8 | chart_id = NULL, 9 | entities = NULL, 10 | start_date = NULL, 11 | end_date = NULL, 12 | url = NULL, 13 | use_column_short_names = TRUE, 14 | snake_case = TRUE 15 | ) 16 | } 17 | \arguments{ 18 | \item{chart_id}{Character string specifying the chart identifier 19 | (e.g., "life-expectancy"). Not required if \code{url} is provided.} 20 | 21 | \item{entities}{Vector of entity codes (e.g., c("USA", "DEU")). 22 | If NULL, data for all available entities is returned.} 23 | 24 | \item{start_date}{Start date for filtering data. Can be a date string or 25 | a year. If NULL, starts from the earliest available data.} 26 | 27 | \item{end_date}{End date for filtering data. Can be a date string or a year. 28 | If NULL, ends with the latest available data.} 29 | 30 | \item{url}{Direct URL to an OWID dataset. If provided, \code{chart_id} is ignored.} 31 | 32 | \item{use_column_short_names}{Logical. If TRUE (default), uses short column 33 | names.} 34 | 35 | \item{snake_case}{Logical. If TRUE (default), converts column names to 36 | lowercase.} 37 | } 38 | \value{ 39 | A tibble containing the requested OWID data. 40 | } 41 | \description{ 42 | Retrieves data from Our World in Data (OWID) by specifying a chart identifier 43 | or direct URL. Allows filtering by entities and time periods. 44 | } 45 | \examples{ 46 | \dontshow{if (curl::has_internet()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 47 | \donttest{ 48 | # Download a full table 49 | owid_get("life-expectancy") 50 | 51 | # Download a table only for selected entities 52 | owid_get("life-expectancy", c("AUS", "AUT", "GER")) 53 | 54 | # Download a table only for selected time periods 55 | owid_get("life-expectancy", c("USA"), 1970, 1980) 56 | 57 | # Download daily data for selected time periods 58 | owid_get( 59 | "daily-covid-vaccination-doses-per-capita", "DEU", 60 | "2020-12-28", "2020-12-31" 61 | ) 62 | 63 | # Download a table by just providing an URL (with or without .csv) 64 | owid_get( 65 | url = paste0( 66 | "https://ourworldindata.org/grapher/civil-liberties-score-fh", 67 | "?tab=chart&time=earliest..2023&country=ARG~AUS~BWA~CHN~ALB~DEU" 68 | ) 69 | ) 70 | owid_get( 71 | url = paste0( 72 | "https://ourworldindata.org/grapher/civil-liberties-score-fh.csv", 73 | "?tab=chart&time=earliest..2023" 74 | ) 75 | ) 76 | } 77 | \dontshow{\}) # examplesIf} 78 | } 79 | -------------------------------------------------------------------------------- /tests/testthat/test-helpers.R: -------------------------------------------------------------------------------- 1 | test_that("format_date handles different date formats correctly", { 2 | expect_equal(format_date("2020-01-01"), "2020-01-01") 3 | expect_equal(format_date(2020), "2020") 4 | expect_equal(format_date("2020"), "2020") 5 | expect_equal(format_date(NA), NA_character_) 6 | }) 7 | 8 | test_that("prepare_url adds ending to URL with no query parameters", { 9 | url <- "https://ourworldindata.org/grapher/life-expectancy" 10 | result <- prepare_url(url) 11 | expected <- "https://ourworldindata.org/grapher/life-expectancy.csv" 12 | expect_equal(result, expected) 13 | }) 14 | 15 | test_that("prepare_url handles custom endings", { 16 | url <- "https://ourworldindata.org/grapher/life-expectancy" 17 | result <- prepare_url(url, ending = ".metadata.json") 18 | expected <- "https://ourworldindata.org/grapher/life-expectancy.metadata.json" 19 | expect_equal(result, expected) 20 | }) 21 | 22 | test_that("prepare_url preserves URL if it already contains the ending", { 23 | url <- "https://ourworldindata.org/grapher/life-expectancy.csv?country=USA" 24 | result <- prepare_url(url) 25 | expect_equal(result, url) 26 | }) 27 | 28 | test_that("prepare_url adds ending and preserves query parameters", { 29 | url <- "https://ourworldindata.org/grapher/life-expectancy?country=USA" 30 | result <- prepare_url(url) 31 | expected <- paste0( 32 | "https://ourworldindata.org/grapher/life-expectancy.csv", 33 | "?csvType=filtered&country=USA" 34 | ) 35 | expect_equal(result, expected) 36 | }) 37 | 38 | test_that("prepare_url adds csvType=filtered for URLs containing 'time'", { 39 | url <- "https://ourworldindata.org/grapher/time-series-data" 40 | result <- prepare_url(url) 41 | expected <- paste0( 42 | "https://ourworldindata.org/grapher/time-series-data.csv?csvType=filtered" 43 | ) 44 | expect_equal(result, expected) 45 | }) 46 | 47 | test_that("prepare_url adds csvType=filtered for URLs containing 'country'", { 48 | url <- "https://ourworldindata.org/grapher/country-indicators" 49 | result <- prepare_url(url) 50 | expected <- paste( 51 | "https://ourworldindata.org/grapher/country-indicators.csv?csvType=filtered" 52 | ) 53 | expect_equal(result, expected) 54 | }) 55 | 56 | test_that("prepare_url handles case insensitivity for 'time' and 'country'", { 57 | url <- "https://ourworldindata.org/grapher/Time-series" 58 | result <- prepare_url(url) 59 | expected <- paste( 60 | "https://ourworldindata.org/grapher/Time-series.csv?csvType=filtered" 61 | ) 62 | expect_equal(result, expected) 63 | 64 | url <- "https://ourworldindata.org/grapher/COUNTRY-comparison" 65 | result <- prepare_url(url) 66 | expected <- paste( 67 | "https://ourworldindata.org/grapher/COUNTRY-comparison.csv?csvType=filtered" 68 | ) 69 | expect_equal(result, expected) 70 | }) 71 | 72 | test_that("prepare_url doesn't add csvType=filtered twice", { 73 | url <- "https://ourworldindata.org/grapher/country-data?csvType=filtered" 74 | result <- prepare_url(url) 75 | expected <- paste( 76 | "https://ourworldindata.org/grapher/country-data.csv?csvType=filtered" 77 | ) 78 | expect_equal(result, expected) 79 | }) 80 | 81 | test_that("prepare_url handles multiple query parameters", { 82 | url <- "https://ourworldindata.org/grapher/data?country=USA&time=2020" 83 | result <- prepare_url(url) 84 | expected <- paste0( 85 | "https://ourworldindata.org/grapher/data.csv", 86 | "?csvType=filtered&country=USA&time=2020" 87 | ) 88 | expect_equal(result, expected) 89 | }) 90 | 91 | test_that("prepare_url correctly handles URLs with special characters", { 92 | url <- "https://ourworldindata.org/grapher/data-with-spaces%20and%20symbols" 93 | result <- prepare_url(url) 94 | expected <- paste( 95 | "https://ourworldindata.org/grapher/data-with-spaces%20and%20symbols.csv" 96 | ) 97 | expect_equal(result, expected) 98 | }) 99 | 100 | test_that("prepare_url with both 'time' and existing csvType parameter", { 101 | url <- paste( 102 | "https://ourworldindata.org/grapher/data?csvType=filtered&time=2020" 103 | ) 104 | result <- prepare_url(url) 105 | expected <- paste( 106 | "https://ourworldindata.org/grapher/data.csv?csvType=filtered&time=2020" 107 | ) 108 | expect_equal(result, expected) 109 | }) 110 | -------------------------------------------------------------------------------- /R/owid_get.R: -------------------------------------------------------------------------------- 1 | #' Download data from Our World in Data 2 | #' 3 | #' @description 4 | #' Retrieves data from Our World in Data (OWID) by specifying a chart identifier 5 | #' or direct URL. Allows filtering by entities and time periods. 6 | #' 7 | #' @param chart_id Character string specifying the chart identifier 8 | #' (e.g., "life-expectancy"). Not required if `url` is provided. 9 | #' @param entities Vector of entity codes (e.g., c("USA", "DEU")). 10 | #' If NULL, data for all available entities is returned. 11 | #' @param start_date Start date for filtering data. Can be a date string or 12 | #' a year. If NULL, starts from the earliest available data. 13 | #' @param end_date End date for filtering data. Can be a date string or a year. 14 | #' If NULL, ends with the latest available data. 15 | #' @param url Direct URL to an OWID dataset. If provided, `chart_id` is ignored. 16 | #' @param use_column_short_names Logical. If TRUE (default), uses short column 17 | #' names. 18 | #' @param snake_case Logical. If TRUE (default), converts column names to 19 | #' lowercase. 20 | #' 21 | #' @return A tibble containing the requested OWID data. 22 | #' 23 | #' @examplesIf curl::has_internet() 24 | #' \donttest{ 25 | #' # Download a full table 26 | #' owid_get("life-expectancy") 27 | #' 28 | #' # Download a table only for selected entities 29 | #' owid_get("life-expectancy", c("AUS", "AUT", "GER")) 30 | #' 31 | #' # Download a table only for selected time periods 32 | #' owid_get("life-expectancy", c("USA"), 1970, 1980) 33 | #' 34 | #' # Download daily data for selected time periods 35 | #' owid_get( 36 | #' "daily-covid-vaccination-doses-per-capita", "DEU", 37 | #' "2020-12-28", "2020-12-31" 38 | #' ) 39 | #' 40 | #' # Download a table by just providing an URL (with or without .csv) 41 | #' owid_get( 42 | #' url = paste0( 43 | #' "https://ourworldindata.org/grapher/civil-liberties-score-fh", 44 | #' "?tab=chart&time=earliest..2023&country=ARG~AUS~BWA~CHN~ALB~DEU" 45 | #' ) 46 | #' ) 47 | #' owid_get( 48 | #' url = paste0( 49 | #' "https://ourworldindata.org/grapher/civil-liberties-score-fh.csv", 50 | #' "?tab=chart&time=earliest..2023" 51 | #' ) 52 | #' ) 53 | #' } 54 | #' @export 55 | owid_get <- function( 56 | chart_id = NULL, 57 | entities = NULL, 58 | start_date = NULL, 59 | end_date = NULL, 60 | url = NULL, 61 | use_column_short_names = TRUE, 62 | snake_case = TRUE 63 | ) { 64 | if (is.null(url)) { 65 | base_url <- "https://ourworldindata.org/grapher/" 66 | 67 | params <- list( 68 | v = "1", 69 | tab = "chart" 70 | ) 71 | 72 | if (!is.null(entities) || !is.null(start_date) || !is.null(end_date)) { 73 | params$csvType <- "filtered" 74 | } else { 75 | params$csvType <- "full" 76 | } 77 | 78 | if (!is.null(entities)) { 79 | params$country <- paste(entities, collapse = "~") 80 | } 81 | 82 | if (!is.null(start_date) || !is.null(end_date)) { 83 | time_start <- ifelse( 84 | !is.null(start_date), 85 | format_date(start_date), 86 | "earliest" 87 | ) 88 | time_end <- ifelse( 89 | !is.null(end_date), 90 | format_date(end_date), 91 | "latest" 92 | ) 93 | params$time <- paste0(time_start, "..", time_end) 94 | } 95 | 96 | if (use_column_short_names) { 97 | params$useColumnShortNames <- "true" 98 | } 99 | 100 | req <- request(base_url) |> 101 | req_url_path_append(paste0(chart_id, ".csv")) |> 102 | req_url_query(!!!params) 103 | } else { 104 | url_prepared <- prepare_url(url, ".csv") 105 | req <- request(url_prepared) 106 | } 107 | 108 | resp <- perform_request(req, "owid_get") 109 | data_raw <- read_chart_body(resp) 110 | 111 | if (snake_case) { 112 | colnames(data_raw) <- tolower(colnames(data_raw)) 113 | colnames(data_raw)[colnames(data_raw) == "entity"] <- "entity_name" 114 | colnames(data_raw)[colnames(data_raw) == "code"] <- "entity_id" 115 | } 116 | 117 | data <- convert_day_columns(data_raw) 118 | 119 | data 120 | } 121 | 122 | #' @keywords internal 123 | #' @noRd 124 | read_chart_body <- function(resp) { 125 | resp |> 126 | resp_body_string() |> 127 | textConnection() |> 128 | read.csv() |> 129 | tibble::as_tibble() 130 | } 131 | 132 | #' @keywords internal 133 | #' @noRd 134 | convert_day_columns <- function(data) { 135 | if ("day" %in% colnames(data)) { 136 | day_col <- "day" 137 | } else if ("Day" %in% colnames(data)) { 138 | day_col <- "Day" 139 | } else { 140 | day_col <- NULL 141 | } 142 | 143 | if (!is.null(day_col)) { 144 | data[[day_col]] <- as.Date(data[[day_col]]) 145 | } 146 | 147 | data 148 | } 149 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # owidapi 17 | 18 | 19 | [![CRAN status](https://www.r-pkg.org/badges/version/owidapi)](https://cran.r-project.org/package=owidapi) 20 | [![CRAN downloads](https://cranlogs.r-pkg.org/badges/owidapi)](https://cran.r-project.org/package=owidapi) 21 | ![R CMD Check](https://github.com/tidy-intelligence/r-owidapi/actions/workflows/R-CMD-check.yaml/badge.svg) 22 | ![Lint](https://github.com/tidy-intelligence/r-owidapi/actions/workflows/lint.yaml/badge.svg) 23 | [![Codecov test coverage](https://codecov.io/gh/tidy-intelligence/r-owidapi/graph/badge.svg)](https://app.codecov.io/gh/tidy-intelligence/r-owidapi) 24 | 25 | 26 | Retrieve data from the Our World in Data (OWID) [Chart API](https://docs.owid.io/projects/etl/api/). OWID provides public access to more than 5,000 charts focusing on global problems such as poverty, disease, hunger, climate change, war, existential risks, and inequality. 27 | 28 | The package is part of the [econdataverse](https://www.econdataverse.org/) family of packages aimed at helping economists and financial professionals work with sovereign-level economic data. 29 | 30 | > 💡 The ETL Catalog API is currently in beta and relies on internal APIs that change on a regular basis. Once the API is stable, it is planned to be included in this package. 31 | 32 | ## Installation 33 | 34 | You can install `owidapi` from [CRAN](https://cran.r-project.org/package=owidapi) via: 35 | 36 | ``` r 37 | install.packages("owidapi") 38 | ``` 39 | 40 | You can install the development version of `owidapi` from [GitHub](https://github.com/tidy-intelligence/r-owidapi) with: 41 | 42 | ``` r 43 | # install.packages("pak") 44 | pak::pak("tidy-intelligence/r-owidapi") 45 | ``` 46 | 47 | ## Usage 48 | 49 | Load the package: 50 | ```{r} 51 | library(owidapi) 52 | ``` 53 | 54 | Download the full life expectancy dataset: 55 | 56 | ```{r} 57 | owid_get("life-expectancy") 58 | ``` 59 | 60 | Get life expectancy data for Australia, Austria, and Germany: 61 | ```{r} 62 | owid_get("life-expectancy", entities = c("AUS", "AUT", "GER")) 63 | ``` 64 | 65 | Download US life expectancy data from 1970 to 1980: 66 | 67 | ```{r} 68 | owid_get("life-expectancy", entities = "USA", start_date = 1970, end_date = 1980) 69 | ``` 70 | 71 | Get daily COVID-19 vaccination doses per capita for Germany between 2020-12-28 and 2020-12-31: 72 | 73 | ```{r} 74 | owid_get( 75 | "daily-covid-vaccination-doses-per-capita", 76 | entities = "DEU", 77 | start_date = "2020-12-28", 78 | end_date = "2020-12-31" 79 | ) 80 | ``` 81 | 82 | Download data directly using an URL from the website: 83 | 84 | ```{r} 85 | url <- paste0( 86 | "https://ourworldindata.org/grapher/civil-liberties-score-fh", 87 | "?tab=chart&time=earliest..2023&country=ARG~AUS~BWA~CHN~ALB~DEU" 88 | ) 89 | owid_get(url = url) 90 | ``` 91 | 92 | You can get metadata as a list by either provoding the data set name or URL. 93 | ```{r} 94 | metadata <- owid_get_metadata("civil-liberties-score-fh") 95 | metadata_url <- owid_get_metadata(url = url) 96 | str(metadata) 97 | ``` 98 | 99 | The only difference is in the `originalChartUrl` value: 100 | 101 | ```{r} 102 | all.equal(metadata, metadata_url) 103 | ``` 104 | 105 | If you want to fetch the full catalog of available charts: 106 | 107 | ```{r} 108 | catalog <- owid_get_catalog() 109 | catalog 110 | ``` 111 | 112 | To search for keywords in the catalog, you can use the following helper: 113 | 114 | ```{r} 115 | owid_search(catalog, c("climate", "carbon")) 116 | ``` 117 | 118 | There are also a few experimental functions to embed OWID charts. For instance, you can create the HTML to embed a chart: 119 | 120 | ```{r} 121 | owid_embed(url) 122 | ``` 123 | 124 | If you want to render embedded OWID charts in a Shiny app, you can use `owid_output()` and `owid_server()`: 125 | 126 | ```{r, eval = FALSE} 127 | library(shiny) 128 | 129 | ui <- fluidPage( 130 | owid_output("co2_chart") 131 | ) 132 | 133 | server <- function(input, output) { 134 | owid_server( 135 | "co2_chart", 136 | "https://ourworldindata.org/grapher/co2-emissions-per-capita" 137 | ) 138 | } 139 | 140 | shinyApp(ui = ui, server = server) 141 | ``` 142 | 143 | ## Relation to Existing Packages 144 | 145 | The [`owidR`](https://github.com/piersyork/owidR) package is broken since Our World in Data updated the API, has not received a commit since November 2023, and uses a different set of dependencies (e.g., `data.table`, `httr`, `rvest`). 146 | -------------------------------------------------------------------------------- /tests/testthat/test-owid_get.R: -------------------------------------------------------------------------------- 1 | test_that("owid_get basic functionality works", { 2 | skip_if_offline() 3 | 4 | result <- owid_get("life-expectancy") 5 | expect_true(is.data.frame(result)) 6 | expect_true("entity_id" %in% names(result)) 7 | expect_true(any(grepl("year|date", names(result)))) 8 | expect_true(nrow(result) > 0) 9 | }) 10 | 11 | test_that("owid_get filters entities correctly", { 12 | skip_if_offline() 13 | 14 | entities <- c("USA", "DEU") 15 | result <- owid_get("life-expectancy", entities = entities) 16 | expect_true(is.data.frame(result)) 17 | expect_true(all(unique(result$entity_id) %in% entities)) 18 | }) 19 | 20 | test_that("owid_get filters dates correctly", { 21 | skip_if_offline() 22 | 23 | start_year <- 1960 24 | end_year <- 1965 25 | result <- owid_get( 26 | "life-expectancy", 27 | entities = "USA", 28 | start_date = start_year, 29 | end_date = end_year 30 | ) 31 | expect_true(is.data.frame(result)) 32 | expect_true(all(result$year >= start_year & result$year <= end_year)) 33 | 34 | start_date <- "2020-12-28" 35 | end_date <- "2020-12-31" 36 | result <- owid_get( 37 | "daily-covid-vaccination-doses-per-capita", 38 | entities = "DEU", 39 | start_date = start_date, 40 | end_date = end_date 41 | ) 42 | expect_true(is.data.frame(result)) 43 | expect_true( 44 | all( 45 | as.Date(result$day) >= as.Date(start_date) & 46 | as.Date(result$day) <= as.Date(end_date) 47 | ) 48 | ) 49 | }) 50 | 51 | 52 | test_that("owid_get handles URLs directly", { 53 | skip_if_offline() 54 | 55 | url <- "https://ourworldindata.org/grapher/civil-liberties-score-fh?tab=chart" 56 | result <- owid_get(url = url) 57 | expect_true(is.data.frame(result)) 58 | expect_true(nrow(result) > 0) 59 | }) 60 | 61 | test_that("owid_get handles column name formatting", { 62 | skip_if_offline() 63 | 64 | result_uppercase <- owid_get("life-expectancy", snake_case = FALSE) 65 | expect_true(any(grepl("[A-Z]", names(result_uppercase)))) 66 | 67 | result_lowercase <- owid_get("life-expectancy", snake_case = TRUE) 68 | expect_true(all(grepl("^[a-z]", names(result_lowercase)))) 69 | }) 70 | 71 | test_that("owid_get handles column short names", { 72 | skip_if_offline() 73 | 74 | result_short <- owid_get("life-expectancy", use_column_short_names = TRUE) 75 | 76 | result_long <- owid_get("life-expectancy", use_column_short_names = FALSE) 77 | 78 | expect_true(is.data.frame(result_short)) 79 | expect_true(is.data.frame(result_long)) 80 | }) 81 | 82 | test_that("owid_get handles errors appropriately", { 83 | expect_error(owid_get("non-existent-dataset-12345")) 84 | 85 | expect_error(owid_get(url = "https://ourworldindata.org/invalid-url")) 86 | }) 87 | 88 | test_that("convert_day_columns handles lowercase 'day' column", { 89 | test_data <- data.frame( 90 | id = 1:3, 91 | day = c("2023-01-01", "2023-01-02", "2023-01-03"), 92 | value = c(10, 20, 30) 93 | ) 94 | result <- convert_day_columns(test_data) 95 | expect_true(inherits(result$day, "Date")) 96 | expect_equal( 97 | as.character(result$day), 98 | c("2023-01-01", "2023-01-02", "2023-01-03") 99 | ) 100 | expect_equal(result$id, test_data$id) 101 | expect_equal(result$value, test_data$value) 102 | }) 103 | 104 | test_that("convert_day_columns handles uppercase 'Day' column", { 105 | test_data <- data.frame( 106 | id = 1:3, 107 | Day = c("2023-01-01", "2023-01-02", "2023-01-03"), 108 | value = c(10, 20, 30) 109 | ) 110 | result <- convert_day_columns(test_data) 111 | expect_true(inherits(result$Day, "Date")) 112 | expect_equal( 113 | as.character(result$Day), 114 | c("2023-01-01", "2023-01-02", "2023-01-03") 115 | ) 116 | expect_equal(result$id, test_data$id) 117 | expect_equal(result$value, test_data$value) 118 | }) 119 | 120 | test_that("convert_day_columns handles data without day column", { 121 | test_data <- data.frame( 122 | id = 1:3, 123 | value = c(10, 20, 30) 124 | ) 125 | result <- convert_day_columns(test_data) 126 | expect_identical(result, test_data) 127 | }) 128 | 129 | test_that("convert_day_columns handles different date formats", { 130 | test_data <- data.frame( 131 | id = 1:3, 132 | day = c("2023/01/01", "01/02/2023", "2023-01-03"), 133 | stringsAsFactors = FALSE 134 | ) 135 | result <- convert_day_columns(test_data) 136 | expect_true(inherits(result$day, "Date")) 137 | }) 138 | 139 | test_that("convert_day_columns preserves row order", { 140 | test_data <- data.frame( 141 | id = c(3, 1, 2), 142 | day = c("2023-01-03", "2023-01-01", "2023-01-02"), 143 | value = c(30, 10, 20) 144 | ) 145 | result <- convert_day_columns(test_data) 146 | expect_equal(result$value, c(30, 10, 20)) 147 | expect_equal( 148 | as.character(result$day), 149 | c("2023-01-03", "2023-01-01", "2023-01-02") 150 | ) 151 | }) 152 | 153 | test_that("convert_day_columns handles empty dataframe", { 154 | test_data <- data.frame(day = character(0)) 155 | result <- convert_day_columns(test_data) 156 | expect_true(is.data.frame(result)) 157 | expect_true(inherits(result$day, "Date")) 158 | expect_equal(nrow(result), 0) 159 | }) 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # owidapi 5 | 6 | 7 | 8 | [![CRAN 9 | status](https://www.r-pkg.org/badges/version/owidapi)](https://cran.r-project.org/package=owidapi) 10 | [![CRAN 11 | downloads](https://cranlogs.r-pkg.org/badges/owidapi)](https://cran.r-project.org/package=owidapi) 12 | ![R CMD 13 | Check](https://github.com/tidy-intelligence/r-owidapi/actions/workflows/R-CMD-check.yaml/badge.svg) 14 | ![Lint](https://github.com/tidy-intelligence/r-owidapi/actions/workflows/lint.yaml/badge.svg) 15 | [![Codecov test 16 | coverage](https://codecov.io/gh/tidy-intelligence/r-owidapi/graph/badge.svg)](https://app.codecov.io/gh/tidy-intelligence/r-owidapi) 17 | 18 | 19 | Retrieve data from the Our World in Data (OWID) [Chart 20 | API](https://docs.owid.io/projects/etl/api/). OWID provides public 21 | access to more than 5,000 charts focusing on global problems such as 22 | poverty, disease, hunger, climate change, war, existential risks, and 23 | inequality. 24 | 25 | The package is part of the 26 | [econdataverse](https://www.econdataverse.org/) family of packages aimed 27 | at helping economists and financial professionals work with 28 | sovereign-level economic data. 29 | 30 | > 💡 The ETL Catalog API is currently in beta and relies on internal 31 | > APIs that change on a regular basis. Once the API is stable, it is 32 | > planned to be included in this package. 33 | 34 | ## Installation 35 | 36 | You can install `owidapi` from 37 | [CRAN](https://cran.r-project.org/package=owidapi) via: 38 | 39 | ``` r 40 | install.packages("owidapi") 41 | ``` 42 | 43 | You can install the development version of `owidapi` from 44 | [GitHub](https://github.com/tidy-intelligence/r-owidapi) with: 45 | 46 | ``` r 47 | # install.packages("pak") 48 | pak::pak("tidy-intelligence/r-owidapi") 49 | ``` 50 | 51 | ## Usage 52 | 53 | Load the package: 54 | 55 | ``` r 56 | library(owidapi) 57 | ``` 58 | 59 | Download the full life expectancy dataset: 60 | 61 | ``` r 62 | owid_get("life-expectancy") 63 | #> # A tibble: 21,565 × 4 64 | #> entity_name entity_id year life_expectancy_0__sex_total__age_0 65 | #> 66 | #> 1 Afghanistan AFG 1950 28.2 67 | #> 2 Afghanistan AFG 1951 28.6 68 | #> 3 Afghanistan AFG 1952 29.0 69 | #> 4 Afghanistan AFG 1953 29.5 70 | #> 5 Afghanistan AFG 1954 29.7 71 | #> 6 Afghanistan AFG 1955 30.4 72 | #> 7 Afghanistan AFG 1956 30.8 73 | #> 8 Afghanistan AFG 1957 31.3 74 | #> 9 Afghanistan AFG 1958 31.8 75 | #> 10 Afghanistan AFG 1959 32.3 76 | #> # ℹ 21,555 more rows 77 | ``` 78 | 79 | Get life expectancy data for Australia, Austria, and Germany: 80 | 81 | ``` r 82 | owid_get("life-expectancy", entities = c("AUS", "AUT", "GER")) 83 | #> # A tibble: 190 × 4 84 | #> entity_name entity_id year life_expectancy_0__sex_total__age_0 85 | #> 86 | #> 1 Australia AUS 1885 49.0 87 | #> 2 Australia AUS 1895 53.0 88 | #> 3 Australia AUS 1905 57 89 | #> 4 Australia AUS 1921 61.0 90 | #> 5 Australia AUS 1922 62.8 91 | #> 6 Australia AUS 1923 61.7 92 | #> 7 Australia AUS 1924 62.5 93 | #> 8 Australia AUS 1925 63.2 94 | #> 9 Australia AUS 1926 62.9 95 | #> 10 Australia AUS 1927 62.8 96 | #> # ℹ 180 more rows 97 | ``` 98 | 99 | Download US life expectancy data from 1970 to 1980: 100 | 101 | ``` r 102 | owid_get("life-expectancy", entities = "USA", start_date = 1970, end_date = 1980) 103 | #> # A tibble: 11 × 4 104 | #> entity_name entity_id year life_expectancy_0__sex_total__age_0 105 | #> 106 | #> 1 United States USA 1970 70.7 107 | #> 2 United States USA 1971 71.1 108 | #> 3 United States USA 1972 71.2 109 | #> 4 United States USA 1973 71.4 110 | #> 5 United States USA 1974 72.0 111 | #> 6 United States USA 1975 72.5 112 | #> 7 United States USA 1976 72.8 113 | #> 8 United States USA 1977 73.2 114 | #> 9 United States USA 1978 73.4 115 | #> 10 United States USA 1979 73.8 116 | #> 11 United States USA 1980 73.7 117 | ``` 118 | 119 | Get daily COVID-19 vaccination doses per capita for Germany between 120 | 2020-12-28 and 2020-12-31: 121 | 122 | ``` r 123 | owid_get( 124 | "daily-covid-vaccination-doses-per-capita", 125 | entities = "DEU", 126 | start_date = "2020-12-28", 127 | end_date = "2020-12-31" 128 | ) 129 | #> # A tibble: 4 × 4 130 | #> entity_name entity_id day daily_vaccinations_smoothed_per_million 131 | #> 132 | #> 1 Germany DEU 2020-12-28 0.0215 133 | #> 2 Germany DEU 2020-12-29 0.0406 134 | #> 3 Germany DEU 2020-12-30 0.0525 135 | #> 4 Germany DEU 2020-12-31 0.0543 136 | ``` 137 | 138 | Download data directly using an URL from the website: 139 | 140 | ``` r 141 | url <- paste0( 142 | "https://ourworldindata.org/grapher/civil-liberties-score-fh", 143 | "?tab=chart&time=earliest..2023&country=ARG~AUS~BWA~CHN~ALB~DEU" 144 | ) 145 | owid_get(url = url) 146 | #> # A tibble: 126 × 4 147 | #> entity_name entity_id year civil.liberties.score 148 | #> 149 | #> 1 Albania ALB 2003 42 150 | #> 2 Albania ALB 2004 40 151 | #> 3 Albania ALB 2005 38 152 | #> 4 Albania ALB 2006 38 153 | #> 5 Albania ALB 2007 39 154 | #> 6 Albania ALB 2008 40 155 | #> 7 Albania ALB 2009 39 156 | #> 8 Albania ALB 2010 40 157 | #> 9 Albania ALB 2011 39 158 | #> 10 Albania ALB 2012 39 159 | #> # ℹ 116 more rows 160 | ``` 161 | 162 | You can get metadata as a list by either provoding the data set name or 163 | URL. 164 | 165 | ``` r 166 | metadata <- owid_get_metadata("civil-liberties-score-fh") 167 | metadata_url <- owid_get_metadata(url = url) 168 | str(metadata) 169 | #> List of 3 170 | #> $ chart :List of 5 171 | #> ..$ title : chr "Civil liberties score" 172 | #> ..$ subtitle : chr "Based on the estimates and scoring by [Freedom House (2024)](#dod:freedom-house). It captures the extent of fre"| __truncated__ 173 | #> ..$ citation : chr "Freedom House (2025)" 174 | #> ..$ originalChartUrl: chr "https://ourworldindata.org/grapher/civil-liberties-score-fh" 175 | #> ..$ selection :List of 4 176 | #> .. ..$ : chr "Argentina" 177 | #> .. ..$ : chr "Australia" 178 | #> .. ..$ : chr "Botswana" 179 | #> .. ..$ : chr "China" 180 | #> $ columns :List of 1 181 | #> ..$ Civil liberties score:List of 13 182 | #> .. ..$ titleShort : chr "Civil liberties score" 183 | #> .. ..$ titleLong : chr "Civil liberties score" 184 | #> .. ..$ descriptionShort: chr "The variable identifies the fine-grained extent of freedom of expression and association, the rule of law, and "| __truncated__ 185 | #> .. ..$ unit : chr "" 186 | #> .. ..$ timespan : chr "2003-2024" 187 | #> .. ..$ type : chr "Integer" 188 | #> .. ..$ owidVariableId : int 1039982 189 | #> .. ..$ shortName : chr "civlibs_score" 190 | #> .. ..$ lastUpdated : chr "2025-06-02" 191 | #> .. ..$ nextUpdate : chr "2026-06-02" 192 | #> .. ..$ citationShort : chr "Freedom House (2025) – processed by Our World in Data" 193 | #> .. ..$ citationLong : chr "Freedom House (2025) – processed by Our World in Data. “Civil liberties score” [dataset]. Freedom House, “Freed"| __truncated__ 194 | #> .. ..$ fullMetadata : chr "https://api.ourworldindata.org/v1/indicators/1039982.metadata.json" 195 | #> $ dateDownloaded: chr "2025-06-23" 196 | ``` 197 | 198 | The only difference is in the `originalChartUrl` value: 199 | 200 | ``` r 201 | all.equal(metadata, metadata_url) 202 | #> [1] "Length mismatch: comparison on first 3 components" 203 | #> [2] "Component \"chart\": Component \"originalChartUrl\": 1 string mismatch" 204 | ``` 205 | 206 | If you want to fetch the full catalog of available charts: 207 | 208 | ``` r 209 | catalog <- owid_get_catalog() 210 | catalog 211 | #> # A tibble: 5,301 × 19 212 | #> rowid id config_id is_inheritance_enabled created_at updated_at 213 | #> 214 | #> 1 26 8727 01978d86-beee-7595-… TRUE 2025-06-20 2025-06-21 215 | #> 2 25 8726 01978cf4-6363-7356-… FALSE 2025-06-20 2025-06-21 216 | #> 3 24 8725 01978ce6-0056-70d1-… TRUE 2025-06-20 2025-06-21 217 | #> 4 23 8724 01978c7e-4996-7d83-… TRUE 2025-06-20 2025-06-21 218 | #> 5 22 8723 01978878-d5b8-78c0-… FALSE 2025-06-19 2025-06-19 219 | #> 6 21 8722 01978873-eaf5-75c0-… FALSE 2025-06-19 2025-06-19 220 | #> 7 20 8721 019787a1-5f3e-7001-… FALSE 2025-06-19 NA 221 | #> 8 19 8720 019782bc-b4ad-71d2-… TRUE 2025-06-18 NA 222 | #> 9 18 8719 019781f9-87c8-7f47-… FALSE 2025-06-18 2025-06-18 223 | #> 10 17 8718 019781ec-db06-7b85-… TRUE 2025-06-18 2025-06-18 224 | #> # ℹ 5,291 more rows 225 | #> # ℹ 13 more variables: last_edited_at , published_at , 226 | #> # last_edited_by_user_id , published_by_user_id , 227 | #> # is_indexable , config , slug , type , title , 228 | #> # subtitle , note , title_plus_variant , is_published 229 | ``` 230 | 231 | To search for keywords in the catalog, you can use the following helper: 232 | 233 | ``` r 234 | owid_search(catalog, c("climate", "carbon")) 235 | #> # A tibble: 206 × 19 236 | #> rowid id config_id is_inheritance_enabled created_at updated_at 237 | #> 238 | #> 1 5166 8555 0195a9ea-1bb2-7af4-… FALSE 2025-03-18 2025-03-18 239 | #> 2 5131 8509 01950523-a967-7c01-… TRUE 2025-02-14 2025-03-23 240 | #> 3 5130 8508 01950523-a857-72c1-… TRUE 2025-02-14 2025-03-23 241 | #> 4 5105 8472 0194b2a8-e036-7c09-… FALSE 2025-01-29 2025-04-07 242 | #> 5 4900 8266 01933e25-e0bc-70e5-… FALSE 2024-11-18 2024-11-22 243 | #> 6 4869 8222 0192d85f-7e6f-7cbb-… FALSE 2024-10-29 2025-04-07 244 | #> 7 5059 8032 0191c217-8b0d-7612-… TRUE 2024-09-05 2025-05-26 245 | #> 8 5058 8031 0191c217-8a17-7cd8-… TRUE 2024-09-05 2025-05-26 246 | #> 9 5057 8030 0191c217-8916-71c6-… TRUE 2024-09-05 2025-05-26 247 | #> 10 5056 8029 0191c217-8831-7fc9-… TRUE 2024-09-05 2025-05-26 248 | #> # ℹ 196 more rows 249 | #> # ℹ 13 more variables: last_edited_at , published_at , 250 | #> # last_edited_by_user_id , published_by_user_id , 251 | #> # is_indexable , config , slug , type , title , 252 | #> # subtitle , note , title_plus_variant , is_published 253 | ``` 254 | 255 | There are also a few experimental functions to embed OWID charts. For 256 | instance, you can create the HTML to embed a chart: 257 | 258 | ``` r 259 | owid_embed(url) 260 | #> [1] "" 261 | ``` 262 | 263 | If you want to render embedded OWID charts in a Shiny app, you can use 264 | `owid_output()` and `owid_server()`: 265 | 266 | ``` r 267 | library(shiny) 268 | 269 | ui <- fluidPage( 270 | owid_output("co2_chart") 271 | ) 272 | 273 | server <- function(input, output) { 274 | owid_server( 275 | "co2_chart", 276 | "https://ourworldindata.org/grapher/co2-emissions-per-capita" 277 | ) 278 | } 279 | 280 | shinyApp(ui = ui, server = server) 281 | ``` 282 | 283 | ## Relation to Existing Packages 284 | 285 | The [`owidR`](https://github.com/piersyork/owidR) package is broken 286 | since Our World in Data updated the API, has not received a commit since 287 | November 2023, and uses a different set of dependencies (e.g., 288 | `data.table`, `httr`, `rvest`). 289 | --------------------------------------------------------------------------------