When to use incomeLato, and when not to

The Latinobarómetro asks about political attitudes and behavior but has no income measure that is comparable across countries and waves. incomeLato fills that gap with a probability: for each respondent, how likely they are to be in the bottom 50%, the middle 40% or the top 10% of their own country in that survey year. Those probabilities are good at one job and bad at another, and this vignette is about telling the two apart.

Good use: aggregate composition

Use the probabilities as continuous weights to describe groups. The question “what share of the poorest half of Brazilians supported party X in 2018?” is answered by a weighted mean of the party indicator, with weight prob_bottom50 * survey_weight. In LAPOP, which records household income, the same shares can be computed twice, weighting respondents once by their observed income group and once by the predicted probabilities. With the packaged model, the two agree closely (357 country-wave-party comparisons, 15 countries, 2008–2018):

  • a mean absolute difference of 0.71 percentage points in the bottom 50’s support share;
  • a Spearman correlation of 0.97–0.99 between the two sets of shares, across the three income groups;
  • the party most supported by the bottom 50% is the same in 97% of 66 country-waves.

Bad use: classifying individuals

At the level of one respondent the model is weak. The Spearman correlation between a person’s predicted probability and their observed income group, taken from the household income bracket they report to LAPOP, is 0.44 for the bottom 50, 0.25 for the middle 40 and 0.35 for the top 10. Five predictors (sex, age, education, car and washing machine ownership) cannot place an individual reliably. So:

  • do not turn the probabilities into a label (prob_bottom50 > 0.5 → “poor”) and treat that label as observed income;
  • do not use the probabilities as an individual-level covariate whose coefficient you interpret as the effect of income.

Comparing waves: yes. The size of the bottom 50% over time: no

Comparing groups across waves is a good use. Support for party X among the bottom 50% in each wave, 1995–2023, is the same weighted mean computed wave by wave: the probabilities describe the composition of the group in every wave, and those compositions can be compared.

What cannot be read over time is the weight itself. mean(prob_bottom50) per country-wave is not an estimate of how large the bottom half is, and its change over time is not a trend: the aggregate margin drifts for reasons internal to the model. The column veredito_deriva_temporal gives a per-country drift label; its magnitude categories come from the leave-one-wave-out validation of an earlier model vintage (v1.1). flag_margin marks the countries where the margin did not validate cleanly (NA for Mexico, where it was never assessed). It remains a justified caution, because the direction of the drift persists under the packaged model.

Validation in detail

The package applies the v1.5 model with the v1.4 recalibrators (Mexico uncalibrated). The table compares that packaged recipe with the two models without recalibration, against LAPOP observed income. For the recipe, the check is in-sample for the recalibrators, which were fitted on this same LAPOP data; LAPOP never enters the training of the model itself.

Packaged recipe v1.5, without recalibration v1.4, without recalibration
Country-waves (countries) 66 (15) 66 (15) 62 (14)
Share of the bottom 50%, predicted minus observed: median absolute difference 2.3 pp 7.3 pp 8.0 pp
Same, largest absolute difference 11.6 pp 25.9 pp 26.8 pp
Individual Spearman, bottom 50 / middle 40 / top 10 0.44 / 0.25 / 0.35 0.42 / 0.23 / 0.35 0.42 / 0.21 / 0.35
Party support, mean absolute difference, bottom 50 0.71 pp 0.67 pp 0.68 pp
Party support, Spearman across the three groups 0.97–0.99 0.97–0.99 0.97–0.99
Most-supported party of the bottom 50% matches 97% 97% 98%

Recalibration fixes the level of each income group. It barely changes the composition of party support, because support within a group is a ratio of weighted sums, and a shift in the level of the weights largely cancels out.

Under the recipe, 11 of 15 countries have a median difference within ±1 pp; Ecuador (−2.1), Uruguay (−2.5) and Mexico (−2.7, no recalibrator) fall outside ±2 pp. Single country-waves vary more: the largest difference is above 2 pp in 14 of the 15 countries.

A minimal example

The example follows the normal path: a wave written to a file, then income_lato(). The wave is synthetic, generated by the package; no real respondent is involved. supports_democracy is simulated (1 = democracy is preferable to any other kind of government), with a probability that rises with predicted income, so that the table shows a gradient.

library(incomeLato)
sim <- simulate_latinobarometro_wave(2011, n = 200)
arquivo <- file.path(tempdir(), "Latinobarometro_2011_sim.dta")
haven::write_dta(sim, arquivo)

res <- income_lato(arquivo)
#> Warning: USE: prob_bottom50, prob_middle40 and prob_top10 are continuous
#> weights for describing the composition of groups (e.g., party support among the
#> bottom 50%) within a wave and across waves. DO NOT classify individual
#> respondents by hard cutoff. DO NOT read mean(prob_bottom50) by country-wave as
#> the size of the bottom half, nor its change over time as a trend: the aggregate
#> margin drifts for reasons internal to the model. See attr(result,
#> 'recommended_use') and attr(result, 'prohibited_use'), and the columns
#> veredito_deriva_temporal and flag_margin for the per-country drift verdict.
#> incomeLato -- run summary
#>   waves detected: 2011
#>   waves excluded by default: none
#>   respondents with a prediction: 2436 of 3400
#>   countries with no prediction:
#>     GTM: country_not_in_15
#>     VEN: venezuela_architectural_quarantine
res <- res[res$elegivel, ]   # scored respondents only

# simulated outcome, rising with predicted income
set.seed(1)
p <- 0.40 + 0.25 * res$prob_middle40 + 1.2 * res$prob_top10
res$supports_democracy <- rbinom(nrow(res), 1, pmin(p, 1))

# share supporting democracy in each income group, by country:
# each respondent weighted by prob_<group> times the survey weight
grupos <- c("bottom50", "middle40", "top10")
share <- sapply(grupos, function(g) {
  w <- res[[paste0("prob_", g)]] * res$weight
  tapply(w * res$supports_democracy, res$iso3, sum) / tapply(w, res$iso3, sum)
})
round(100 * share, 1)
#>     bottom50 middle40 top10
#> ARG     51.2     59.8  77.2
#> BOL     64.6     74.3  90.6
#> BRA     54.1     66.5  84.4
#> CHL     49.6     57.9  70.2
#> COL     57.6     66.7  82.2
#> CRI     56.1     66.7  83.4
#> ECU     56.4     65.3  78.8
#> HND     70.0     77.5  90.7
#> MEX     58.8     69.3  82.8
#> NIC     74.1     79.0  91.7
#> PAN     58.4     66.7  81.8
#> PER     61.7     73.6  89.6
#> PRY     66.5     74.3  88.7
#> SLV     66.6     78.2  90.1
#> URY     57.1     66.2  78.3

With real data, the variable comes from the Latinobarómetro’s standard question on preference for democracy. Its name changes between waves, so look it up in each wave’s questionnaire. income_lato() does not carry over the other columns of your file; bring the variable in with raw$variable[res$respondent_row_id], where raw is the wave read with haven::read_dta() and respondent_row_id is the row’s position in the file.