
Adding OEWS Wage and Employment Context
Source:vignettes/oews-wage-context.Rmd
oews-wage-context.RmdO*NET tells you what occupations do: their tasks, abilities, skills, knowledge, and work contexts. The Bureau of Labor Statistics Occupational Employment and Wage Statistics (OEWS) program tells you how large those occupations are in the labor market and what they pay. Joining the two lets you move from an occupation-level descriptor table to a labor-market-weighted question.
This walkthrough asks: among occupations in a small O*NET ability panel, what does a national employment-weighted ability score look like, and what changes if we use a custom PUMS-style population instead?
Read OEWS Estimates
onet_oews_national() normalizes BLS OEWS files into
snake_case columns and parses formatted employment and wage fields into
numeric values.
BLS sometimes rejects automated ZIP downloads even when the same OEWS
URL works in a browser. In interactive sessions,
onet_oews_national() opens the official BLS URL in your
browser, waits for the matching ZIP to appear in your Downloads folder,
validates it, and copies it into the package cache. You can set
options(onet2r.oews_download_dir = "path/to/downloads") for
a different folder or pass the file explicitly with path,
which is what this article does so it can build without network access.
Passing path is also the fallback if BLS changes the OEWS
URL or file name.
oews <- onet_oews_national(year = 2023, path = sample_oews)
oews |>
select(occ_code, occ_title, tot_emp, a_median, h_median, a_median_topcoded) |>
knitr::kable(digits = 3, align = "l")| occ_code | occ_title | tot_emp | a_median | h_median | a_median_topcoded |
|---|---|---|---|---|---|
| 00-0000 | All Occupations | 151853870 | 48060 | 23.11 | FALSE |
| 15-0000 | Computer and Mathematical Occupations | 5199210 | 104420 | 50.20 | FALSE |
| 15-1252 | Software Developers | 1847900 | 133080 | 63.98 | FALSE |
| 29-1141 | Registered Nurses | 3175400 | 93070 | 44.75 | FALSE |
| 11-1011 | Chief Executives | 211230 | NA | NA | TRUE |
The sample includes BLS hierarchy rows and a top-coded wage. The
parser keeps the o_group column, parses numeric fields, and
adds flag columns when a wage is top-coded or suppressed.
Build a Reference-SOC Weight Panel
OEWS uses 6-digit SOC codes. O*NET archive tables use 8-digit O*NET-SOC detail codes. The weight-panel helper records the source and reference taxonomy so the join is auditable.
oews_weights <- onet_weight_panel_oews(oews, year = 2024)
#> Dropped 2 OEWS aggregate rows; keeping "detailed" occupations.
oews_weights |>
knitr::kable(digits = 3, align = "l")| reference_soc_code | year | employment | weight_share | source | source_taxonomy | reference_taxonomy |
|---|---|---|---|---|---|---|
| 11-1011 | 2024 | 211230 | 0.040 | OEWS | 2018 SOC | 2018 SOC |
| 15-1252 | 2024 | 1847900 | 0.353 | OEWS | 2018 SOC | 2018 SOC |
| 29-1141 | 2024 | 3175400 | 0.607 | OEWS | 2018 SOC | 2018 SOC |
Get O*NET Occupation Scores from an Archive
The occupation scores below come from
onet_archive_read(), not a hand-built example table. We use
Oral Comprehension as a concrete descriptor because it is available in
the bundled fixture.
abilities <- onet_archive_read(
"30.3",
"Abilities",
path = archive_303,
release_date = "2026-05-01"
)
oral_scores <- abilities |>
filter(element_id == "1.A.1.a.1") |>
transmute(
onet_soc_code,
title,
measure_score = data_value
)
oral_scores |>
knitr::kable(digits = 3, align = "l")| onet_soc_code | title | measure_score |
|---|---|---|
| 15-1252.00 | NA | 4.35 |
| 29-1141.00 | NA | 4.71 |
| 11-1011.00 | NA | 4.50 |
| 41-1011.00 | NA | 4.15 |
Aggregate with OEWS Employment
oral_oews <- onet_measure_aggregate(
oral_scores,
oews_weights,
measure_id = "oral_comprehension_fixture"
)
oral_oews |>
select(-coverage, -provenance) |>
knitr::kable(digits = 3, align = "l")| measure_id | aggregate | total_employment | covered_employment | employment_coverage_share | n_occupations | n_reference_soc |
|---|---|---|---|---|---|---|
| oral_comprehension_fixture | 4.574 | 5234530 | 5234530 | 1 | 4 | 4 |
onet_provenance(oral_oews) |>
knitr::kable(digits = 3, align = "l")| measure_id | measure_release | weight_source | weight_year | source_taxonomy | reference_taxonomy | bridge_used | crosswalk_path |
|---|---|---|---|---|---|---|---|
| oral_comprehension_fixture | NA | OEWS | 2024 | 2018 SOC | 2018 SOC | FALSE | 2018 SOC -> 2018 SOC |
The aggregate is the employment-weighted score for occupations covered by both the O*NET fixture and the OEWS sample. Coverage tells you how much of the weight panel was included.
onet_coverage(oral_oews) |>
knitr::kable(digits = 3, align = "l")| measure_id | total_employment | covered_employment | employment_coverage_share | n_occupations | n_reference_soc |
|---|---|---|---|---|---|
| oral_comprehension_fixture | 5234530 | 5234530 | 1 | 4 | 4 |
Inspect Occupation Contributions
contributions <- oral_scores |>
mutate(reference_soc_code = sub("\\.\\d{2}$", "", onet_soc_code)) |>
summarise(
title = paste(sort(unique(title)), collapse = "; "),
measure_score = mean(measure_score),
.by = "reference_soc_code"
) |>
inner_join(oews_weights, by = join_by(reference_soc_code), relationship = "many-to-one") |>
mutate(weighted_score = measure_score * employment) |>
arrange(desc(weighted_score)) |>
select(title, reference_soc_code, measure_score, employment, weight_share, weighted_score)
contributions |>
knitr::kable(digits = 3, align = "l")| title | reference_soc_code | measure_score | employment | weight_share | weighted_score |
|---|---|---|---|---|---|
| 29-1141 | 4.71 | 3175400 | 0.607 | 14956134 | |
| 15-1252 | 4.35 | 1847900 | 0.353 | 8038365 | |
| 11-1011 | 4.50 | 211230 | 0.040 | 950535 |
The contribution is already the weighted_score column:
occupation score times employment. In this fixture a separate chart
would only redraw the table.
Compare OEWS with a PUMS-Style Weight Panel
OEWS is the right default for official occupation employment and wage context. PUMS-derived weights are useful when the target population is a custom sample, geography, or demographic cell.
pums <- tibble::tibble(
SOCP = c("151252", "151252", "291141", "291141", "111011"),
PWGTP = c(120, 80, 90, 110, 20),
sex = c("F", "M", "F", "M", "F")
)
pums_weights <- onet_weight_panel_pums(
pums,
year = 2022,
group = "sex"
)
pums_weights |>
knitr::kable(digits = 3, align = "l")| reference_soc_code | sex | year | employment | weight_share | source | source_taxonomy | reference_taxonomy |
|---|---|---|---|---|---|---|---|
| 11-1011 | F | 2022 | 20 | 0.087 | PUMS | 2018 SOC | 2018 SOC |
| 15-1252 | F | 2022 | 120 | 0.522 | PUMS | 2018 SOC | 2018 SOC |
| 15-1252 | M | 2022 | 80 | 0.421 | PUMS | 2018 SOC | 2018 SOC |
| 29-1141 | F | 2022 | 90 | 0.391 | PUMS | 2018 SOC | 2018 SOC |
| 29-1141 | M | 2022 | 110 | 0.579 | PUMS | 2018 SOC | 2018 SOC |
Because this PUMS-style panel has cells, pick one cell before aggregating.
For real ACS PUMS, filter to the employment universe before building
weights. The common starting point is employed civilians, often
ESR %in% c(1, 2) and age 16 or older. SOCP is
the SOC field. OCCP is a Census occupation recode and needs
a separate crosswalk before it can be used with O*NET or OEWS.
pums <- tidycensus::get_pums(
variables = c("SOCP", "PWGTP", "ESR", "AGEP"),
state = "WA",
year = 2023,
survey = "acs1"
) |>
filter(ESR %in% c("1", "2"), AGEP >= 16)
pums_weights <- onet_weight_panel_pums(pums, year = 2023)
oral_pums_f <- onet_measure_aggregate(
oral_scores,
pums_weights,
measure_id = "oral_comprehension_fixture",
cell = list(sex = "F")
)
tibble::tibble(
weight_source = c("OEWS national", "PUMS-style F cell"),
aggregate = c(oral_oews$aggregate, oral_pums_f$aggregate),
coverage = c(
oral_oews$employment_coverage_share,
oral_pums_f$employment_coverage_share
)
) |>
knitr::kable(digits = 3, align = "l")| weight_source | aggregate | coverage |
|---|---|---|
| OEWS national | 4.574 | 1 |
| PUMS-style F cell | 4.504 | 1 |
Neither source is always better. OEWS gives official labor market estimates; PUMS gives custom cells. The package records the weight source, year, taxonomy, and coverage so a reader can see what changed.