This report analyzes a Census Bureau American Community Survey (ACS) Public Use Microdata Sample (PUMS) housing file for Idaho, drawn from the 2006 single-year ACS. Each of the 6,496 rows is one sampled housing unit — not a summary statistic — with variables covering tenure (own/rent), structure type, rooms and bedrooms, household income, monthly housing costs, vehicles, and the year the structure was built.
This is genuine microdata, and its shape reflects that:
WGTP is the housing unit’s sampling
weight — the number of actual Idaho housing units that this one sampled
record represents. Summing WGTP across all rows, rather
than simply counting rows, is what turns a 6,496-row sample into a
population-representative estimate.wgtp1 through wgtp80 are
80 replicate weights, a standard Census technique for computing
margins of error on weighted estimates without requiring a closed-form
formula for every statistic. They are not used for point estimates and
are excluded from the substantive analysis below, but they explain the
bulk of this file’s 188 columns.raw <- read_csv("idaho_housing.csv", col_types = cols(.default = "c"))
housing <- raw %>%
transmute(
WGTP = as.numeric(WGTP),
NP = as.numeric(NP),
TEN = TEN,
BLD = BLD,
BDS = as.numeric(BDS),
RMS = as.numeric(RMS),
VEH = as.numeric(VEH),
YBL = YBL,
HINCP = as.numeric(HINCP),
GRNTP = as.numeric(GRNTP),
SMOCP = as.numeric(SMOCP),
GRPIP = as.numeric(GRPIP),
OCPIP = as.numeric(OCPIP)
) %>%
mutate(
Tenure = case_when(
TEN == "1" ~ "Owned with mortgage/loan",
TEN == "2" ~ "Owned free and clear",
TEN == "3" ~ "Rented",
TEN == "4" ~ "Occupied without payment of rent",
TRUE ~ NA_character_
),
Structure = case_when(
BLD == "01" ~ "Mobile home or trailer",
BLD == "02" ~ "One-family house, detached",
BLD == "03" ~ "One-family house, attached",
BLD == "04" ~ "2 apartments",
BLD == "05" ~ "3-4 apartments",
BLD == "06" ~ "5-9 apartments",
BLD == "07" ~ "10-19 apartments",
BLD == "08" ~ "20-49 apartments",
BLD == "09" ~ "50+ apartments",
BLD == "10" ~ "Boat, RV, van, etc.",
TRUE ~ NA_character_
),
YearBuilt = case_when(
YBL == "1" ~ "1939 or earlier",
YBL == "2" ~ "1940-1949",
YBL == "3" ~ "1950-1959",
YBL == "4" ~ "1960-1969",
YBL == "5" ~ "1970-1979",
YBL == "6" ~ "1980-1989",
YBL == "7" ~ "1990-1999",
YBL == "8" ~ "2000-2004",
YBL == "9" ~ "2005",
TRUE ~ NA_character_
),
YearBuilt = factor(YearBuilt, levels = c("1939 or earlier","1940-1949","1950-1959","1960-1969",
"1970-1979","1980-1989","1990-1999","2000-2004","2005"))
)
tibble(
Metric = c("Sampled housing records", "Estimated total Idaho housing units (weighted)",
"Records with tenure recorded", "Records with household income recorded"),
Value = c(
comma(nrow(housing)),
comma(sum(housing$WGTP, na.rm = TRUE)),
comma(sum(!is.na(housing$Tenure))),
comma(sum(!is.na(housing$HINCP)))
)
) %>%
kable(caption = "Dataset snapshot") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Metric | Value |
|---|---|
| Sampled housing records | 6,496 |
| Estimated total Idaho housing units (weighted) | 615,703 |
| Records with tenure recorded | 5,637 |
| Records with household income recorded | 5,637 |
The gap between “sampled records” (6,496) and “records with tenure
recorded” (5,637) is expected, not a data-quality problem: PUMS housing
files include some vacant units and non-interviews for which
occupancy-related fields are legitimately blank. All weighted estimates
below use WGTP and are therefore representative of occupied
and applicable Idaho housing units, not just the raw row count.
tenure_summary <- housing %>%
filter(!is.na(Tenure)) %>%
group_by(Tenure) %>%
summarise(units = sum(WGTP)) %>%
mutate(share = units / sum(units))
ggplot(tenure_summary, aes(x = fct_reorder(Tenure, units), y = units, fill = Tenure)) +
geom_col(show.legend = FALSE) +
geom_text(aes(label = paste0(comma(units), " (", percent(share, accuracy = 0.1), ")")),
hjust = -0.05, size = 3.5) +
coord_flip(clip = "off") +
scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.35))) +
scale_fill_brewer(palette = "Blues") +
labs(title = "Idaho housing units by tenure, 2006 (weighted estimate)",
x = NULL, y = "Estimated housing units") +
theme_minimal(base_size = 12)
Homeownership dominates Idaho’s housing stock: roughly 74% of housing units are owner-occupied (with or without a mortgage), versus about 24% rented — a notably higher ownership rate than the U.S. national average for this period, consistent with Idaho’s larger share of single-family, non-metro housing.
structure_summary <- housing %>%
filter(!is.na(Structure)) %>%
group_by(Structure) %>%
summarise(units = sum(WGTP)) %>%
mutate(share = units / sum(units))
ggplot(structure_summary, aes(x = fct_reorder(Structure, units), y = units)) +
geom_col(fill = "#2c7fb8") +
geom_text(aes(label = percent(share, accuracy = 0.1)), hjust = -0.1, size = 3.3) +
coord_flip(clip = "off") +
scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.2))) +
labs(title = "Idaho housing units by structure type, 2006 (weighted estimate)",
x = NULL, y = "Estimated housing units") +
theme_minimal(base_size = 12)
Detached single-family homes are the overwhelming majority of Idaho’s housing stock (about 72%), more than seven times the share of any other category. Mobile homes and trailers (10.5%) outnumber every size class of apartment building individually, and large apartment complexes (20+ units) are rare — together barely over 2% of the state’s housing units. This is a housing landscape defined by low-density, single-family construction rather than multifamily density.
inc_by_tenure <- housing %>%
filter(!is.na(Tenure), !is.na(HINCP)) %>%
group_by(Tenure) %>%
summarise(
weighted_mean_income = weighted.mean(HINCP, WGTP),
weighted_median_income = matrixStats::weightedMedian(HINCP, WGTP)
)
inc_by_tenure %>%
mutate(across(where(is.numeric), ~ dollar(round(.)))) %>%
kable(col.names = c("Tenure", "Weighted Mean Income", "Weighted Median Income"),
caption = "Household income by tenure (weighted)") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Tenure | Weighted Mean Income | Weighted Median Income |
|---|---|---|
| Occupied without payment of rent | $31,176 | $24,852 |
| Owned free and clear | $48,546 | $34,000 |
| Owned with mortgage/loan | $67,540 | $57,000 |
| Rented | $34,118 | $28,000 |
ggplot(housing %>% filter(!is.na(Tenure), !is.na(HINCP), HINCP >= 0),
aes(x = HINCP, weight = WGTP, fill = Tenure)) +
geom_density(alpha = 0.5, color = NA) +
scale_x_continuous(labels = dollar, limits = c(0, 200000)) +
labs(title = "Household income distribution by tenure (weighted)",
x = "Household income (2006 $)", y = "Density", fill = NULL) +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom")
Homeowners have substantially higher household incomes than renters: households owning with a mortgage report a weighted mean income of roughly $67,500, versus about $34,100 for renters — nearly double. This is the expected and well-documented pattern (mortgage qualification itself selects for higher, more stable income), but the gap is large enough to be worth stating plainly rather than assuming.
cost_burden <- tibble(
Group = c("Renters (Gross Rent / Income)", "Owners (Selected Monthly Costs / Income)"),
`Weighted Median % of Income` = c(
matrixStats::weightedMedian(housing$GRPIP[!is.na(housing$GRPIP)], housing$WGTP[!is.na(housing$GRPIP)]),
matrixStats::weightedMedian(housing$OCPIP[!is.na(housing$OCPIP)], housing$WGTP[!is.na(housing$OCPIP)])
)
)
cost_burden %>%
kable(caption = "Housing cost burden: share of household income spent on housing (weighted median)") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Group | Weighted Median % of Income |
|---|---|
| Renters (Gross Rent / Income) | 26 |
| Owners (Selected Monthly Costs / Income) | 20 |
Renters carry a meaningfully heavier cost burden than owners: the typical (median) renter spends about 26% of household income on gross rent, compared to about 20% for owners on their mortgage/tax/insurance costs. Federal housing policy typically treats 30%+ of income spent on housing as “cost-burdened” — worth keeping in mind when reading the income distributions above, since a lower income combined with a higher cost share compounds for renting households.
size_summary <- housing %>%
filter(!is.na(BDS)) %>%
summarise(
`Weighted mean bedrooms` = weighted.mean(BDS, WGTP),
`Weighted mean rooms` = weighted.mean(RMS, WGTP, na.rm = TRUE),
`Weighted mean household size (persons)` = weighted.mean(NP, WGTP, na.rm = TRUE),
`Weighted mean vehicles available` = weighted.mean(VEH, WGTP, na.rm = TRUE)
)
size_summary %>%
mutate(across(everything(), ~ round(., 2))) %>%
pivot_longer(everything(), names_to = "Measure", values_to = "Weighted Mean") %>%
kable(caption = "Household and structure size (weighted averages)") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Measure | Weighted Mean |
|---|---|
| Weighted mean bedrooms | 2.87 |
| Weighted mean rooms | 5.68 |
| Weighted mean household size (persons) | 2.30 |
| Weighted mean vehicles available | 2.10 |
veh_summary <- housing %>%
filter(!is.na(VEH)) %>%
group_by(VEH) %>%
summarise(units = sum(WGTP)) %>%
mutate(share = units / sum(units))
ggplot(veh_summary, aes(x = factor(VEH), y = units)) +
geom_col(fill = "#31a354") +
geom_text(aes(label = percent(share, accuracy = 0.1)), vjust = -0.4, size = 3.5) +
scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.15))) +
labs(title = "Vehicles available per household, 2006 (weighted estimate)",
x = "Number of vehicles available", y = "Estimated housing units") +
theme_minimal(base_size = 12)
The typical Idaho home has just under 3 bedrooms and about 6 rooms overall, consistent with the detached-single-family-dominated stock seen above. Two-vehicle households are the single most common arrangement (about 37% of households), and only about 3% of households report having no vehicle at all — a strong signal of Idaho’s car-dependent, low-density settlement pattern, in contrast to denser urban states where zero-vehicle households are far more common.
ybl_summary <- housing %>%
filter(!is.na(YearBuilt)) %>%
group_by(YearBuilt) %>%
summarise(units = sum(WGTP))
ggplot(ybl_summary, aes(x = YearBuilt, y = units)) +
geom_col(fill = "#e34a33") +
geom_text(aes(label = comma(units)), vjust = -0.4, size = 3) +
scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.15))) +
labs(title = "Idaho housing units by decade built (weighted estimate)",
x = NULL, y = "Estimated housing units") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 30, hjust = 1))
Idaho’s housing stock shows two clear building booms: the 1950s and the 1970s each added more housing than any other decade on record, together accounting for well over 40% of the state’s pre-2006 housing stock. Construction slowed markedly in the 1980s before picking back up through the 1990s and early 2000s — a pattern that roughly tracks Idaho’s population growth waves over the second half of the 20th century.
housing %>%
select(WGTP, NP, Tenure, Structure, BDS, RMS, VEH, YearBuilt, HINCP) %>%
rename(Weight = WGTP, Persons = NP, Bedrooms = BDS, Rooms = RMS, Vehicles = VEH,
`Household Income` = HINCP) %>%
datatable(options = list(pageLength = 10, scrollX = TRUE), rownames = FALSE, filter = "top")
Every estimate above uses the WGTP housing-unit weight,
which is what makes a 6,496-row sample representative of the full Idaho
housing stock. This file also carries 80 replicate weights
(wgtp1-wgtp80) specifically so that a margin
of error can be computed for any of these estimates using the Census
Bureau’s successive-difference replication method — a step not performed
here, since it requires specialized survey-design software (e.g. R’s
survey package with svrepdesign()). Any
estimate in this report should be read as a point estimate with an
unstated sampling margin of error, not an exact population count.
This is a single-year (2006) survey sample, not a
census — every figure here is subject to sampling error, and Idaho’s
relatively small population means single-year state-level ACS estimates
carry wider margins of error than larger states would. The file also
reports nominal dollar figures with no adjustment for inflation, so
income and cost figures should not be compared directly to other years
without adjustment. Codes for VAL (owner-estimated home
value) were intentionally left undecoded in this report, since that
variable uses a bracketed range code whose exact dollar cutoffs are best
confirmed against the specific-year Census PUMS data dictionary before
publishing precise figures.
Report generated in R Markdown. To publish: put
idaho_housing.csv in the same folder as this file, open it
in RStudio, click Knit, then use the
Publish button (top right of the preview pane) to push
directly to RPubs. This report uses
matrixStats::weightedMedian() for weighted medians —
install it with install.packages("matrixStats") if you
don’t already have it.