targets pipeline

Here’s the general process for building and running this analysis. This is all done with the magical targets package, which orchestrates all the dependencies automatically.

Data cleaning

All the data processing is handled with dataset-specific functions that live in R/funs_data-cleaning.R, which targets then runs as needed. For the sake of transparency, here’s that code:

create_lookups <- function() {
  emlaw_lookup <- tribble(
    ~emlaw, ~emlaw_fct, ~emlaw_cond,
    1, "Declaration in existing legal framework", "Emergency response with legal instruments",
    2, "Declaration in existing legal framework, distinguishes public health emergency", "Emergency response with legal instruments",
    3, "Declaration of disaster where different from emergency", "Emergency response with legal instruments",
    4, "Declaration using other legislation", "Emergency response with legal instruments",
    5, "Emergency response without legal instruments", "Emergency response without legal instruments",
    6, "No emergency response", "No emergency response",
    7, "Other", "Other"
  )

  regime_lookup <- tribble(
    ~v2x_regime, ~regime,
    0, "Closed autocracy",
    1, "Electoral autocracy",
    2, "Electoral democracy",
    3, "Liberal democracy"
  ) %>%
    mutate(regime = fct_inorder(regime, ordered = TRUE))

  type_lookup <- tribble(
    ~type, ~type_clean,
    "A", "Derogated as intended",
    "B", "Derogated; measures not temporary, proportional, or necessary",
    "C", "Emergency declared; no formal derogation",
    "D", "No emergency; no formal derogation",
    "E", "Has not ratified ICCPR"
  ) %>%
    mutate(type_clean_letter = paste0(type, ": ", type_clean)) %>%
    mutate(across(starts_with("type"), ~fct_inorder(., ordered = TRUE)))

  civicus_lookup <- tribble(
    ~value, ~category,
    1, "Closed",
    2, "Repressed",
    3, "Obstructed",
    4, "Narrowed",
    5, "Open"
  ) %>%
    mutate(category = fct_inorder(category, ordered = TRUE))

  return(list(emlaw = emlaw_lookup,
              regime = regime_lookup,
              type = type_lookup,
              civicus = civicus_lookup))
}

load_clean_pandem <- function(path, lookup) {
  pandem_raw <- read_csv(path, col_types = cols()) %>% suppressWarnings()

  pandem_clean <- pandem_raw %>%
    mutate(ndrights_fct = factor(ndrights, levels = 0:1,
                                 labels = c("No violations", "Violations of non-derogable rights"))) %>%
    group_by(country_name) %>%
    mutate(emlaw_min = min(emlaw),
           pandem_max = max(pandem),
           panback_max = max(panback)) %>%
    ungroup() %>%
    left_join(lookup$emlaw, by = "emlaw") %>%
    left_join(rename_with(lookup$emlaw, ~str_c(., "_min"), everything()), by = "emlaw_min") %>%
    left_join(lookup$regime, by = "v2x_regime") %>%
    mutate(emlaw_fct = factor(emlaw_fct, levels = lookup$emlaw$emlaw_fct, ordered = TRUE),
           emlaw_cond = factor(emlaw_cond, levels = unique(lookup$emlaw$emlaw_cond), ordered = TRUE)) %>%
    mutate(emlaw_min_fct = factor(emlaw_fct, levels = lookup$emlaw$emlaw_fct, ordered = TRUE),
           emlaw_min_cond = factor(emlaw_cond, levels = unique(lookup$emlaw$emlaw_cond), ordered = TRUE)) %>%
    mutate(cowcode = countrycode(country_name, "country.name", "cown",
                                 custom_match = c("Serbia" = 345,
                                                  "Hong Kong" = 997)),
           iso3 = countrycode(country_name, "country.name", "iso3c"))

  return(pandem_clean)
}

create_pandem_single <- function(pandem) {
  pandem %>%
    filter(Quarter == "Q4")
}

load_clean_derogations <- function(path) {
  derogations_raw <- read_csv(path, col_types = cols())

  derogations_clean <- derogations_raw %>%
    rename(ratified = `Did they ratify/are they a member? (1=Yes, 0=No, 99= Not possible)`,
           derogate = `Did they officially derogate?`,
           derogation_start = `Derogation start date`,
           derogation_end = `Derogation end date`,
           country_name = Country) %>%
    mutate(country_name = recode(country_name, "Columbia" = "Colombia")) %>%
    mutate(cowcode = countrycode(country_name, "country.name", "cown",
                                 custom_match = c("Serbia" = 345,
                                                  "State of Palestine" = 998,
                                                  "Cook Islands" = NA,
                                                  "Niue" = NA)),
           iso3 = countrycode(country_name, "country.name", "iso3c"))

  return(derogations_clean)
}

