Lab 1: Your First Reproducible Survey Analysis

R, RStudio, Quarto, and the Latino National Survey

Purpose

This lab introduces the workflow we will use throughout the semester. We will work with a real public-opinion survey, the Latino National Survey (LNS), and learn how to move from a raw data file to a small, documented analysis file.

Most of the lab will be completed together in class. The final On Your Own section asks you to adapt the same workflow alone.

Learning objectives

By the end of this lab, you should be able to:

  • identify the main parts of RStudio;
  • work inside an RStudio project;
  • distinguish a Quarto source file from the Console;
  • install and load packages;
  • import a Stata survey file with haven;
  • inspect a data frame and use its codebook;
  • recognize common variable types and labelled variables;
  • select, filter, recode, mutate, count, and summarize with dplyr;
  • distinguish substantive responses from missing values;
  • save a clean analysis file; and
  • render a reproducible HTML document.

Before class

Install current versions of R, RStudio Desktop, and Quarto.

Open the course project rather than opening this file by itself. A project keeps the data, code, and output in a stable relationship and helps us avoid fragile computer-specific paths.

The RStudio workspace

R is the programming language. RStudio is the interface we use to write, run, organize, and inspect R code.

The four panes normally contain:

  • Source: the Quarto document or script we are writing;
  • Console: commands R has executed and messages it returns;
  • Environment/History: objects created during the current session;
  • Files/Plots/Packages/Help: project files, graphics, packages, and documentation.
Tip

Write code in the source document and run it from there. Code typed only in the Console is easy to lose and cannot produce a reproducible analysis.

1. A Quarto document

This file combines prose, executable R code, results, and document settings. Add your name below, save the file, and render it before continuing.

Student: Your name here

If the document renders, Quarto can execute the code in a fresh session and build the output.

2. Packages

A package adds functions to R. We will use tidyverse for data work, haven for Stata files, labelled for survey labels, and here for stable project paths.

Install a package once with install.packages(). Load it in every new R session with library().

# Run this only if a package is not already installed:
# install.packages(c("tidyverse", "haven", "labelled", "here"))

library(tidyverse)
library(haven)
library(labelled)
library(here)
Important

Do not place install.packages() in a document you submit. Installing changes the computer; loading declares what the analysis needs.

3. Import the Latino National Survey

The LNS interviewed more than 8,000 Latino adults in 2006. Its questionnaire contains measures of political attitudes, identity, discrimination, policy preferences, demographic characteristics, and incorporation.

The raw file is stored in Stata format. We import it with read_dta().

lns_raw <- read_dta(
  here("Labs", "case_study", "data", "lns_full.dta")
)

The assignment operator stores the result on its left. We use the name lns_raw deliberately: the raw data should remain unchanged.

Check the import

dim(lns_raw)
names(lns_raw)[1:20]
glimpse(lns_raw)
  • dim() reports rows and columns.
  • names() reports variable names.
  • glimpse() gives a compact view of names, types, and example values.

Checkpoint 1: How many respondents and variables are in the raw file? Why is the number of rows not necessarily the same as the number of people in the target population?

Your response:

4. Survey data and labelled variables

Survey files frequently store a short variable name, a longer variable label describing the question, and value labels explaining what numeric codes mean.

var_label(lns_raw$natprob)
val_labels(lns_raw$natprob)

var_label(lns_raw$age)
summary(lns_raw$age)

The variable natprob records what respondents named as the most important problem facing the country. The variable age records respondent age.

Convert labelled response categories to readable factors when needed:

lns_raw |>
  count(as_factor(natprob), sort = TRUE)
Warning

A numeric code is not automatically a quantity. A code of 4 for “strongly agree” does not mean the respondent possesses twice as much agreement as someone coded 2. Consult the labels and questionnaire before calculating.

Checkpoint 2: What is the most common response to natprob? Identify at least one category that may represent missing information rather than a substantive answer.

Your response:

5. Build a small analysis file

A professional workflow rarely modifies the full raw file directly. Create a smaller analysis object containing the variables required for the current task.

lns_clean <- lns_raw |>
  transmute(
    main_problem = as_factor(natprob),
    latino_problem = as_factor(latprob),
    generation = as_factor(newgen),
    age = as.numeric(age),
    female = as_factor(female),
    income = as_factor(income),
    poor_work = as_factor(poordisc),
    latino_work = as_factor(latdisc),
    discrimination_index = as.numeric(discindx)
  )

