├── .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 |
22 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-superseded.svg:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-experimental.svg:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-stable.svg:
--------------------------------------------------------------------------------
1 |
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 | [](https://cran.r-project.org/package=owidapi)
20 | [](https://cran.r-project.org/package=owidapi)
21 | 
22 | 
23 | [](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 | [](https://cran.r-project.org/package=owidapi)
10 | [](https://cran.r-project.org/package=owidapi)
12 | 
14 | 
15 | [](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 |
--------------------------------------------------------------------------------