# SIPP data are in person-month format, meaning each record represents one month for a specific person.
# Unique persons are identified using SSUID+PNUM. Unique households are identified using SSUID+ERESIDENCEID. For
# additional guidance on using SIPP data, please see the SIPP Users' Guide at <https://www.census.gov/programs-surveys/sipp/guidance/users-guide.html>
# Note the 'select' statement in the first use of fread(). Most machines do not have enough memory to read
# the entire SIPP file into memory. Use a 'select' statement to read in only the columns you are interested in using.
# If you still encounter an out-of-memory error, you must select less columns or less observations.
# Load the "data.table", "dplyr", and "bit64" libraries. Use install.packages function if you don't already have these installed.
library("data.table")
##
## Attaching package: 'data.table'
## The following object is masked from 'package:base':
##
## %notin%
library("bit64")
## ********************************************************
## R-core is collecting use cases for 64-bit integers as they explore native support for these vectors.
##
## See https://stat.ethz.ch/pipermail/r-devel/2026-July/084631.html and reach out to Luke Tierney (luke-tierney@uiowa.edu).
## ********************************************************
##
## Attaching package: 'bit64'
## The following object is masked from 'package:utils':
##
## hashtab
## The following objects are masked from 'package:base':
##
## %in%, :, array, as.factor, as.ordered, colSums, factor, intersect,
## is.double, is.element, match, matrix, order, rank, rowSums,
## setdiff, setequal, table, union
library("dplyr")
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:bit64':
##
## intersect, setdiff, setequal, union
## The following objects are masked from 'package:data.table':
##
## between, first, last
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
# Store the text "pu2025.csv" in a name called ds
ds <- c("pu2025.csv")
# The file is pipe-delimited even though it's named .csv
pu <- fread(ds, sep = "|", select = c("SSUID", "ERESIDENCEID", "PNUM", "MONTHCODE", "SPANEL", "WPFINWGT", "ESEEING", "RFOODS", "RFOODR", "TAGE", "ESEX", "THHLDSTATUS"))
# Out of 5,203 variables, 12 variables were selected. The first six in the above line represent ID and structure variables. SSUID is sample unit ID with PNUM identifies a person and ERESIDENCEID identifies a household. PNUM is person number within the sample unit. MONTHCODE is reference month (1–12). SPANEL is panel year (2022-2025). WPFINWGT is the final person weight.The next six represent ESEEING blind or serious difficulty seeing even with glasses/contacts. RFOODS food security status recode: 1 high/marginal, 2 low, 3 very low. RFOODR is the raw food security score a count of affirmative responses (0–6). TAGE is age as of last birthday (capped at 90 for confidentiality). ESEX is sex with 1 Male, 2 Female. THHLDSTATUS represents household status and was added in the midst of coding due to needing to prove why I received 5,423 NAs.
# Make sure all the column names are upper-case
names(pu) <- toupper(names(pu))
# Preview the data
head(pu, 20)
## SSUID ERESIDENCEID PNUM MONTHCODE SPANEL WPFINWGT ESEEING RFOODS
## <i64> <int> <int> <int> <int> <num> <int> <int>
## 1: 11428525324 100001 101 1 2024 8290.772 2 1
## 2: 11428525324 100001 101 2 2024 8476.619 2 1
## 3: 11428525324 100001 101 3 2024 9238.426 2 1
## 4: 11428525324 100001 101 4 2024 9371.330 2 1
## 5: 11428525324 100001 101 5 2024 9547.140 2 1
## 6: 11428525324 100001 101 6 2024 9337.827 2 1
## 7: 11428525324 100001 101 7 2024 9096.493 2 1
## 8: 11428525324 100001 101 8 2024 9310.043 2 1
## 9: 11428525324 100001 101 9 2024 9209.630 2 1
## 10: 11428525324 100001 101 10 2024 9150.450 2 1
## 11: 11428525324 100001 101 11 2024 9195.871 2 1
## 12: 11428525324 100001 101 12 2024 9433.126 2 1
## 13: 11428534325 100001 101 1 2025 4473.576 2 3
## 14: 11428534325 100001 101 2 2025 4561.484 2 3
## 15: 11428534325 100001 101 3 2025 4761.070 2 3
## 16: 11428534325 100001 101 4 2025 5111.557 2 3
## 17: 11428534325 100001 101 5 2025 5001.163 2 3
## 18: 11428534325 100001 101 6 2025 4933.283 2 3
## 19: 11428534325 100001 101 7 2025 4894.891 2 3
## 20: 11428534325 100001 101 8 2025 4716.378 2 3
## SSUID ERESIDENCEID PNUM MONTHCODE SPANEL WPFINWGT ESEEING RFOODS
## <i64> <int> <int> <int> <int> <num> <int> <int>
## RFOODR TAGE ESEX THHLDSTATUS
## <int> <int> <int> <int>
## 1: 0 70 1 1
## 2: 0 70 1 1
## 3: 0 70 1 1
## 4: 0 70 1 1
## 5: 0 70 1 1
## 6: 0 70 1 1
## 7: 0 70 1 1
## 8: 0 70 1 1
## 9: 0 70 1 1
## 10: 0 70 1 1
## 11: 0 70 1 1
## 12: 0 70 1 1
## 13: 6 50 1 2
## 14: 6 50 1 2
## 15: 6 50 1 2
## 16: 6 50 1 2
## 17: 6 50 1 2
## 18: 6 50 1 2
## 19: 6 50 1 2
## 20: 6 50 1 2
## RFOODR TAGE ESEX THHLDSTATUS
## <int> <int> <int> <int>
# Rows are person-months, not people therefore 379,215 rows equals 32,052 people.
nrow(pu)
## [1] 379215
n_distinct(pu$SSUID, pu$PNUM)
## [1] 32052
n_distinct(pu$SSUID, pu$ERESIDENCEID)
## [1] 15215
summary(pu)
## SSUID ERESIDENCEID PNUM MONTHCODE
## Min. : 11428525324 Min. :100001 Min. :101.0 Min. : 1.000
## 1st Qu.:33286052753124 1st Qu.:100001 1st Qu.:101.0 1st Qu.: 3.000
## Median :66728586009222 Median :100001 Median :102.0 Median : 6.000
## Mean :56487541761701 Mean :115599 Mean :111.3 Mean : 6.482
## 3rd Qu.:87686017231924 3rd Qu.:100001 3rd Qu.:103.0 3rd Qu.: 9.000
## Max. :88199599599922 Max. :400005 Max. :405.0 Max. :12.000
##
## SPANEL WPFINWGT ESEEING RFOODS
## Min. :2022 Min. : 0 Min. :1.00 Min. :1.000
## 1st Qu.:2022 1st Qu.: 5673 1st Qu.:2.00 1st Qu.:1.000
## Median :2023 Median : 8573 Median :2.00 Median :1.000
## Mean :2023 Mean : 10505 Mean :1.95 Mean :1.157
## 3rd Qu.:2025 3rd Qu.: 13105 3rd Qu.:2.00 3rd Qu.:1.000
## Max. :2025 Max. :133335 Max. :2.00 Max. :3.000
## NAs :5423
## RFOODR TAGE ESEX THHLDSTATUS
## Min. :0.0000 Min. : 0.00 Min. :1.000 Min. :1.000
## 1st Qu.:0.0000 1st Qu.:25.00 1st Qu.:1.000 1st Qu.:1.000
## Median :0.0000 Median :46.00 Median :2.000 Median :1.000
## Mean :0.4814 Mean :45.31 Mean :1.513 Mean :1.365
## 3rd Qu.:0.0000 3rd Qu.:66.00 3rd Qu.:2.000 3rd Qu.:2.000
## Max. :6.0000 Max. :90.00 Max. :2.000 Max. :6.000
##
# Descriptive stats shows 5,423 NAs in ESEEING, these NAs were not asked because they were outside the variable's universe, per the dictionary.No other variables report NA.
na_rows <- pu %>% filter(is.na(ESEEING))
table(na_rows$TAGE)
##
## 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
## 693 31 67 23 32 32 44 30 21 29 42 22 24 8 30 35 58 33 179 282
## 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
## 142 140 122 166 138 118 83 74 141 160 71 75 86 103 36 57 56 68 59 90
## 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
## 45 73 27 16 9 51 35 24 5 12 41 21 12 24 20 31 15 30 48 31
## 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
## 28 13 20 48 39 11 28 14 20 15 39 24 43 33 66 67 69 33 67 21
## 80 81 82 83 84 85 86 87 88 89 90
## 54 40 5 12 37 58 20 11 86 47 85
sum(na_rows$TAGE>= 1)
## [1] 4730
# 4730 are the NA rows aged 1 or older. Age doesn't explain them.
table(na_rows$THHLDSTATUS, na_rows$TAGE >= 1)
##
## FALSE TRUE
## 1 10 0
## 2 677 0
## 3 0 1335
## 4 6 2909
## 5 0 140
## 6 0 346
# All 5,423 NAs fall outside ESEEING's universe. 687 failed age only, 4,730 failed status only, 6 failed both.
table(pu$SPANEL)
##
## 2022 2023 2024 2025
## 120652 69613 75519 113431
spanel_2025 <- pu %>% filter(SPANEL == 2025)
nrow(spanel_2025)
## [1] 113431
# Reference years differ across panels; weights are designed within panel. Kept 2025 for recency, sample from 2025 is 113,431 person-months.
n_distinct(spanel_2025$SSUID, spanel_2025$PNUM)
## [1] 9490
month_1 <- spanel_2025 %>% filter(MONTHCODE == 1)
nrow(month_1)
## [1] 9419
# 9,490 people in the 2025 panel, one row per person is set, nrow produces 71-person gap.
first_months <- spanel_2025 %>% group_by(SSUID, PNUM) %>% summarise(first_month =min(MONTHCODE))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by SSUID and PNUM.
## ℹ Output is grouped by SSUID.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(SSUID, PNUM))` for per-operation grouping
## (`?dplyr::dplyr_by`) instead.
table(first_months$first_month)
##
## 1 2 3 5 6 7 8 9 10 11 12
## 9419 5 6 6 13 8 8 7 5 5 8
# All 71 missing people have their first record in the data after January, spread from February to December. None of them have a January row, so filtering to January drops them. Data doesn't show why (e.g., birth, moving, etc.)
summary(month_1)
## SSUID ERESIDENCEID PNUM MONTHCODE
## Min. : 11428534325 Min. :1e+05 Min. :101 Min. :1
## 1st Qu.:33286027627725 1st Qu.:1e+05 1st Qu.:101 1st Qu.:1
## Median :66720486067625 Median :1e+05 Median :102 Median :1
## Mean :55752179964229 Mean :1e+05 Mean :102 Mean :1
## 3rd Qu.:87686013804325 3rd Qu.:1e+05 3rd Qu.:103 3rd Qu.:1
## Max. :88199599528525 Max. :1e+05 Max. :110 Max. :1
## SPANEL WPFINWGT ESEEING RFOODS
## Min. :2025 Min. : 0 Min. :1.000 Min. :1.000
## 1st Qu.:2025 1st Qu.: 4180 1st Qu.:2.000 1st Qu.:1.000
## Median :2025 Median : 5723 Median :2.000 Median :1.000
## Mean :2025 Mean : 6522 Mean :1.957 Mean :1.168
## 3rd Qu.:2025 3rd Qu.: 7868 3rd Qu.:2.000 3rd Qu.:1.000
## Max. :2025 Max. :40935 Max. :2.000 Max. :3.000
## RFOODR TAGE ESEX THHLDSTATUS
## Min. :0.0000 Min. : 1.00 Min. :1.000 Min. :2
## 1st Qu.:0.0000 1st Qu.:24.00 1st Qu.:1.000 1st Qu.:2
## Median :0.0000 Median :44.00 Median :2.000 Median :2
## Mean :0.5225 Mean :43.91 Mean :1.513 Mean :2
## 3rd Qu.:0.0000 3rd Qu.:65.00 3rd Qu.:2.000 3rd Qu.:2
## Max. :6.0000 Max. :87.00 Max. :2.000 Max. :2
sum(is.na(month_1$ESEEING))
## [1] 0
# There are no longer any NAs
table(month_1$ESEEING)
##
## 1 2
## 406 9013
# Frequency table for January of people with and without visual impairments yields 406 people are visually impaired while 9,013 are not.
table(month_1$RFOODS)
##
## 1 2 3
## 8302 654 463
# RFOODS food security status recode: 1 high/marginal, 2 low, 3 very low.
vision_food <- table(month_1$ESEEING, month_1$RFOODS)
vision_food
##
## 1 2 3
## 1 314 56 36
## 2 7988 598 427
prop.table(vision_food, margin = 1)
##
## 1 2 3
## 1 0.77339901 0.13793103 0.08866995
## 2 0.88627538 0.06634861 0.04737601
# 22.7% of vision impaired people are food insecure. 11.4% of non impaired people are food insecure. 22.7% is columns 2 and 3 combined, low plus very low.
hist(month_1$RFOODR)
# Histogram shows zero-inflated and right-skewed.
cor(month_1$ESEEING, month_1$RFOODR)
## [1] -0.06769058
# Cross-tab showed not-impaired people are less food insecure. But correlation is −0.068. As ESEEING goes up, the score goes down therefore there is a negative correlation. Very little variation to measure due to impaired people being a small share (406 of 9,419).
chisq.test(vision_food)
##
## Pearson's Chi-squared test
##
## data: vision_food
## X-squared = 47.763, df = 2, p-value = 4.25e-11
# The difference in food security between the two vision groups is very unlikely to be chance
Reference years differ across panels and weights are designed within panel. The panel for 2025 was kept for recency and weights weren’t used due to the assignment scope. Because weights weren’t used, the numbers (22.7% vs 11.4%) describe the 9,419 people in the sample, not all visually impaired Americans. Rows are person-months so keeping one month gives one row per person. January was selected simply for being the start of the year which interestingly became a limitation as 71 people first appear in the survey after the month of January. Rows in month_1 are people.
There were 5,423 NAs all outside ESEEING’s universe (p.942). 687 failed age only, 4,730 failed status only, 6 failed both, with no one inside the universe missing. January had 0 NAs. Everyone in month_1 has status 2 and is at least 1 year old, so all of them are inside the universe and all were asked.
As aforementioned, there are 9,490 people in the panel, 9,419 in month_1 (January). 71 or 0.75% of missing people is a small share. That share cannot be confirmed whether they differ from the rest of the sample.
The histogram shows a zero-inflated, right-skewed tail due to the high values bringing the mean (0.52) up with a low median (0) and making this a not normal distribution.
The cross-tab shows those that are vision impaired face nearly double the rate of food insecurity. The correlation is −0.068 (2 = not impaired). As ESEEING goes up, the score goes down therefore there is a negative correlation. Very little variation to measure due to impaired people being a small share (406 of 9,419). Chi-square shows that difference between the two vision groups is very unlikely to be chance. But rows aren’t independent, which overstates the certainty of the chi-square. Lastly, a limitation of this data is that the severity of a person’s visual impairment is not measured. Those who are more severely visually impaired may experience greater barriers to food access than those with partial visual impairments.
Due to the limitations listed above, it cannot be asserted that vision impairment causes food insecurity. Other factors like healthcare access or income may drive this.