load_clean_vdem <- function(path) {
  vdem_raw <- read_rds(path) %>% as_tibble()

  vdem_clean <- vdem_raw %>%
    filter(year > 2015) %>%
    rename(cowcode = COWcode) %>%
    mutate(cowcode = case_when(
      country_name == "Hong Kong" ~ 997,
      country_name == "Palestine/West Bank" ~ 998,
      TRUE ~ cowcode
    )) %>%
    mutate(iso3c = countrycode(country_name, "country.name", "iso3c",
                               custom_match = c("Kosovo" = "XKK",
                                                "Somaliland" = "XSO",
                                                "Zanzibar" = "EAZ"))) %>%
    select(country_name, year, cowcode, iso3c,
           # Civil society stuff
           v2cseeorgs,  # CSO entry and exit
           v2csreprss,  # CSO repression
           v2cscnsult,  # CSO consultation
           v2csprtcpt,  # CSO participatory environment
           v2csgender,  # CSO women's participation
           v2csantimv,  # CSO anti-system movements
           v2xcs_ccsi,  # Core civil society index (entry/exit, repression, participatory env)
           # Human rights and politics
           # Political corruption index (less to more, 0-1) (public sector +
           # executive + legislative + judicial corruption)
           v2x_corr,
           # Rule of law index
           v2x_rule,
           # Rights indexes
           v2x_civlib,  # Civil liberties index
           v2x_clphy,  # Physical violence index
           v2x_clpriv,  # Private civil liberties index
           v2x_clpol,  # Political civil liberties index
           # Democracy
           v2x_polyarchy
    )

  return(vdem_clean)
}

# Civicus Monitor
# We downloaded the standalone embeddable widget
# (https://monitor.civicus.org/widgets/world/) as an HTML file with
# `wget https://monitor.civicus.org/widgets/world/` and saved it as index_2021-03-19.html
#
# We then extracted the COUNTRIES_DATA variable embedded in a <script> tag
# (xpath = /html/body/script[5]), which is JSON-ish, but not quite. jsonlite
# can't parse it for whatever reason, but some online JSON formatter and
# validator could, so we ran it through that and saved the resulting clean file
load_clean_civicus <- function(path, lookup) {
  civicus_raw <- read_json(path) %>% as_tibble() %>% slice(1)

  civicus_clean <- civicus_raw %>%
    pivot_longer(everything(), names_to = "name", values_to = "value") %>%
    mutate(value = map_chr(value, ~.)) %>%
    mutate(value = parse_number(value, na = c("", "NA", "None"))) %>%
    mutate(country_name = countrycode(name, "iso3c", "country.name",
                                      custom_match = c("KOSOVO" = "XKK",
                                                       "SVT" = "VCT")),
           iso3c = countrycode(country_name, "country.name", "iso3c",
                               custom_match = c("XKK" = "Kosovo",
                                                "VCT" = "Saint Vincent and the Grenadines"))) %>%
    left_join(lookup$civicus, by = "value") %>%
    select(-name, -value, -country_name)

  return(civicus_clean)
}

create_pandem_derog <- function(derogations, pandem, vdem, lookups) {
  pandem_derog <- derogations %>%
    left_join(pandem, by = c("country_name", "cowcode", "iso3")) %>%
    left_join(filter(vdem, year == 2019), by = c("country_name", "cowcode")) %>%
    filter(!is.na(X1)) %>%
    mutate(has_derogation_end = case_when(
      is.na(derogation_end) ~ FALSE,
      str_detect(derogation_end, "N/A") ~ FALSE,
      TRUE ~ TRUE
    )) %>%
    mutate(has_emergency_end = emlimit == 1) %>%
    mutate(nonproportional = Type1 > 1 | Type2 == 1 | Type3 > 1) %>%
    mutate(type = case_when(
      derogate == 1 & has_derogation_end & !nonproportional ~ "A",
      derogate == 1 & (!has_derogation_end | nonproportional) ~ "B",
      derogate == 0 & ratified == 1 & emlaw < 5 ~ "C",
      derogate == 0 & ratified == 1 & emlaw >= 5 ~ "D",
      TRUE ~ "E"
    )) %>%
    left_join(lookups$type, by = "type")

  return(pandem_derog)
}

load_world_map <- function(path) {
  world_map <- read_sf(path) %>%
    filter(ISO_A3 != "ATA")

  return(world_map)
}

