library(tidyverse)
# ---- Load data ----
df <- read_csv("data/salary.csv", show_col_types = FALSE)
us_state_set <- c(state.name, "District of Columbia")
# Country/place names that look like plausible "countries" after cleaning
# but are actually US states/cities mistakenly entered in the country field.
known_us_places <- c("california", "san francisco", "hartford", "virginia")
# Free-text / garbage tokens that are not country names at all.
known_garbage <- c(
"is", "u.a.", "ua", "uxz", "y", "united y", "currently finance",
"contracts", "policy", "global", "international", "europe", "africa",
"remote", "na", "n/a"
)
# ---- Standardization function ----
# Order of operations matters:
# 1. Match well-known country name variants (regex-based) for the most
# common countries in the data (USA, UK, Canada, NZ, Netherlands, Ireland).
# 2. For anything unmatched, treat the (cleaned/title-cased) country text
# itself as the country name, IF it looks plausible (not a sentence,
# not a known US place, not garbage, not literally a US state name).
# 3. Only as a last resort — if the country text still isn't resolved —
# fall back to inferring "USA" from the `state` column (state is a
# valid US state name). This must come last so that rows with a
# clearly-named foreign country (e.g. country = "France", state =
# "Alaska" from a bad state selection) are NOT overwritten with "USA".
standardize_country <- function(country, state) {
x <- str_trim(country)
x_low <- str_to_lower(x)
out <- rep(NA_character_, length(x))
is_us <- str_detect(
x_low,
"united stat|unite states|unites states|u\\.?\\s?s\\.?a?\\.?$|^usa?$|^us$|^america$|🇺🇸|isa|i\\.s\\."
) | str_detect(x_low, "^u\\.?s\\.?a?\\.?\\s*$")
is_uk <- str_detect(
x_low,
"united kingdom|^uk$|^u\\.k\\.?$|england|scotland|wales|britain|northern ireland"
) & !str_detect(x_low, "new zealand")
is_canada <- str_detect(x_low, "canad")
is_nz <- str_detect(x_low, "new.?zealand|^nz$|aotearoa")
is_neth <- str_detect(x_low, "netherlands")
is_ireland <- str_detect(x_low, "^ireland$|^ireland,|republic of ireland") &
!str_detect(x_low, "northern")
out[is_uk] <- "United Kingdom"
out[is_canada] <- "Canada"
out[is_nz] <- "New Zealand"
out[is_neth] <- "Netherlands"
out[is_ireland] <- "Ireland"
out[is_us] <- "USA"
# Treat remaining unmatched country text as a standalone country name,
# if it looks plausible.
still_na <- is.na(out)
candidate <- x[still_na]
cand_low <- str_to_lower(candidate)
plausible <-
str_length(candidate) <= 25 &
!str_detect(candidate, "[0-9]") &
!str_detect(
cand_low,
"\\bbut\\b|\\bwork\\b|\\bi \\b|\\bthe \\b|\\bfor \\b|remote|govt|government|based|company|contract|bonus|raise|withheld"
) &
!(cand_low %in% known_us_places) &
!(cand_low %in% known_garbage) &
!(str_to_title(cand_low) %in% state.name)
cleaned <- str_to_title(cand_low)
out[still_na][plausible] <- cleaned[plausible]
# Last resort: infer USA from `state` only for rows still unresolved.
still_na2 <- is.na(out)
us_by_state <- still_na2 & state %in% us_state_set
out[us_by_state] <- "USA"
out
}
# ---- Apply standardization ----
df <- df |>
mutate(country_clean = standardize_country(country, state))
# ---- Manual fixes for residual typos / alternate spellings not caught by regex ----
df <- df |>
mutate(country_clean = case_when(
country_clean %in% c("Canda", "Csnada", "Can") ~ "Canada",
country_clean %in% c("Australi", "Australian") ~ "Australia",
country_clean %in% c("Englang", "United Kindom", "Unites Kingdom",
"London") ~ "United Kingdom",
country_clean %in% c("Danmark") ~ "Denmark",
country_clean %in% c("Nederland", "Nl") ~ "Netherlands",
country_clean %in% c("Luxemburg") ~ "Luxembourg",
country_clean %in% c("Hong Konh") ~ "Hong Kong",
country_clean %in% c("Uae", "United Arab Emirates") ~ "United Arab Emirates",
country_clean %in% c("Mainland China") ~ "China",
country_clean %in% c("Brasil") ~ "Brazil",
country_clean %in% c("México") ~ "Mexico",
country_clean %in% c("Panamá") ~ "Panama",
country_clean %in% c("Cote D'ivoire") ~ "Cote d'Ivoire",
str_detect(country_clean, "^Usa") ~ "USA",
country_clean %in% c("Japan, Us Gov Position") ~ "Japan",
TRUE ~ country_clean
))
# ---- Sanity checks ----
n_distinct(df$country_clean) # ~97 distinct standardized values (down from 318)
sum(is.na(df$country_clean)) # rows left NA: unresolvable free text / garbage
df |> count(country_clean, sort = TRUE) |> print(n = 20)
# ---- Save ----
write_csv(df, "data/salary_clean.csv")