This introduces several important ideas:

  • the pipe sends the result from one step into the next;
  • transmute() creates a new data frame containing only the listed variables;
  • descriptive names document what variables mean; and
  • conversion is performed one variable at a time.

Inspect the result:

glimpse(lns_clean)
summary(lns_clean$age)
count(lns_clean, generation, sort = TRUE)

6. Missing and non-substantive responses

Don't know, refusal, not asked, and legitimate categories such as none are not interchangeable. Inspect the questionnaire and make an explicit decision.

lns_clean |>
  count(main_problem, sort = TRUE)

Retain the original variable and create an analysis version:

lns_clean <- lns_clean |>
  mutate(
    main_problem_analysis = main_problem,
    main_problem_analysis = na_if(main_problem_analysis, "dk/na")
  )

Check the result:

lns_clean |>
  summarize(
    total_rows = n(),
    observed_main_problem = sum(!is.na(main_problem_analysis)),
    missing_main_problem = sum(is.na(main_problem_analysis)),
    percent_missing = mean(is.na(main_problem_analysis)) * 100
  )
Note

The text used for a missing category must match its value label exactly. If the code produces no change, inspect the frequency table and revise the text.

Checkpoint 3: Why did we create main_problem_analysis instead of overwriting main_problem?

Your response:

7. Filter, count, and summarize

Create an object containing respondents ages 18–29.

young_adults <- lns_clean |>
  filter(age >= 18, age <= 29)

nrow(young_adults)

Summarize their age:

young_adults |>
  summarize(
    respondents = n(),
    mean_age = mean(age, na.rm = TRUE),
    median_age = median(age, na.rm = TRUE),
    minimum_age = min(age, na.rm = TRUE),
    maximum_age = max(age, na.rm = TRUE)
  )

Count the most important problems they named:

young_adults |>
  count(main_problem_analysis, sort = TRUE) |>
  mutate(percent = n / sum(n) * 100)

Checkpoint 4: In plain language, explain what one row of the table tells us. Why should we not yet describe these as nationally representative estimates?

Your response:

8. Save the analysis file

Save the smaller analysis object, not a modified version of the raw file.

dir.create(
  here("Labs", "fall2026", "lab1", "output"),
  recursive = TRUE,
  showWarnings = FALSE
)

write_rds(
  lns_clean,
  here("Labs", "fall2026", "lab1", "output", "lns_clean.rds")
)

An RDS file stores one R object and preserves its structure.

Guided-work checklist

Before moving to the independent section, confirm that you can:

  • render the document;
  • explain installing versus loading a package;
  • locate data using a project-relative path;
  • identify the dimensions and variables of a data frame;
  • inspect variable and value labels;
  • preserve a raw object while building a clean object;
  • explain the principal dplyr functions used above; and
  • identify an unresolved methodological question instead of silently making a coding decision.

On Your Own

Complete this section alone. You may consult the guided code, package help, and course materials. Include code, output, and brief interpretations.

Use latino_problem, which records what respondents considered the most important problem facing Latinos.

Question 1: Inspect the variable

Produce a frequency table for latino_problem, sorted from most to least frequent.

  • What is the most common response?
  • Which categories should be treated as non-substantive or missing?
  • Explain your decision in two or three sentences.
# Your code

Your interpretation:

Question 2: Create an analysis version

Create latino_problem_analysis. Preserve latino_problem and recode the appropriate non-substantive categories to NA. Show the number and percentage missing.

# Your code

Your interpretation:

Question 3: Select a group

Choose one generation category and create a filtered data frame for that group. Report the category, number of respondents, mean and median age, and percentage missing latino_problem_analysis.

# Your code

Your interpretation:

Question 4: Produce a substantive table

Within the selected generation, calculate the count and percentage naming each substantive problem facing Latinos. Sort from highest to lowest and display the five most common responses.

# Your code

Interpret the leading response accurately without making a causal or population-level claim.

Question 5: Document one decision

Write a short paragraph explaining one decision made while cleaning, filtering, or summarizing. State what you decided, what evidence you used, how it affects the result, and what you would verify before publishing.

Submission

Submit the completed QMD source file, rendered HTML file, and lns_clean.rds analysis file. Before submitting, restart R and render again. A successful clean render is the final reproducibility check.

Reference materials