create_civicus_map_data <- function(civicus, map) {
  map_with_civicus <- map %>%
    # Fix some Natural Earth ISO weirdness
    mutate(ISO_A3 = ifelse(ISO_A3 == "-99", as.character(ISO_A3_EH), as.character(ISO_A3))) %>%
    mutate(ISO_A3 = case_when(
      .$ISO_A3 == "GRL" ~ "DNK",
      .$NAME == "Norway" ~ "NOR",
      .$NAME == "Kosovo" ~ "XKK",
      TRUE ~ ISO_A3
    )) %>%
    left_join(civicus, by = c("ISO_A3" = "iso3c"))

  return(map_with_civicus)
}
LS0tCnRpdGxlOiAiUHJvY2VzcyBhbmQgbWVyZ2UgZGF0YSIKYXV0aG9yOiAiU3VwYXJuYSBDaGF1ZGhyeSBhbmQgQW5kcmV3IEhlaXNzIgpkYXRlOiAiTGFzdCBydW46IGByIGZvcm1hdChTeXMudGltZSgpLCAnJUYnKWAiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZm9sZGluZzogc2hvdwplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShrbml0cikKbGlicmFyeShrYWJsZUV4dHJhKQprbml0X3ByaW50LmRhdGEuZnJhbWUgPC0gZnVuY3Rpb24oeCwgLi4uKSB7CiAgcmVzIDwtIHBhc3RlKGMoJycsICcnLCBrYWJsZV9zdHlsaW5nKGthYmxlKHgsIGJvb2t0YWJzID0gVFJVRSkpKSwgY29sbGFwc2UgPSAnXG4nKQogIGFzaXNfb3V0cHV0KHJlcykKfQoKcmVnaXN0ZXJTM21ldGhvZCgia25pdF9wcmludCIsICJkYXRhLmZyYW1lIiwga25pdF9wcmludC5kYXRhLmZyYW1lKQpyZWdpc3RlclMzbWV0aG9kKCJrbml0X3ByaW50IiwgImdyb3VwZWRfZGYiLCBrbml0X3ByaW50LmRhdGEuZnJhbWUpCgprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLnJldGluYSA9IDMsCiAgICAgICAgICAgICAgICAgICAgICB0aWR5Lm9wdHMgPSBsaXN0KHdpZHRoLmN1dG9mZiA9IDEyMCksICAjIEZvciBjb2RlCiAgICAgICAgICAgICAgICAgICAgICBvcHRpb25zKHdpZHRoID0gOTApLCAgIyBGb3Igb3V0cHV0CiAgICAgICAgICAgICAgICAgICAgICBmaWcuYXNwID0gMC42MTgsIGZpZy53aWR0aCA9IDcsIAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIsIG91dC53aWR0aCA9ICI4NSUiKQoKb3B0aW9ucyhkcGx5ci5zdW1tYXJpc2UuaW5mb3JtID0gRkFMU0UsCiAgICAgICAga25pdHIua2FibGUuTkEgPSAiIikKYGBgCgojIGB0YXJnZXRzYCBwaXBlbGluZQoKSGVyZSdzIHRoZSBnZW5lcmFsIHByb2Nlc3MgZm9yIGJ1aWxkaW5nIGFuZCBydW5uaW5nIHRoaXMgYW5hbHlzaXMuIFRoaXMgaXMgYWxsIGRvbmUgd2l0aCBbdGhlIG1hZ2ljYWwgKipgdGFyZ2V0c2AqKiBwYWNrYWdlXShodHRwczovL2RvY3Mucm9wZW5zY2kub3JnL3RhcmdldHMvKSwgd2hpY2ggb3JjaGVzdHJhdGVzIGFsbCB0aGUgZGVwZW5kZW5jaWVzIGF1dG9tYXRpY2FsbHkuCgpgYGB7ciBzaG93LXRhcmdldHMtcGlwZWxpbmUsIGVjaG89RkFMU0V9CndpdGhyOjp3aXRoX2RpcihoZXJlOjpoZXJlKCksIHsKICB0YXJnZXRzOjp0YXJfZ2xpbXBzZSgpCn0pCmBgYAoKCiMgRGF0YSBjbGVhbmluZwoKQWxsIHRoZSBkYXRhIHByb2Nlc3NpbmcgaXMgaGFuZGxlZCB3aXRoIGRhdGFzZXQtc3BlY2lmaWMgZnVuY3Rpb25zIHRoYXQgbGl2ZSBpbiBgUi9mdW5zX2RhdGEtY2xlYW5pbmcuUmAsIHdoaWNoICoqYHRhcmdldHNgKiogdGhlbiBydW5zIGFzIG5lZWRlZC4gRm9yIHRoZSBzYWtlIG9mIHRyYW5zcGFyZW5jeSwgaGVyZSdzIHRoYXQgY29kZToKCmBgYHtyLCBjb2RlPXhmdW46OnJlYWRfdXRmOChoZXJlOjpoZXJlKCJSIiwgImZ1bnNfZGF0YS1jbGVhbmluZy5SIikpLCBldmFsPUZBTFNFfQpgYGAK