*! wid v1.0.6 world inequality database 26Jan2026 program wid version 13 local wid_cmdline `"`0'"' syntax, [INDicators(string) AReas(string) Years(numlist) Perc(string) AGes(string) POPulation(string) METAdata SCORE_filter(string asis) clear VERBOSE] // ---------------------------------------------------------------------- // // Check if there are already some data in memory // ---------------------------------------------------------------------- // quietly count if (r(N) > 0 & "`clear'" == "") { display as error "no; data in memory would be lost" exit 4 } // ---------------------------------------------------------------------- // // Define verbosity // ---------------------------------------------------------------------- // local verbosity = cond("`verbose'" != "", "verbose", "silent") // ---------------------------------------------------------------------- // // Parse score filters before any API request is made // ---------------------------------------------------------------------- // local wid_cmdline_lower = lower(`"`wid_cmdline'"') local wid_cmdline_nospace = subinstr(`"`wid_cmdline_lower'"', " ", "", .) local score_filter_specified = (strpos(`"`wid_cmdline_nospace'"', "score_filter(") > 0) local row_score_filter = 0 local series_score_filter = 0 local row_score_min = . local row_score_max = . local series_score_min = . local series_score_max = . if (`score_filter_specified') { if (trim(`"`score_filter'"') == "") { display as error "score_filter() cannot be empty" exit 198 } wid_parse_score_filter `"`score_filter'"' local row_score_filter = r(row_score_filter) local series_score_filter = r(series_score_filter) local row_score_min = r(row_score_min) local row_score_max = r(row_score_max) local series_score_min = r(series_score_min) local series_score_max = r(series_score_max) } // ---------------------------------------------------------------------- // // Check the user specified at least some countries and/or indicators // ---------------------------------------------------------------------- // if (inlist("`indicators'", "", "_all") & inlist("`areas'", "", "_all")) { display as error "you need to specify some indicators, some areas, or both" exit 198 } // ---------------------------------------------------------------------- // // Parse the arguments // ---------------------------------------------------------------------- // // If no area specified, use all of them if inlist("`areas'", "_all", "") { local areas "all" } else { // Add a comma between areas foreach a of local areas { if ("`areas_comma'" != "") { local areas_comma "`areas_comma',`a'" } else { local areas_comma "`a'" } } local areas `areas_comma' } // ---------------------------------------------------------------------- // // Retrieve all possible variables for the area(s) // ---------------------------------------------------------------------- // display as text "" display as text "* Get variables associated to your selection...", _continue tempfile allvars clear quietly save "`allvars'", emptyok foreach sixlet in `indicators' { clear if regexm("`sixlet'", "^[a-z][a-z][a-z][a-z][a-z][a-z]$") { clear javacall com.wid.WIDDownloader importCountriesAvailableVariables, args("`areas'" "`sixlet'" "`verbosity'") jars("wid.jar" "json-20180813.jar" "sfi-api.jar") } else if ("`sixlet'" == "_all") { clear javacall com.wid.WIDDownloader importCountriesAvailableVariables, args("`areas'" "all" "`verbosity'") jars("wid.jar" "json-20180813.jar" "sfi-api.jar") } else { display as error "`name' is not a valid six letter code" exit 198 } quietly append using "`allvars'" quietly save "`allvars'", replace } // Check if there are some results quietly use "`allvars'" quietly count if (r(N) == 0) { display as text "DONE" display as text "(no data matching your selection)" exit 0 } // ---------------------------------------------------------------------- // // Only keep variables that the user asked for // ---------------------------------------------------------------------- // // Create a file with all indicators specified, if any clear if !inlist("`indicators'", "", "_all") { local n: word count `indicators' quietly set obs `n' quietly generate variable = "" forvalues i = 1/`n' { local name: word `i' of `indicators' if !regexm("`name'", "^[a-z][a-z][a-z][a-z][a-z][a-z]$") { display as error "`name' is not a valid six letter code" exit 198 } quietly replace variable = "`name'" in `i' } quietly duplicates drop tempfile list_indicators quietly save "`list_indicators'" } // Create a list with all the years specified, if any clear if !inlist("`years'", "", "_all") { local n: word count `years' quietly set obs `n' quietly generate year = . forvalue i = 1/`n' { local year: word `i' of `years' quietly replace year = `year' in `i' } quietly duplicates drop tempfile list_years quietly save "`list_years'" } // Create a list with all percentiles specified, if any clear if !inlist("`perc'", "", "_all") { local n: word count `perc' quietly set obs `n' quietly generate percentile = "" forvalues i = 1/`n' { local p: word `i' of `perc' if !(regexm("`p'", "^p[\.0-9]+$") | regexm("`p'", "^p[\.0-9]+p[\.0-9]+$")) { display as error "`p' is not a valid percentile or percentile group" exit 198 } quietly replace percentile = "`p'" in `i' } quietly duplicates drop tempfile list_perc quietly save "`list_perc'" } // Create a list with all ages specified, if any clear if !inlist("`ages'", "", "_all") { local n: word count `ages' quietly set obs `n' quietly generate age = "" forvalues i = 1/`n' { local a: word `i' of `ages' if !regexm("`a'", "^[0-9][0-9][0-9]$") { display as error "`a' is not a valid age code" exit 198 } quietly replace age = "`a'" in `i' } quietly duplicates drop tempfile list_ages quietly save "`list_ages'" } // Create a list with all populations specified, if any clear if !inlist("`population'", "", "_all") { local n: word count `population' quietly set obs `n' quietly generate pop = "" forvalues i = 1/`n' { local pop: word `i' of `population' if !inlist("`pop'", "i", "j", "m", "f", "t", "e") { display as error "`pop' is not a valid population code" exit 198 } quietly replace pop = "`pop'" in `i' } quietly duplicates drop tempfile list_population quietly save "`list_population'" } // From the list of all indicators, only keep the one we are interested in quietly use "`allvars'", clear if !inlist("`indicators'", "", "_all") { quietly merge n:1 variable using "`list_indicators'", nogenerate keep(match) } if !inlist("`perc'", "", "_all") { quietly merge n:1 percentile using "`list_perc'", nogenerate keep(match) } if !inlist("`ages'", "", "_all") { quietly merge n:1 age using "`list_ages'", nogenerate keep(match) } if !inlist("`population'", "", "_all") { quietly merge n:1 pop using "`list_population'", nogenerate keep(match) } // Check that there are some data left quietly count if (r(N) == 0) { display as text "DONE" display as text "(no data matching you selection)" exit 0 } // ---------------------------------------------------------------------- // // Display how many variables remain // ---------------------------------------------------------------------- // quietly tab variable local nb_variable = r(r) if (`nb_variable' > 1) { local plural_variable "s" } quietly tab country local nb_country = r(r) if (`nb_country' > 1) { local plural_country "s" } quietly tab percentile local nb_percentile = r(r) if (`nb_percentile' > 1) { local plural_percentile "s" } quietly tab age local nb_age = r(r) if (`nb_age' > 1) { local plural_age "ies" } else { local plural_age "y" } quietly tab pop local nb_pop = r(r) if (`nb_pop' > 1) { local plural_pop "ies" } else { local plural_pop "y" } display as text "DONE" display as text "(found `nb_variable' variable`plural_variable'", _continue display as text "for `nb_country' area`plural_country',", _continue display as text "`nb_percentile' percentile`plural_percentile',", _continue display as text "`nb_age' age categor`plural_age',", _continue display as text "`nb_pop' population categor`plural_pop')" display as text "" // ---------------------------------------------------------------------- // // Retrieve the data from the API // ---------------------------------------------------------------------- // display as text "* Downloading the data",, _continue // Generate the variable names to be used in the API quietly generate data_code = variable + "_" + percentile + "_" + age + "_" + pop // Divide the data in smaller chunks before making the request: group by // variable and percentiles sort variable percentile age pop country quietly egen grp = group(variable percentile age pop) quietly generate chunk = round(grp/10) quietly drop grp tempfile codes output_data quietly save "`codes'" display "" display "" display "{c LT} 0% {hline 3}{c +}{hline 3} 20% {hline 3}{c +}{hline 3} 40% {hline 3}{c +}{hline 3} 60% {hline 3}{c +}{hline 3} 80% {hline 3}{c +}{hline 3} 100% {c RT}" in smcl quietly tabulate chunk local nchunks = r(r) quietly levelsof chunk, local(chunk_list) local progress = 1 foreach c of local chunk_list { quietly use "`codes'" quietly levelsof data_code if (chunk == `c'), separate(",") local(variables_list) clean quietly levelsof country if (chunk == `c'), separate(",") local(areas_list) clean clear javacall com.wid.WIDDownloader importCountriesVariables, args("`areas_list'" "`variables_list'" "`verbosity'") jars("wid.jar" "json-20180813.jar" "sfi-api.jar") quietly drop if missing(value) if (`c' != 0) { quietly append using "`output_data'" } quietly save "`output_data'", replace while (`c'/`nchunks'*68 > `progress') { di "=",, _continue local progress = `progress' + 1 } } while (`progress' < 68) { di "=",, _continue local progress = `progress' + 1 } display "" display "" if ("`list_years'" != "") { quietly merge n:1 year using "`list_years'", nogenerate keep(match) } quietly count if (r(N) == 0) { display as text "(no data matching you selection)" exit 0 } quietly duplicates drop country variable age pop percentile year, force quietly replace variable = variable + age + pop capture confirm variable row_score if (_rc) { quietly generate double row_score = . } if (`row_score_filter') { quietly drop if missing(row_score) | row_score < `row_score_min' | row_score > `row_score_max' } order country variable percentile year value quietly save "`output_data'", replace // ---------------------------------------------------------------------- // // Retrieve the metadata, if requested or needed for series_score filtering // ---------------------------------------------------------------------- // if ("`metadata'" != "" | `series_score_filter') { if ("`metadata'" != "") { display as text "* Download the metadata...", _continue } else { display as text "* Download the metadata for score filtering...", _continue } local metadata_mode "compact" if ("`metadata'" == "") { local metadata_mode "score_only" } // Metadata are percentile-invariant, but vary by country, indicator, // age category, and population category. tempfile output_metadata quietly use "`codes'", clear drop chunk quietly duplicates drop variable country age pop, force quietly generate chunk = round(_n/50) quietly save "`codes'", replace quietly levelsof chunk, local(chunk_list) local first 1 foreach c of local chunk_list { quietly use "`codes'", clear quietly levelsof data_code if (chunk == `c'), separate(",") local(variables_list) clean quietly levelsof country if (chunk == `c'), separate(",") local(areas_list) clean clear javacall com.wid.WIDDownloader importCountriesVariablesMetadata, args("`areas_list'" "`variables_list'" "`verbosity'" "`metadata_mode'") jars("wid.jar" "json-20180813.jar" "sfi-api.jar") // Pass if the dataset is empty (can happen with metadata) quietly count if (r(N) == 0) { continue } capture drop percentile quietly replace variable = variable + agecode + popcode quietly duplicates drop variable country, force if (`first' == 0) { quietly append using "`output_metadata'" } local first 0 quietly save "`output_metadata'", replace } if (`first' == 0) { quietly use "`output_metadata'", clear display as text "DONE" capture drop quality data_quality imputation quietly duplicates drop variable country, force quietly save "`output_metadata'", replace if ("`metadata'" != "") { use "`output_data'", clear capture drop age pop quietly merge n:1 country variable using "`output_metadata'", nogenerate keep(master match) } else { quietly keep country variable series_score quietly save "`output_metadata'", replace use "`output_data'", clear quietly merge n:1 country variable using "`output_metadata'", nogenerate keep(master match) } } else { display as text "DONE (no metadata found for requested data)" use "`output_data'", clear if ("`metadata'" != "") { capture drop age pop } if (`series_score_filter') { quietly generate double series_score = . } } } if (`series_score_filter') { capture confirm variable series_score if (_rc) { quietly generate double series_score = . } quietly drop if missing(series_score) | series_score < `series_score_min' | series_score > `series_score_max' } capture drop quality data_quality imputation if ("`metadata'" != "") { foreach v in countryname shortname shortdes pop age source method { capture confirm variable `v' if (_rc) { quietly generate str1 `v' = "" } } foreach v in row_score series_score { capture confirm variable `v' if (_rc) { quietly generate double `v' = . } } quietly keep country countryname variable percentile year value shortname shortdes pop age source row_score series_score method order country countryname variable percentile year value shortname shortdes pop age source row_score series_score method } else { if (!`row_score_filter') { capture drop row_score } if (!`series_score_filter') { capture drop series_score } local score_columns if (`row_score_filter') { local score_columns `score_columns' row_score } if (`series_score_filter') { local score_columns `score_columns' series_score } capture order country variable percentile year value age pop `score_columns' } // Saves memory quietly compress sort country variable percentile year end program wid_parse_score_filter, rclass version 13 args filter local filter = lower(trim(`"`filter'"')) if (`"`filter'"' == "") { display as error "score_filter() cannot be empty" exit 198 } local row_used = 0 local series_used = 0 local row_min = . local row_max = . local series_min = . local series_max = . if (strpos(`"`filter'"', "row_score") == 0 & strpos(`"`filter'"', "series_score") == 0) { wid_parse_score_bounds `"`filter'"' local row_used = 1 local row_min = r(min) local row_max = r(max) } else { local work = subinstr(`"`filter'"', char(9), " ", .) local work = subinstr(`"`work'"', "row_score", ";row_score", .) local work = subinstr(`"`work'"', "series_score", ";series_score", .) local rest `"`work'"' while (`"`rest'"' != "") { gettoken segment rest : rest, parse(";") local segment = trim(`"`segment'"') if (`"`segment'"' == "" | `"`segment'"' == ";") { continue } local junk = subinstr(`"`segment'"', "{", "", .) local junk = subinstr(`"`junk'"', "}", "", .) local junk = subinstr(`"`junk'"', ",", "", .) if (trim(`"`junk'"') == "") { continue } if (substr(`"`segment'"', 1, 9) == "row_score") { local name row_score local values = substr(`"`segment'"', 10, .) } else if (substr(`"`segment'"', 1, 12) == "series_score") { local name series_score local values = substr(`"`segment'"', 13, .) } else { display as error "score_filter() only supports row_score and series_score" exit 198 } local separator = substr(`"`values'"', 1, 1) if (`"`separator'"' != "" & !inlist(`"`separator'"', " ", "=", ":", "{", "[")) { display as error "invalid score_filter() score name" exit 198 } wid_parse_score_bounds `"`values'"' if ("`name'" == "row_score") { if (`row_used') { display as error "row_score specified more than once in score_filter()" exit 198 } local row_used = 1 local row_min = r(min) local row_max = r(max) } else { if (`series_used') { display as error "series_score specified more than once in score_filter()" exit 198 } local series_used = 1 local series_min = r(min) local series_max = r(max) } } } if (!`row_used' & !`series_used') { display as error "score_filter() cannot be empty" exit 198 } return scalar row_score_filter = `row_used' return scalar series_score_filter = `series_used' return scalar row_score_min = `row_min' return scalar row_score_max = `row_max' return scalar series_score_min = `series_min' return scalar series_score_max = `series_max' end program wid_parse_score_bounds, rclass version 13 args raw local spec = lower(trim(`"`raw'"')) local first = substr(`"`spec'"', 1, 1) if (inlist(`"`first'"', "=", ":")) { local spec = trim(substr(`"`spec'"', 2, .)) } if (`"`spec'"' == "") { display as error "score_filter() bounds cannot be empty" exit 198 } local min_set = 0 local max_set = 0 local min = . local max = . if (strpos(`"`spec'"', "min") > 0 | strpos(`"`spec'"', "max") > 0) { local clean `"`spec'"' foreach ch in "{" "}" "[" "]" "," ":" "=" "/" { local clean = subinstr(`"`clean'"', "`ch'", " ", .) } local clean = itrim(trim(`"`clean'"')) while (`"`clean'"' != "") { gettoken key clean : clean local key = trim(`"`key'"') if (inlist(`"`key'"', "min", "minimum")) { if (`min_set') { display as error "minimum score specified more than once" exit 198 } gettoken token clean : clean wid_parse_score_number `"`token'"' local min = r(value) local min_set = 1 } else if (inlist(`"`key'"', "max", "maximum")) { if (`max_set') { display as error "maximum score specified more than once" exit 198 } gettoken token clean : clean wid_parse_score_number `"`token'"' local max = r(value) local max_set = 1 } else { display as error "invalid score_filter() bounds" exit 198 } } if (!`min_set' & !`max_set') { display as error "score_filter() bounds cannot be empty" exit 198 } if (!`min_set') { local min = 0 } if (!`max_set') { local max = 5 } } else { local clean `"`spec'"' foreach ch in "{" "}" "[" "]" "," "/" { local clean = subinstr(`"`clean'"', "`ch'", " ", .) } local clean = itrim(trim(`"`clean'"')) local n : word count `clean' if (!inlist(`n', 1, 2)) { display as error "score_filter() bounds must be one number, two numbers, or min/max bounds" exit 198 } gettoken token clean : clean wid_parse_score_number `"`token'"' local min = r(value) if (`n' == 1) { local max = 5 } else { gettoken token clean : clean wid_parse_score_number `"`token'"' local max = r(value) } } if (`min' < 0 | `min' > 5 | `max' < 0 | `max' > 5) { display as error "score_filter() bounds must be between 0 and 5" exit 198 } if (`min' > `max') { display as error "score_filter() minimum cannot exceed maximum" exit 198 } return scalar min = `min' return scalar max = `max' end program wid_parse_score_number, rclass version 13 args token local token = trim(`"`token'"') local value = real(`"`token'"') if (`"`token'"' == "" | missing(`value')) { display as error "score_filter() bounds must be numeric" exit 198 } return scalar value = `value' end