Solution · Build an index from the SM11 items

The worked answer to the M05 bonus challenge

This is the answer key for the SM11 index challenge.

If you haven’t attempted it yet, go back and try — the checks on that page are designed so you can tell whether you’ve got it without looking here. Reading a solution you haven’t wrestled with teaches you that the solution is reasonable, which is not the same as being able to produce one.

Step 1 · Check before you average

Averaging assumes the items share something. Look first.

But before any of that, do the conversion once. The items arrive as labelled vectors (<dbl+lbl>) carrying 99 = Refused as a real number, and everything below — the correlations, the reversal, the average — wants plain numbers with that sentinel already gone. Make one analysis-ready copy and work from it:

library(corrplot)   # not loaded by the lab skeleton -- add it yourself

sm11_items <- c("SM11_a_W163", "SM11_b_W163", "SM11_c_W163")

# Refusals to NA, labels stripped, values themselves untouched.
pew_sm11 <- pew_selected |>
  mutate(across(all_of(sm11_items), ~ zap_labels(na_if(.x, 99))))

Then the check itself is short, because the columns are already clean:

pew_sm11 |>
  select(all_of(sm11_items)) |>
  cor(use = "pairwise.complete.obs") |>
  corrplot.mixed(lower = "number", upper = "circle", tl.col = "black")

All three correlations come back positive and moderate-to-strong — 0.66 to 0.75. Nothing here argues against averaging.

Why convert once, into its own data frame

The M05 lab’s closing box covers what zap_labels() does and why the na_if()-then-zap_labels() order matters. What is worth adding here is where to do it.

You could convert inline in each step — once for the correlations, again inside the averaging. It would work. But then the same recode lives in two places, and the day you decide 98 is also a refusal, you have two lines to remember and one of them will be missed. Converting once into pew_sm11 makes the cleaning a single, inspectable step, and everything downstream simply inherits it.

That is the general habit: clean the columns once, name the result, then analyze the named thing. Every step after the conversion gets shorter, and each one is about the analysis rather than about the file format.

Did you use starts_with("SM11_") instead of naming the three? It returns the same columns today, and it isn’t wrong. But an index is a claim about which items it contains, and a select helper re-answers that question every time the file changes — a future wave with a fourth SM11_ item would fold it in silently. Naming them once, in a vector, makes the content of your index a decision rather than a side effect.

Step 2 · Build the index

Three things happen here, and the order matters. Note that this builds on pew_sm11, not pew_selected — the refusals are already gone and the columns are already plain numbers, so this step is only about the index.

pew_index <- pew_sm11 |>
  mutate(
    across(all_of(sm11_items), ~ 5 - .x),
    n_sm11 = rowSums(!is.na(pick(all_of(sm11_items)))),
    sm_importance = case_when(
      n_sm11 >= 2 ~ rowMeans(pick(all_of(sm11_items)), na.rm = TRUE),
      n_sm11 <  2 ~ NA_real_
    )
  )

The direction fix — 5 - x. Pew stores 1 = Very important through 4 = Not at all important, so bigger means less. Subtracting from 5 flips it: 1 → 4, 4 → 1. Why 5 and not 4? Because for a 1–k scale the reversal is (k + 1) − x, and 4 + 1 = 5. Check it at the endpoints and you’ll never misremember it.

This is also where converting first pays off concretely. 5 - .x is arithmetic on a number, and it only makes sense once the column is a number with the refusals already removed — run it on the raw labelled column and 99 would quietly become -94.

Note what that across() does. It isn’t assigned to a new name, so it rewrites the three columns in place. From that line onward those columns hold reversed values — which is exactly what the averaging step below expects. If you’d written the reversal after the averaging, you’d have averaged the original direction.

The counter. is.na() marks the missing cells, ! flips it to mark the answered ones, and rowSums() adds across the row — TRUE counts as 1. Result: how many of the three each person answered.

The rule. Both case_when() arms are written out, and the missing value is typed NA_real_ to match the numeric the other arm returns.

Why na.rm = TRUE is doing real work here

The two pieces cooperate. case_when() decides who is eligible for a score; na.rm = TRUE makes it possible to actually compute one for them. A respondent who answered two items and skipped the third should get the mean of their two — and without na.rm = TRUE, rowMeans() would return NA regardless of what the rule said. In this wave that affects 7 people.

Now run it backwards — na.rm = TRUE with no eligibility rule. Everyone gets a score, including someone who answered a single item. That lone rating lands in the same column, formatted identically, indistinguishable from a genuine three-item average. Worse, it’s silent: nothing warns you, and the only trace is a distribution quietly mixing three-item means with one-item stand-ins.

na.rm = TRUE is not a fix for missing data; it is an instruction to ignore it. That’s the right call once you’ve decided how much information a score must rest on — and the wrong one before.

Why 2 of 3? Because someone chose it in advance and wrote it down. That is the entire standard. Requiring all three is equally defensible — with three substantively different items, dropping one removes a third of the content. What is not defensible is picking the threshold after seeing which one gives a nicer answer.

Step 3 · Verify it

# Range, coverage, and the shape of the thing
pew_index |>
  summarize(
    n_scored = sum(!is.na(sm_importance)),
    min      = min(sm_importance, na.rm = TRUE),
    max      = max(sm_importance, na.rm = TRUE),
    mean     = mean(sm_importance, na.rm = TRUE),
    sd       = sd(sm_importance, na.rm = TRUE)
  )

# Does it exist for exactly the people who were eligible?
pew_index |>
  mutate(uses_sm = if_else(SNSUSE_W163 == 1, "Yes", "No")) |>
  count(uses_sm, scored = !is.na(sm_importance))

What you should see: about 4,110 respondents scored, the index spanning 1 to 4 with a mean near 2.26 (SD 0.91), and essentially every scored respondent a social-media user.

That last check is the habit worth keeping: a derived variable should exist for exactly the people who were eligible for it, and no one else. If a non-user turned up with a score, the likeliest culprit is na.rm = TRUE averaging a row that was entirely missing — which returns NaN, not NA, and can slip past a careless check.

What the index is, and isn’t

A first taste of psychometrics

Checking whether items “hang together” before combining them is the opening move of psychometrics, the science of measurement. Inter-item correlation is the crudest version; real scale construction adds reliability (Cronbach’s α), factor analysis, and validity evidence. You’ll go much deeper in your forthcoming Measurement course.

Be precise about what the correlation matrix licensed. Positive associations are preliminary evidence that an average may be a useful summary. They do not establish that the three items measure one underlying thing, that the index is reliable or valid, or that equal weighting is right — and these three ask about genuinely different activities. What you built is an instructor-defined index for practice, not a published Pew scale, and describing it that way in a write-up is the honest move.

Where this connects

You’ve now built this kind of score twice, on two different datasets — the interference index in the M05 Module, and this one on a survey file you procured yourself. The recipe is identical: check the items, reverse-code if the direction is backwards, count what each person answered, average only those who cleared a threshold you set in advance.

When you build a composite for your group project, the questions will be the same three: do these belong together, which direction is “more,” and how much of the scale does someone have to have answered before I’m willing to score them?


← Back to the challenge · ← Back to the M05 lab