HSCI 488 · Lesson 8

AI for Data Analysis and Coding in R

Directed Studies on Artificial Intelligence in Health Sciences

Learning objectives for this lesson:

  • Describe the main forms of AI coding assistance (chat assistants, editor assistants, coding agents and code-executing data tools) and what each can see and do.
  • Summarize the evidence from Peng and colleagues (2023) and METR (2025) on AI and programming speed, and explain why the gains depend on the task and on the user's ability to judge the code.
  • Describe a dataset to an AI assistant through its data dictionary and request R code in small steps that are run and read one at a time.
  • Read basic R code well enough to explain what each line does, and use an assistant to explain unfamiliar functions and error messages.
  • Check an AI-written analysis by confirming row counts and missing values, recomputing results by a second method, plotting the data and testing code on data with a known answer.
  • Diagnose and correct a missing-value code counted as a number and an age grouping with the wrong boundaries in AI-proposed R code.
  • Explain the garden of forking paths and use a pre-specified analysis plan to guard against selective reporting.
  • Explain why identifiable or confidential health data must not be uploaded to consumer AI services, and describe the conditions that platforms such as Population Data BC attach to data access.
  • Obtain AI help with code for confidential data through data dictionaries, synthetic data and local models, and apply the OCAP® principles to AI use with First Nations data.

This course was developed by Dr. Kiffer G. Card, Faculty of Health Sciences, Simon Fraser University. Product examples are current as of 2026.

Lesson 8 · HSCI 488

AI for Data Analysis and Coding in R

A short guided walkthrough before you work through the lesson at your own pace.

Directed Studies on Artificial Intelligence in Health Sciences
Why this lesson

Directing and checking an analysis

An AI assistant lets a student with no programming experience run a real analysis in R.

The skills this lesson teaches are describing the data, asking for code in small steps, reading the output and checking every result before it is reported.

The road map

Four sections

1 · Coding assistants

The forms of assistance and the evidence on speed.

2 · Writing R with AI

A workflow that starts from the data dictionary.

3 · Checking an analysis

Four checks, two real errors and the garden of forking paths.

4 · Data governance

Privacy, research data platforms and OCAP®.

The running example

Jordan's first analysis (fictional case)

4,045respondents in the 2021 survey wave
3 to 9range of the three-item UCLA Loneliness Scale
6+the Connector program's referral threshold

The data are public and de-identified, and every R output in the lesson comes from a real run of its script.

How to use this lesson

Work alongside the script

  • Download the R script from the first R box and run each block as you read.
  • Each section ends with a knowledge check and a reflection with a model answer.
  • The final assessment has fifteen questions and a final reflection.
  • Lewis, the study assistant on every page, answers questions from the lesson text.
Section 1 of 5

AI Coding Assistants: Forms, Evidence and Limits

⏱ Estimated reading time: 35 minutes
Section 1 of 5

AI Coding Assistants

The forms of coding assistance, the evidence on speed, and what the evidence means for a beginner in R.

Why code

A script records every step

A script can be rerun by anyone with the data, which makes each decision visible and the result reproducible.

R

A free, open-source language for statistics and graphics, extended by packages such as dplyr, readr and ggplot2.

Checkable

Code can be run, so its results can be compared with a count, a hand calculation or a second method.

Four forms

Who sees what, and who runs the code

Chat assistants

See only what you paste. You run the code.

Editor assistants

See your open script and sometimes your R session.

Coding agents

Work across a project folder and run code themselves.

Code-executing data tools

Run hidden code on an uploaded file.

The shared limit

An assistant knows only what it is told

Without a description of the data, an assistant guesses variable names, types and missing-value codes.

The data dictionary supplies each variable's exact name, type, permitted values and codes, without sharing any record.

Study one

A defined task, 55.8 percent faster

95professional programmers, randomly assigned
161 → 71average minutes without and with the assistant
55.8%less time (Peng and colleagues, 2023)
Study two

Experts on their own projects, 19 percent slower

24%faster, as forecast before the study
20%faster, as believed after the study
19%slower, as measured (METR, 2025)
What it means

Gains depend on the task and the user

Strong

Turning a clear request into code, explaining code and error messages, and formatting tables and plots.

Weak

Knowing the data's codes and scoring rules, and choosing and interpreting the analysis.

Impressions of speed are unreliable, and only checks show whether an analysis is right.

Carry forward

What to take into Section 2

  • Every form of assistance knows only what it is told about the data.
  • Gains depend on the task and on the user's ability to judge the code.
  • A beginner benefits most by reading, running and checking every step.

Learning Objectives for this section

  • Describe four forms of AI coding assistance (chat assistants, assistants built into code editors, coding agents and code-executing data tools) and state what each one can see and do.
  • Summarize the controlled experiment by Peng and colleagues (2023) and the randomized trial by METR (2025), including the setting, the participants and the main result of each.
  • Explain why the gains from AI coding tools depend on the task and on the user's ability to judge the code.
  • Identify the R tasks on which an assistant helps a beginner most and the decisions that remain with the analyst.
  • Describe the Cedar Valley analysis that this lesson follows and the rules under which it is carried out.

Most quantitative work in public health is now done by writing code. An analyst who estimates the prevalence of loneliness, compares vaccination coverage across regions or fits a regression model usually does so in a statistical programming language such as R, Python, SAS or Stata. Until recently, a student who had never programmed needed weeks of practice before producing a first reliable table. AI coding assistants change that starting point. A student can describe an analysis in plain language, receive working code within seconds, ask what each line does and ask for help when R reports an error. This lesson teaches the practices that turn that capability into analyses a student can trust and defend.

The lesson follows a single analysis from beginning to end. It uses the 2021 wave of the Canadian Social Connection Survey, a public, de-identified dataset that appears in HSCI 207 Lesson 11 and in several HSCI 410 lessons, and it shows the prompts, the code an assistant proposed, the real R output and the checks applied at each step. This first section describes the tools and summarizes what controlled studies show about their effect on the speed of programming work.

Box 8.1 introduces the Cedar Valley Health Authority, its Connector program for socially isolated and lonely older adults, and Jordan, a co-op student whose first analysis in R supplies the example followed through the lesson.

Box 8.1: Case: Jordan's first analysis in R (fictional)

The Cedar Valley Health Authority is a fictional, mostly rural health authority in British Columbia with about 210,000 residents, about 46,000 of them aged 65 and older, and 24 primary care clinics. Its Cedar Valley Connector program links adults aged 65 and older who screen as socially isolated or lonely with community groups, volunteer roles, transportation help and services. The program screens with the three-item UCLA Loneliness Scale, on which scores run from 3 to 9, and a score of 6 or higher triggers a referral.

Jordan, a Health Sciences co-op student on a four-month placement with the population health team, is asked by the team lead for some national context before a planning meeting: in a large Canadian survey, how common is a score of 6 or higher among adults aged 65 and older compared with younger adults? Jordan has never written R. The team is piloting an enterprise AI assistant that the health authority procured after a privacy impact assessment; under the pilot's rules it may be used with public documents, internal non-confidential documents and aggregate data, and it must never receive personal health information. Jordan records each prompt in the pilot's AI use log, and the team's epidemiologist has agreed to review the finished script.

Why analysts write code

A spreadsheet hides its steps: once a column has been recoded or a row deleted by hand, nobody can see what was done. A script, which is a plain text file of instructions that a program carries out in order, records every step. Anyone with the script and the data can run it again, see each decision and obtain the same result. That property is called reproducibility, and it is the reason HSCI 410 Lesson 1 treats the script as the official record of an analysis.

R is a free, open-source language for statistics and graphics. It was created by the statisticians Ross Ihaka and Robert Gentleman at the University of Auckland in the early 1990s and is now maintained by a large international community. Much of its usefulness comes from packages, which are bundles of additional functions that users install. This lesson uses three of them: dplyr, for selecting, filtering and summarizing data; readr, for reading and writing data files; and ggplot2, for plots. Hadley Wickham, chief scientist at Posit, created both dplyr and ggplot2.

Language models write code well for reasons that Lesson 2 explained. Their training data contain very large amounts of public code, and code is highly regular, so the next token in a line of R is often easy to predict. Code also has a property that ordinary prose lacks: it can be run. A factual claim in a paragraph can only be checked against a source, but a line of R either runs or produces an error message, and when it runs, its result can be compared with a count, a hand calculation or a second method. AI-written code is therefore easier to verify than many other kinds of AI output, provided that the user actually runs and checks it.

The next part describes the four forms in which AI coding assistance is available and what each can see of the data.

Four forms of coding assistance

Coding assistance arrives in four main forms in 2026. They differ in what the assistant can see, in who runs the code and in how much the assistant does before a person reviews its work. The products named below are examples available in 2026; the categories are likely to outlast any particular product.

The tabs that follow describe the four forms in turn, with examples of current products and the main weakness of each.

Chat assistants are the general-purpose assistants that most students already use, such as ChatGPT, Claude, Gemini and Microsoft Copilot. The user describes a task in a conversation, pastes in code or an error message, and copies the code that comes back into R. The assistant sees only what the user types or pastes. This form suits learning well, because every exchange is visible and the user runs each piece of code personally. Its main weakness is that the assistant has never seen the data, so it guesses variable names, types and codes unless the user supplies them.

Assistants built into code editors work inside the program where code is written. GitHub Copilot suggests the next lines of code as the user types, in editors that support it. Posit, the company that develops the RStudio and Positron editors for R, introduced Posit Assistant in 2026, a conversational assistant that can read the analyst's live R session. Because these assistants see the open script and sometimes the data in memory, their suggestions fit the analysis more closely. Their weakness is speed: a suggestion appears in grey and is accepted with one key, which makes it easy to accept code without reading it.

Coding agents, such as Claude Code and OpenAI's Codex, carry out a multi-step task with less supervision. Following the agent loop described in Lesson 2, the agent plans the task, edits files across a project, runs the code, reads the results and repeats until it judges the task complete. Agents suit larger projects and repetitive changes, for example updating twenty scripts to use a renamed variable. They also make many changes before a person looks at them, and they can run commands on the user's computer, so the review points that Lesson 2 recommended matter more than they do in a chat.

Code-executing data tools write and run code in a protected environment on files that the user uploads, and they return tables, charts and answers directly. The data analysis feature of ChatGPT, for example, runs Python code on uploaded spreadsheets. These tools give a fast first look at a dataset. They have two drawbacks for health research: the data leave the user's computer, which Section 4 shows is often not permitted, and the code that produced an answer stays hidden unless the user asks to see it, so the result cannot be checked or reproduced without extra effort.

Table 8.1 sets the four forms side by side, with what each can see, who runs the code and the work each suits.

Table 8.1. Four forms of AI coding assistance, with what each can see, who runs the code and the work each suits.

FormWhat it can seeWho runs the codeSuited to
Chat assistantOnly what the user pastesThe userLearning, small steps, explanations of code and errors
Editor assistantOpen files and, for some tools, the live R sessionThe userWriting code in context and fixing errors in place
Coding agentThe whole project folderThe agent, with user approvalMulti-file changes and repetitive edits
Code-executing data toolThe uploaded data fileThe tool, out of sightA quick first look at non-confidential data

What the assistant knows about the data

All four forms share one limit: an assistant knows only what it has been told or shown about the data. A chat assistant that has never seen the Canadian Social Connection Survey cannot know that the age variable is named DEMO_age, that the loneliness items are stored as ordered categories, or that a question shown to a respondent and left blank is recorded as the text "Presented but no response". It fills each gap with a plausible guess, and plausible guesses produce code that looks right. There are two ways to close the gap. The user can describe the data through its data dictionary, which is the document that lists each variable's name, meaning, type, permitted values and missing-value codes (Section 2 shows how), or the user can give a tool access to the data itself, which is subject to the governance rules in Section 4.

With the forms of assistance and their shared limit described, the next part turns to the evidence from randomized studies on whether AI tools make programmers faster.

What the evidence says about speed

Two randomized studies are often cited on the question of whether AI tools make programmers faster. They reached very different results, and the reasons for the difference are the most useful part of the evidence.

A defined task completed 56 percent faster

Peng, Kalliamvakou, Cihon and Demirer (2023) recruited 95 professional programmers through a freelancing platform and asked them to write an HTTP server in JavaScript as quickly as possible. An HTTP server is a small program that answers requests from web browsers; the task was self-contained, clearly specified and of a kind that appears often in public code. The programmers were randomly assigned to work with or without GitHub Copilot. Those with the assistant finished in an average of about 71 minutes, compared with about 161 minutes for the control group, which is 55.8 percent less time. The authors also reported signs that developers with less programming experience gained more, although that estimate was imprecise.

Experienced developers on their own projects took 19 percent longer

METR, a research organization that evaluates AI systems, ran a randomized trial with 16 experienced open-source developers (Becker, Rush, Barnes and Rein, 2025). The developers worked on 246 real tasks, such as bug fixes, new features and reorganizations of code, in large and mature software projects to which they had contributed for years; the projects averaged more than 22,000 stars on GitHub and more than one million lines of code. Each task was randomly assigned to allow or disallow AI tools, and when AI was allowed the developers mostly used an AI-enabled code editor with leading models of early 2025. Tasks completed with AI took 19 percent longer. Before the study, the developers had forecast that AI would cut their time by 24 percent, and after it they still believed that AI had made them about 20 percent faster.

METR stated the limits of its result plainly: the trial is a snapshot of early-2025 tools in one setting, it does not show that most developers or most programming work are slowed by AI, and AI may help more with unfamiliar code or less experienced developers. METR has continued to publish measurements as the tools have changed.

Figure 8.1 sets the results of the two studies side by side, using the figures their authors reported.

Peng and colleagues (2023) Average minutes to finish one defined task Without AI 161 With Copilot 71 55.8 percent less time with the assistant (95 programmers) METR (2025) Change in time per task when AI was allowed Forecast before the study 24% faster Belief after the study 20% faster Measured 19% slower Bars left of the line mean less time with AI; bars right of the line mean more time.
Figure 8.1. The two randomized studies, drawn from the figures their authors reported. Freelance programmers finished a defined task faster with an assistant, while experienced developers working on their own large projects took longer with AI and believed that they had been faster.

Reading the two studies together

Both studies were randomized, and their settings differed in three ways. The tasks differed: a new, self-contained program of a familiar kind in the first study, and changes to large, complex code with high standards for quality in the second. The people differed: freelance professionals meeting a task for the first time, and experts who already knew their code in detail. The most reasonable reading is that the gains from AI coding tools depend on the task and on the user's relationship to it. When a task is well defined and common, an assistant's first answer is usually close to right and the time saved is large. When an expert already knows what the code should do and must review, correct and adapt the assistant's suggestions to a complex project, the review can cost more time than the assistant saves.

The METR result also shows a gap between perception and measurement. Developers who were slower with AI felt faster, which suggests that a person's impression of an AI-assisted session is an unreliable guide to its real effect. For a data analyst, the corresponding risk concerns correctness more than speed: an analysis produced quickly with an assistant feels finished, and only checks of the kind taught in Section 3 can show whether it is right.

The next part considers what this evidence implies for a student who has never written R.

What this means for a beginner in R

A student who has never programmed differs from the participants in both studies. The potential gain is larger, because the alternative is often no analysis at all, and the ability to judge the code is smaller, because the student cannot yet tell good R from bad R at a glance. That combination explains the design of this lesson. Students do not need to write R from memory to use it well with an assistant, but they do need to read each line well enough to say what it does, run the code in small steps, read the output and apply checks that would reveal an error. Lesson 3 described AI capability as a jagged frontier, strong on some tasks and weak on others that look similar. For R, the frontier runs roughly as the cards below describe.

Turning a plain request into codeClick to explore
Explaining code line by lineClick to explore
Explaining error messagesClick to explore
Formatting tables and plotsClick to explore
Knowing the data's codesClick to explore
Choosing and interpreting the analysisClick to explore

The accordion that follows takes up three questions about learning R with an assistant: whether the language still needs to be learned, which form of assistance to start with, and whether code written with an assistant counts as the student's own work.

Do I still need to learn R if an assistant can write it?v

A user who cannot read R cannot check it, and Section 3 shows that AI-written code can run without an error message and still give a wrong answer. The level of fluency needed changes, however. A beginner working with an assistant needs to read code, recognize common functions, understand data types and interpret output, while the exact spelling of function arguments can be left to the assistant. HSCI 410 Lesson 1 sets up R and RStudio, and working through an analysis with an assistant, asking for explanations at each step, is itself an effective way to learn the language.

Which form of assistance should a beginner start with?v

A chat assistant, or the chat panel of an editor assistant, is the best starting point, because every exchange is visible and the student runs each piece of code personally. Code-executing data tools give answers without showing the code by default, and coding agents make many changes at once; both are easier to use well after the student can read R comfortably.

Is code written with an assistant my own work?v

The analyst who runs and reports an analysis is accountable for it, whoever wrote the code. Whether AI help is permitted in a particular course is set by the instructor under SFU's guidelines, and permitted use must be disclosed, as Lesson 3 explained. In professional settings, the AI use log and comments in the script record which parts the assistant drafted and how they were checked.

Box 8.2 asks the reader to match four tasks to the form of coding assistance that suits each and to name one risk to watch for.

Box 8.2: Try it: match the tool to the task

For each of the following four tasks, choose the form of coding assistance that suits it best (chat assistant, editor assistant, coding agent or code-executing data tool) and name one risk to watch for. (1) A student wants to understand what a ten-line R script from a classmate does. (2) An analyst wants the variable name region changed to health_region in fifteen scripts in a project folder. (3) A student wants a quick look at the distribution of a variable in a public spreadsheet of national immunization coverage. (4) An analyst is writing a ggplot2 figure in RStudio and wants suggestions as the code is typed.

Show a model answerv

(1) A chat assistant suits the explanation of a short script; the risk is an explanation that sounds right but misreads the code, so the student should run the script and confirm that the output matches the explanation. (2) A coding agent suits a repetitive change across many files; the risk is a change in the wrong place, so the analyst should review the list of edits before accepting them and rerun every script. (3) A code-executing data tool suits a first look at public, non-confidential data; the risk is a hidden computation, so the student should ask to see the code and check one number against the spreadsheet. (4) An editor assistant suits writing code in place; the risk is accepting a suggestion without reading it, so the analyst should read each suggested line before accepting it.

Jordan settles on the pilot's chat assistant for the analysis, because each step will then be visible, logged and run by Jordan personally. Section 2 sets out the workflow Jordan follows and the first four steps of the analysis.

Before that, the reflection and knowledge check that follow review the forms of coding assistance and the evidence on their effect on speed.

Reflection

A classmate who has a summer research position says: "A study showed that programmers with AI were 56 percent faster, so I will have an assistant write my whole analysis script in one go and skip learning R." Two studies are relevant. In Peng and colleagues (2023), 95 freelance professional programmers wrote an HTTP server in JavaScript, a small and clearly specified program; those randomly assigned to use GitHub Copilot finished 55.8 percent faster (about 71 minutes compared with about 161). In a randomized trial by METR (2025), 16 experienced open-source developers completed 246 real tasks in large projects they had worked on for years; tasks done with AI took 19 percent longer, although the developers believed afterward that AI had made them about 20 percent faster. Using these facts, explain to your classmate what the evidence shows and where the plan goes beyond it, and describe how you would recommend using an assistant for a first analysis in R.

Model answer

The 56 percent figure is real, but it comes from one kind of task: a small, clearly specified program of a familiar type, written from scratch by professionals. The METR trial shows that the gain is not universal. Experienced developers working on complex code they knew well took 19 percent longer with AI and still felt faster. Read together, the studies suggest that the benefit depends on the task and on whether the user can judge the code, and that impressions of speed are unreliable.

For a first analysis in R, the potential gain is large, because an assistant can supply syntax the student does not know. The weak point is judgement: a student who cannot read R cannot tell whether a whole script generated in one reply is right, and code can run without errors and still give wrong answers. I would recommend describing the data to the assistant through its data dictionary, asking for one small step at a time, running each step and reading the output, and asking the assistant to explain every function used. After each step I would check row counts and missing values, and I would recompute key numbers by a second method. Working this way still saves time, and it builds the reading fluency in R that checking requires.

Minimum 20 characters required.

✓ Reflection saved
Knowledge Check: this section

Question 1: In the controlled experiment by Peng and colleagues (2023), what task did the programmers complete, and what was the main result?

Peng and colleagues (2023) randomly gave 95 professional programmers access to GitHub Copilot or not, and asked them to write an HTTP server in JavaScript; the Copilot group finished 55.8 percent faster. Option a describes the METR (2025) trial, a different study with a different result.

Question 2: In the METR (2025) trial with experienced open-source developers, how did the developers' beliefs compare with the measured effect of AI tools?

The developers forecast a 24 percent speedup and afterward believed AI had made them about 20 percent faster, yet tasks with AI took 19 percent longer. The gap between perception and measurement is one of the study's main lessons; options b and c wrongly suggest that the developers judged the effect correctly.

Question 3: Which form of coding assistance plans a task, edits files across a project, runs the code and reads the results, repeating these steps until it judges the task complete?

The plan, act, observe and repeat loop across a project defines a coding agent. A chat assistant leaves the running of code to the user, an editor assistant suggests code in place, and a code-executing data tool runs code on an uploaded file in a single exchange.

Question 4: A student asks a chat assistant, which has never seen the data, for code to compute mean age, and receives mean(df$age). The student's data frame is called cscs and its age column is DEMO_age. What best explains the output, and what should the student do?

An assistant knows only what it has been told about the data, so it filled the gaps with plausible names. Supplying the dictionary entries gives it the real names. R does not match names approximately (option b), and repeated requests without the dictionary would only produce more guesses (option a).
Section 2 of 5

Writing and Understanding R with AI

⏱ Estimated reading time: 40 minutes
Section 2 of 5

Writing and Understanding R with AI

A workflow for producing, reading and saving R code with an assistant, step by step.

The workflow

Six steps, repeated for every piece

1. Describe the data 2. Ask for one step 3. Run the code 4. Read the output 5. Check the result 6. Save and log

Steps 2 to 5 repeat for each small piece of the analysis.

The first prompt

Describe the data, then ask for one step

  • The prompt states who the user is and the question the analysis must answer.
  • It pastes the dictionary entries: names, questions, types, options and codes.
  • It asks for a plan, the code for step one only and the expected output.
  • It shares no records.
Reading R

A minimum vocabulary

<- and |>

Store a result under a name; pass a result on, read as "and then".

f(x, n = 3)

Apply a function to its inputs, called arguments.

== and $

Test equality; take one column from a data frame.

#

Start a comment, which R ignores.

Steps 1 and 2

A correct count and a safe failure

4,045rows in the 2021 wave, matching the dictionary

Guessed names

R stopped: "Column `age` doesn't exist." The dictionary supplied the real name, DEMO_age.

Steps 3 and 4

Types first, then missing values

  • The loneliness items are ordered factors: categories stored in a fixed order.
  • Gender had 3 missing values and 4 answers of "Presented but no response".
  • After recoding, gender had 7 missing values and province had 50.
Explain and save

Build fluency, keep a record

Ask for explanations

Line by line, what if, and what the code assumes about the data.

Save and log

One commented script that reruns from a fresh session, and an entry in the AI use log.

Carry forward

What to take into Section 3

  • A dictionary lets the assistant write correct code without seeing any record.
  • Small steps, each run and read, keep errors easy to find.
  • Code that runs without a message still needs checking.

Learning Objectives for this section

  • Describe a dataset to an AI assistant through its data dictionary instead of uploading the records.
  • Request R code in small steps, and run and read the output of each step before moving to the next.
  • Read basic R syntax (assignment, the pipe, functions and their arguments, and comparisons) well enough to say what a line of code does.
  • Use an assistant to explain unfamiliar functions and error messages.
  • Save an analysis as a commented script or Quarto document and record the assistant's role in an AI use log.

An assistant can produce a complete analysis script in one reply, and for a beginner that is the least useful thing it can do. A long script that has never been run hides its errors among dozens of lines, and a student who cannot yet read R has no way to find them. The workflow in this section slows the process down to a pace at which each piece of code is understood and checked before the next one is written. It assumes no prior experience with R. Students who have not yet installed R and RStudio can follow the setup in HSCI 410 Lesson 1, and HSCI 207 Lesson 11 explains data dictionaries and missing-value codes using the same survey.

A workflow for writing R with an assistant

The workflow in this lesson turns a request for code into a sequence of small steps, each of which is run, read and checked before the next is written. Figure 8.2 shows the workflow as a whole, in which most steps repeat for every small piece of the analysis and the script is saved and logged once the last check has passed. The subsections that follow describe its parts: describing the data, asking for code in small steps, running and reading each step, asking for explanations and saving the work.

1. Describe the data with its dictionary 2. Ask for one step and what to expect 3. Run the code in your own R session 4. Read the output and every message 5. Check the result counts, second method 6. Save and log script, comments, AI log next step
Figure 8.2. The workflow used in this lesson. Steps 2 to 5 repeat for every small piece of the analysis, and the script is saved and logged once the last check has passed.

Describe the data through its data dictionary

The assistant writes code for data it cannot see, so the quality of its code depends on the description it receives. The most efficient description is the relevant part of the data dictionary: each variable's exact name, the question it records, its type (number, text or category), its permitted values and its missing-value codes. A missing-value code is a value that stands for a missing answer, such as 999, -88 or, in the Canadian Social Connection Survey, the text "Presented but no response". Describing the data in this way gives the assistant what it needs without sending a single record, which is also the habit that Section 4 requires for confidential data. A useful prompt adds who the user is, the question the analysis must answer and an instruction to ask for anything the dictionary does not say.

Ask for code in small steps

Each request should cover one step that produces output the user can inspect: load the data and count the rows, keep the needed variables, recode one kind of missing value, create one new variable. Small steps keep each block of code short enough to read, they make an error easy to locate, and they create a natural point at which to check the result. It also helps to ask the assistant what output to expect, because a prediction made before the code is run gives the user something to compare against.

Run and read each step

The user runs every block in an R session (in RStudio, Positron or the R console) and reads all of the output. A warning means that R completed the step but noticed something worth attention, such as values it could not convert. An error means that R stopped without completing the step, which is the safer kind of failure, because a step that stops cannot silently produce a wrong number.

Ask for explanations

After each step, the user can ask what a function does, what each argument means and what an error message means. Over a few analyses, these questions build the reading fluency in R that checking requires.

Save the work

Code that has been run and checked goes into a single script, with a comment on each step, so that the whole analysis can be rerun from the top. The script, together with the AI use log introduced in Lesson 3, records what the assistant drafted and how each step was checked.

The workflow asks a student to read each block of code and its output. The next part sets out the minimum R notation needed to do so.

Reading R: a minimum vocabulary

A beginner can read most of the code in this lesson with the eight pieces of notation in Table 8.2. In nrow(cscs), R applies the function nrow(), which counts rows, to the object called cscs. A function is a named operation that takes inputs, called arguments, inside its parentheses and returns a result.

Table 8.2. Eight pieces of R notation, with their meaning in plain language and an example of each.

NotationMeaning in plain languageExample
<-Store the result on the right under the name on the left.cscs <- data
|>The pipe: pass the result on the left into the function on the right. Read it as "and then".data |> filter(...)
f(x, n = 3)Call the function f with the input x and the named argument n set to 3.head(cscs, n = 3)
==Test whether two values are equal (two equals signs; one equals sign names an argument).year == 2021
$Take one column from a data frame.lonely$gender
c()Combine values into a list of values, called a vector.c(16, 30, 45)
NAA missing value.is.na(x)
#A comment: R ignores the rest of the line.# keep 2021

With this vocabulary, the code in the demonstration that follows can be read line by line. Worked Example 8.1 begins the demonstration that runs through the rest of the lesson: Jordan describes the survey to the assistant through its data dictionary and asks for a plan and the code for the first step only.

Worked Example 8.1: From a data dictionary to a first step in R

Jordan opens the pilot's chat assistant, describes the survey through its data dictionary and asks for a plan and the code for the first step only. The assistant's replies in this lesson are representative examples prepared for teaching, and they are not the verbatim responses of any named product. The R code attributed to the assistant was run on the survey file, and every R output on this page was pasted unchanged from that run. The demonstration continues through Worked code 8.1 to Worked code 8.4 and into Section 3.

Prompt

I am a co-op student with no R experience. I am analyzing the 2021 wave of the Canadian Social Connection Survey, a public, de-identified file that loads into R as a data frame called data (13,219 rows from four survey waves). My question is: what percentage of respondents in each of four age groups (16 to 29, 30 to 44, 45 to 64, and 65 and older) scored 6 or higher on the three-item UCLA Loneliness Scale? I also need code that scores the three items myself, because a program I work with records the same three items, and I can use the survey's own score as a check. The data dictionary entries are below. Please (1) propose a plan in small steps, (2) give me the R code for step 1 only, using dplyr, with a comment on each line, and (3) tell me what output to expect so that I can check it. Do not assume anything about the data that the dictionary does not say; ask me if you need more information.

SURVEY_collection_year: survey wave (2021, 2022, 2023 or 2024)
DEMO_age: integer, "How old are you (in years)?" (16 to 100 in 2021)
DEMO_gender: factor; Man, Woman, Non-binary, Presented but no response
GEO_province: factor; 13 provinces and territories, Presented but no response
LONELY_ucla_loneliness_scale_companionship: ordered factor,
  "How often do you feel that you lack companionship?"
  Hardly Ever, Some of the time, Often, Presented but no response
LONELY_ucla_loneliness_scale_left_out: ordered factor,
  "How often do you feel left out?" (same options)
LONELY_ucla_loneliness_scale_isolated: ordered factor,
  "How often do you feel isolated from others?" (same options)
LONELY_ucla_loneliness_scale_score: integer, 3 to 9 (items scored 1 to 3, added)
Codes: "Presented but no response" = question shown and left blank;
NA = a question never answered.

Output (abridged)

Here is a plan in six steps. (1) Load the file, keep the 2021 wave and count the rows. (2) Keep the seven variables you listed and give them short names. (3) Convert "Presented but no response" to NA in gender and province, and count the missing values in every column. (4) Score each UCLA item (Hardly Ever = 1, Some of the time = 2, Often = 3), add the three scores, and compare your total with LONELY_ucla_loneliness_scale_score. (5) Create the four age groups. (6) Make a table of the number of respondents, the mean score and the percentage scoring 6 or higher in each age group, and draw two plots.

The code for step 1 is below [the code shown in the first R box]. nrow(cscs) should print one number, the count of 2021 respondents, which you can compare with the count your data dictionary gives for the 2021 wave. If it prints 0, the year may be stored as text, and we will adjust the filter.

One question for you: should respondents whose gender is "Presented but no response" stay in the age-group table? Gender does not affect the age groups, so I suggest keeping them.

Commentary. The plan is sound. It works in small steps, it proposes the comparison with the survey's own score that Jordan asked for, it states an expected result for step 1, and it asks a question in place of making an assumption, which Jordan answers by keeping those respondents. The plan also leaves two things out. It says nothing about the survey's design, so Jordan adds a note that the survey recruited respondents online and is cross-sectional, which limits generalization to the Canadian population (the limitation that HSCI 410 Lesson 6 states for this survey). Step 4 also does not say what happens to "Presented but no response" on the loneliness items; that gap becomes an error in Section 3. The first check is applied as soon as step 1 runs: nrow(cscs) printed 4045, and the CSCS data dictionary lists 4,045 respondents in the 2021 wave, so the filter kept the right rows. The checks for the later steps appear with each R box.

The R code, output and checks for the first four steps follow. Worked code 8.1 loads the survey and keeps the 2021 wave, Worked code 8.2 shows the error produced by code written before the assistant saw the dictionary, Worked code 8.3 selects and renames the seven variables, and Worked code 8.4 counts the missing values and recodes the missing-value code.

RWorked code 8.1: Step 1: load the survey and keep the 2021 wave

The first three lines load the packages. Loading dplyr prints a message that some objects "are masked from 'package:stats'"; this is normal and means that dplyr's filter() replaces a function of the same name. The next two lines download the survey file and load it as a data frame called data, which requires an internet connection. The pipe then passes data to filter(), which keeps only the rows in which the survey year equals 2021, and the arrow stores the result as cscs.

library(dplyr)     # verbs for data frames: filter, select, mutate, summarise
library(readr)     # write_csv() and read_csv()
library(ggplot2)   # plots

github_url <- "https://raw.githubusercontent.com/jorgeandr3s/heal/main/cscs/public_data/CSCS2025_full_cleaned_deidentified_data_and_metadata.RData"
load(url(github_url))          # creates a data frame called data

cscs <- data |>
  filter(SURVEY_collection_year == 2021)   # keep the 2021 wave only

nrow(cscs)    # rows: one per respondent
ncol(cscs)    # columns: one per variable
[1] 4045
[1] 3247

The [1] at the start of each line is R's numbering of output values and can be ignored. The 2021 wave has 4,045 respondents, matching the data dictionary, and the file has 3,247 columns because it holds every variable from all four waves. Selecting the few variables the analysis needs is the next step.

RWorked code 8.2: Step 2: code written before the assistant saw the dictionary

Before writing the prompt in Worked Example 8.1, Jordan had tried a shorter request with no dictionary: "Give me R code to keep age, gender, province and the UCLA loneliness score from my data frame." The assistant proposed the code below. It has no way of knowing the real column names, so it invented short ones. The try() wrapper is there only so that the downloadable script keeps running after the error. At the console, the inner lines alone stop with the same error, laid out slightly differently and headed Error in `select()`.

try(
  lonely <- cscs |>
    select(age, gender, province, ucla_score)
)
Error in select(cscs, age, gender, province, ucla_score) :
  Can't subset columns that don't exist.
x Column `age` doesn't exist.

Prompt

I ran your code and R printed this error: "Can't subset columns that don't exist. x Column `age` doesn't exist." What does this mean, in plain language, and how do I fix it? [Jordan pastes the dictionary entries for the seven variables.]

Output (abridged)

The message means that select() looked for a column called age in cscs and did not find one. My earlier code guessed short names because I had not seen your data. R reports only the first missing column, but gender, province and ucla_score do not exist either. In select() you can keep a column and rename it in one move by writing the new name, an equals sign and the dictionary name, for example age = DEMO_age.

The explanation is correct and matches the output, in which R named only age. An error of this kind is a safe failure, because R stopped before producing anything; guesses about codes and types, by contrast, produce no error at all.

RWorked code 8.3: Step 3: select and rename the variables, using the dictionary names

With the dictionary in the conversation, the assistant's code names each column correctly. Inside select(), the short name on the left of each equals sign is the new name, and the dictionary name on the right is the column being kept. Keeping the survey's own score under the name ucla_survey preserves it for the comparison planned in step 4. The function glimpse() prints one line per column with its name, its type and its first few values.

lonely <- cscs |>
  select(age           = DEMO_age,
         gender        = DEMO_gender,
         province      = GEO_province,
         companionship = LONELY_ucla_loneliness_scale_companionship,
         left_out      = LONELY_ucla_loneliness_scale_left_out,
         isolated      = LONELY_ucla_loneliness_scale_isolated,
         ucla_survey   = LONELY_ucla_loneliness_scale_score)

glimpse(lonely)   # one line per column: name, type, first values
Rows: 4,045
Columns: 7
$ age           <int> 58, 35, 39, 55, 70, 60, 77, 37, 50, 24, 27, 29~
$ gender        <fct> Woman, Non-binary, Man, Woman, Woman, Woman, M~
$ province      <fct> Alberta, British Columbia, British Columbia, O~
$ companionship <ord> Often, Some of the time, Often, Some of the ti~
$ left_out      <ord> Often, Often, Often, Some of the time, Often, ~
$ isolated      <ord> Often, Some of the time, Often, Hardly Ever, O~
$ ucla_survey   <int> 9, 7, 9, 5, 9, 6, 3, 5, 8, 8, 5, 6, 7, 3, 4, 6~

The output confirms 4,045 rows and 7 columns; the ~ marks a line cut short to fit the screen. The types in angle brackets matter for what follows. <int> means whole numbers. <fct> means a factor, which is R's type for a categorical variable: the values are stored as categories, called levels, in a fixed order. <ord> means an ordered factor, a factor whose levels have a meaningful order, as the three loneliness answers do. Section 3 shows why the stored order of the levels can matter a great deal.

RWorked code 8.4: Step 4: count the missing values, then recode the missing-value code

Before any recoding, Jordan asks for code that counts the missing values in every column and lists every answer to the gender question, including NA. The function is.na() marks each missing value as TRUE, and colSums() adds up the TRUE values in each column. The argument useNA = "ifany" tells table() to show a count of NA as well, which it leaves out by default.

colSums(is.na(lonely))                  # NA count in each column
table(lonely$gender, useNA = "ifany")   # every answer, NA included
          age        gender      province companionship      left_out
            0             3             0           421           421
     isolated   ucla_survey
          421           462

                      Man                     Woman
                     1883                      2049
               Non-binary Presented but no response
                      106                         4
                     <NA>
                        3

Two things stand out. Each loneliness item has 421 missing values, while the survey's score has 462, so 41 respondents with all three items present have no score; Section 3 returns to them. Gender has two kinds of missing answer: 3 NA values and 4 respondents recorded as "Presented but no response". The assistant's next block converts that code to NA in gender and province. The function mutate() changes or creates columns, as.character() turns the factor into plain text, and na_if() replaces one stated value with NA.

lonely <- lonely |>
  mutate(gender   = na_if(as.character(gender),
                          "Presented but no response"),
         province = na_if(as.character(province),
                          "Presented but no response"))

table(lonely$gender, useNA = "ifany")   # the 4 codes are now NA
sum(is.na(lonely$province))             # 50 codes became NA
summary(lonely$age)                     # plausible range for adults?
       Man Non-binary      Woman       <NA>
      1883        106       2049          7

[1] 50

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
   16.0    28.0    34.0    39.8    52.0   100.0

Each result can be checked against the earlier output. Gender now has 7 missing values, the 3 original NA values plus the 4 converted codes, and the other categories keep their counts of 1,883, 106 and 2,049. Province has 50 missing values, all of them converted codes, since province had no NA values before. Ages run from 16 to 100 with a median of 34, which is plausible for an adult survey and matches the dictionary's stated range. Jordan logs the step as complete.

By the end of these four steps, the survey file has been loaded, reduced to seven variables and checked for missing values. The next part turns to prompts that ask the assistant to explain the code at each step.

Asking for explanations

Every block in Worked code 8.1 to Worked code 8.4 contains functions that a beginner has not met before. Each of the prompts below asks the assistant to explain the code the student is actually running, which keeps the explanations tied to real output.

Explain this code line by linev

"Explain this code line by line for someone who has never used R. For each function, say what it does and what each argument means." This is the most useful general prompt. Paste the exact code that was run, so that the explanation describes that code and not a similar version.

What would happen if...?v

"What would happen if I removed useNA = "ifany" from this line?" Questions of this kind teach what each argument contributes. The student can test the answer immediately by running the changed line, which also checks the assistant's explanation.

What does this code assume about my data?v

"What assumptions does this code make about my data? List anything that would make it give a wrong answer without an error." This prompt draws out hidden assumptions about codes, types and missing values, which are the main source of silent errors. It works best when the dictionary entries are in the conversation.

Explain this error or warningv

"I ran this code and R printed this message: [paste the full message]. Explain what it means and suggest a fix." Include the code, the complete message and the relevant dictionary entries. Then ask why the fix works, so that the same error is recognizable next time.

Explanations of this kind build the reading fluency that checking requires. Once a step has been explained and checked, its code is saved, which the next part describes.

Saving the work

Once a step has run and passed its checks, its code goes into the script in order, with a comment that says what the step does. The downloadable script for this lesson follows that pattern, and it labels the blocks that contain the assistant's errors. A script should run from a fresh R session without any manual steps, so the final test of a saved script is to restart R and run it from the first line to the last.

Analysts who want text, code and output in one report can use a Quarto document, a file with the extension .qmd that mixes paragraphs of writing with chunks of R code. Quarto, an open-source publishing system developed by Posit, runs the code and inserts the output into an HTML, Word or PDF report, so the numbers in the report always come from the code.

The AI use log introduced in Lesson 3 records the assistant's part in the analysis. A short entry for each working session is enough, as in Jordan's first entry in Table 8.3.

Table 8.3. Jordan's AI use log entry for the first analysis session.

Log fieldJordan's entry
Date and toolFirst analysis session; the pilot's enterprise chat assistant.
Data sharedData dictionary entries for seven public CSCS variables. No records were shared.
What the assistant producedA six-step plan; code for steps 1 to 4; an explanation of one error message.
Checks appliedRow count 4,045 matched the dictionary; missing values counted before and after recoding (gender 3 to 7, province 0 to 50); age range 16 to 100.
Changes madeReplaced guessed column names with dictionary names; added a note on survey design.

Box 8.3 asks the reader to explain a single line of R in plain words, using the notation introduced in this section.

Box 8.3: Try it: read a line of R

Without running it, explain in plain words what the following line does, using the vocabulary in Table 8.2, and say what the result would be if every value of age in the British Columbia rows were missing: mean(lonely$age[lonely$province == "British Columbia"], na.rm = TRUE). (The square brackets after a column keep only the values for which the condition inside them is TRUE, and na.rm = TRUE tells mean() to remove missing values before averaging.)

Show a model answerv

The line takes the age column of lonely, keeps only the values in rows where province equals "British Columbia", removes any missing values and returns their mean, which is the average age of respondents from British Columbia. If every one of those ages were missing, there would be nothing left to average after the missing values were removed, and R would return NaN ("not a number"). A careful analyst would also notice that the 50 respondents whose province is now NA are excluded by the condition, which is correct for this question but worth stating.

The first four steps produced a file of 4,045 respondents with the right variables and with the gender and province codes converted. The assistant's remaining code (scoring the items, building the age groups, the table and the plots) runs without a single error message. Section 3 shows that two of those steps are nonetheless wrong, and how the checks find them.

Before that, the reflection and knowledge check that follow review the workflow, the data dictionary and the first four steps of the analysis.

Reflection

You are about to analyze a public survey file with an AI assistant. The file has been loaded into R as a data frame called sleep with 2,000 rows. Its data dictionary lists three variables: resp_age (integer, 18 to 99), sleep_hours ("On a typical night, how many hours do you sleep?", numeric, 0 to 24, with the value 99 meaning "Don't know") and region (factor with the levels North, South, Coast and Refused). You want the mean hours of sleep in each region. Write the prompt you would send to the assistant to obtain the code for the first step, and explain what each part of the prompt is for. Then describe two things you would check in the output of that first step before asking for the next one.

Model answer

My prompt: "I am new to R. I have a public survey data frame called sleep with 2,000 rows. Data dictionary: resp_age, integer, 18 to 99; sleep_hours, numeric, 0 to 24, where 99 means Don't know; region, factor: North, South, Coast, Refused. My goal is the mean hours of sleep in each region. Please propose a plan in small steps, then give me only the code for step 1, which should count the rows and show every value of region and a summary of sleep_hours, with a comment on each line. Tell me what output to expect, and ask me if anything is unclear."

The first sentence sets the level of explanation. The dictionary gives the exact names, types and codes, so the assistant does not guess, and no records are shared. Stating the goal lets the assistant plan, while asking for step 1 only keeps the code short enough to read. Asking for expected output gives me something to compare with, and the invitation to ask questions reduces silent assumptions.

In the output I would first check that nrow() prints 2000 and that the table of region, with useNA = "ifany", shows how many answered Refused, which must be treated as missing. Second, I would check that the summary of sleep_hours has a maximum of 99, which confirms that the Don't know code is present and must be converted to NA before any mean is calculated.

Minimum 20 characters required.

✓ Reflection saved
Knowledge Check: this section

Question 1: Why does the workflow in this lesson describe the data to the assistant through its data dictionary instead of uploading the records?

The dictionary gives the assistant what it needs to write correct code (exact names, types, permitted values and missing-value codes) without sharing records, a habit that Section 4 requires for confidential data. Some tools can read uploaded files (option a), and de-identified records can still identify people (option d).

Question 2: What does the line cscs <- data |> filter(SURVEY_collection_year == 2021) do?

The pipe passes data to filter(), which keeps the rows for which the condition is TRUE, and the arrow stores the result as cscs. The double equals sign tests equality; it does not assign a value (option b), and filter() keeps matching rows instead of removing them (option c).

Question 3: After running select(cscs, age, gender, province, ucla_score), R prints: "Can't subset columns that don't exist. x Column `age` doesn't exist." What is the best next step?

The error means the column names were guessed. R reports only the first missing column, so the other three invented names would also fail (option a). The fix is to use the dictionary names, for example age = DEMO_age inside select().

Question 4: Before recoding, table(lonely$gender, useNA = "ifany") showed 4 answers of "Presented but no response" and 3 NA values. After the code converts the missing-value code to NA, how many NA values should gender have?

The 3 original NA values remain and the 4 converted codes are added, giving 7, which is what the real output showed (50 is the number of converted province codes). Checking that the count changes by exactly the number of converted codes confirms that the recoding did what was intended.
Section 3 of 5

Checking an Analysis

⏱ Estimated reading time: 45 minutes
Section 3 of 5

Checking an Analysis

Four checks, two silent errors in AI-written code, and the garden of forking paths.

Four checks

How silent errors are found

Counts and missing values

Rows lost, values turned into missing, codes left in.

A second method

Logic errors that produce plausible numbers.

A plot

Impossible values and unexpected shapes.

A known-answer test

Errors in a scoring rule or function.

Silent error 1

A missing-value code counted as 4

19impossible scores of 10 or 12
41scores that disagreed with the survey's own score
4thlevel: "Presented but no response"
The correction

A stated rule, a second method and a known answer

  • 22 of the 41 wrong scores lay inside the valid range of 3 to 9.
  • The corrected score matched the survey's score for all 4,045 respondents.
  • The known-answer test returned 3, 9, 7 and NA, as expected.
Silent error 2

Ages at the boundaries in the wrong group

1016-year-olds left with no age group
4765-year-olds placed in the 45 to 64 group
486respondents aged 65 and older after the fix
Table and plots

Percent scoring 6 or higher, CSCS 2021

49.6%16 to 29
45.5%30 to 44
57.9%45 to 64
51.7%65 and older

Older respondents were spread toward both ends of the scale: 23.1 percent scored 3 and 14.8 percent scored 9.

Forking paths

Many analyses, and choices made after the data

5%chance of a false positive with 1 analysis
40%with 10 independent analyses
64%with 20 independent analyses

A pre-specified analysis plan fixes the question, groups, thresholds and statistics before the results are seen.

Carry forward

What to take into Section 4

  • Silent errors are found by running checks, since reading the code rarely reveals them.
  • A second method found every wrong score; a range check found fewer than half.
  • A dated plan guards against selective reporting and forking paths.

Learning Objectives for this section

  • Explain why code that runs without an error message can still give a wrong answer.
  • Apply four checks to an AI-written analysis: row counts and missing values, recomputation by a second method, plots of the data, and a test on a small dataset with a known answer.
  • Diagnose and correct two common errors in AI-written R code: a missing-value code counted as a number and an age group with the wrong boundaries.
  • Explain the garden of forking paths and how AI makes it easy to run many analyses and report the most favourable one.
  • Describe what a pre-specified analysis plan contains, and explain why the choice and interpretation of an analysis remain the analyst's responsibility.

When R stops with an error message, the problem is visible and an assistant can usually explain it. The harder case is code that runs to the end and prints a tidy result that is wrong. Such silent errors arise most often where the code meets the data: a missing-value code that the code treats as a real answer, a boundary that puts people in the wrong group, or a filter that removes rows without saying so. An assistant that has been told little about the data makes these errors easily, and a beginner cannot spot them by reading the code. They are found by checking the results.

Four checks for an AI-written analysis

The four checks in Table 8.4 catch most silent errors in descriptive analyses. None of them requires advanced R, and an assistant can write the code for each one. The effort should rise with the stakes, as Lesson 3 recommended for verification in general: a figure for a planning meeting deserves every check, and an exploratory look at a variable may need only the first.

Table 8.4. Four checks for an AI-written analysis, with what each one catches and how to carry it out in R.

CheckWhat it catchesHow to do it in R
Confirm row counts and missing valuesRows lost or duplicated by a filter; values turned into NA; missing-value codes left in as answersnrow(), table(x, useNA = "ifany"), colSums(is.na()), summary()
Recompute by a second methodErrors of logic that produce plausible numbersA different function, or a variable that someone else computed independently
Plot the dataImpossible values, outliers and unexpected shapesA bar chart or histogram of every new variable
Test on data with a known answerErrors in a scoring rule or functionA few invented rows whose answers are worked out by hand

The two parts that follow apply these checks to the next two steps of Jordan's analysis, the scoring of the loneliness items and the construction of the age groups.

Check in action: scoring the loneliness items

The next step in Jordan's plan scores the three UCLA items and adds them. The demonstration continues with the prompt, the assistant's code as proposed, and the real output.

Box 8.4 gives Jordan's prompt for this step and Box 8.5 the assistant's reply. Worked code 8.5 shows the code as proposed, with a range check and a comparison against the survey's own score applied to its output.

Box 8.4: Prompt

Step 4: write code that scores each of the three UCLA items (Hardly Ever = 1, Some of the time = 2, Often = 3) and adds the three scores into a new column called ucla_score.

Box 8.5: Output (abridged)

The three items are ordered factors whose levels are already in the order Hardly Ever, Some of the time, Often, so as.numeric() converts them to 1, 2 and 3 directly. [The code shown in Worked code 8.5.]

RWorked code 8.5: Check 1: the scoring code as the assistant proposed it

The code runs without a warning. Jordan's first check is to count every value of the new score, including NA.

lonely <- lonely |>
  mutate(ucla_score = as.numeric(companionship) +
                      as.numeric(left_out) +
                      as.numeric(isolated))

table(lonely$ucla_score, useNA = "ifany")   # every value, NA included
   3    4    5    6    7    8    9   10   12 <NA>
 494  490  806  888  403  209  315    4   15  421

The scale runs from 3 to 9, yet 4 respondents have a score of 10 and 15 have a score of 12. A range check of this kind has caught 19 impossible values. The second check compares Jordan's score with the score the survey team computed, by counting the rows in which each one is missing.

# Second method: compare with the score the survey team computed
lonely |>
  count(ours_missing   = is.na(ucla_score),
        survey_missing = is.na(ucla_survey))

# Look at the rows where the two disagree
lonely |>
  filter(!is.na(ucla_score), is.na(ucla_survey)) |>
  select(companionship, left_out, isolated, ucla_score) |>
  head(3)

levels(lonely$companionship)   # the order of the levels decides the numbers
  ours_missing survey_missing    n
1        FALSE          FALSE 3583
2        FALSE           TRUE   41
3         TRUE           TRUE  421

              companionship                  left_out
1 Presented but no response Presented but no response
2               Hardly Ever          Some of the time
3 Presented but no response Presented but no response
                   isolated ucla_score
1 Presented but no response         12
2 Presented but no response          7
3 Presented but no response         12

[1] "Hardly Ever"               "Some of the time"
[3] "Often"                     "Presented but no response"

Forty-one respondents have a score from Jordan's code and none from the survey. They are the same 41 people noticed in Section 2, who had all three items present but no survey score. The rows printed show why: each has at least one answer of "Presented but no response", and levels() shows that this code is the fourth level of the factor. For a factor, as.numeric() returns the position of each answer's level, so the missing-value code was counted as an answer worth 4. The assistant's statement about the order of the first three levels was true, and the code still failed because it ignored the fourth.

The consequences reach further than the 19 impossible values. Since the smallest possible wrong score is 1 + 1 + 4 = 6, every one of the 41 respondents would have been counted as screening positive at the Connector threshold. Comparing the table in Worked code 8.5 with the corrected table in Worked code 8.6 shows that 22 of the 41 wrong scores fell inside the valid range (10 extra respondents at 6, 6 at 7, 3 at 8 and 3 at 9). A range check alone would have missed more than half of the errors, and only the comparison with an independent second method found them all. HSCI 207 Lesson 11 makes the same point about numeric codes such as 999: a code treated as a number distorts every summary that includes it.

Worked code 8.6 gives the corrected scoring code, which states the scoring rule in full and confirms the result with a second method and a known-answer test.

RWorked code 8.6: Check 1, corrected: an explicit scoring rule, a second method and a known-answer test

The corrected code, which Jordan asked the assistant to write with the dictionary's codes spelled out, states the scoring rule in full. The first block creates a small function of Jordan's own called score_item(), so that one rule is applied to all three items. Inside it, case_when() checks each condition in turn and returns the value after the tilde (~) for the first condition that is TRUE; the last line, TRUE ~ NA_real_, catches every other answer, including the missing-value code, and returns a missing number.

score_item <- function(x) {
  x <- as.character(x)
  case_when(x == "Hardly Ever"      ~ 1,
            x == "Some of the time" ~ 2,
            x == "Often"            ~ 3,
            TRUE                    ~ NA_real_)
}

lonely <- lonely |>
  mutate(ucla_score = score_item(companionship) +
                      score_item(left_out) +
                      score_item(isolated))

table(lonely$ucla_score, useNA = "ifany")
all.equal(lonely$ucla_score, lonely$ucla_survey)   # second method agrees?
   3    4    5    6    7    8    9 <NA>
 494  490  806  878  397  206  312  462

[1] TRUE

All scores now lie between 3 and 9, there are 462 missing scores, and all.equal() confirms that Jordan's score matches the survey's score for every one of the 4,045 respondents, with the missing values in the same rows. The last check runs the function on four invented respondents whose scores can be worked out by hand: 1 + 1 + 1 = 3, 3 + 3 + 3 = 9, 2 + 2 + 3 = 7, and a missing score for the person with a missing-value code.

test <- data.frame(
  companionship = c("Hardly Ever", "Often", "Some of the time", "Often"),
  left_out      = c("Hardly Ever", "Often", "Some of the time",
                    "Presented but no response"),
  isolated      = c("Hardly Ever", "Often", "Often", "Often"))

test |>
  mutate(score = score_item(companionship) + score_item(left_out) +
                 score_item(isolated))
# Expected by hand: 3, 9, 7 and NA
     companionship                  left_out    isolated score
1      Hardly Ever               Hardly Ever Hardly Ever     3
2            Often                     Often       Often     9
3 Some of the time          Some of the time       Often     7
4            Often Presented but no response       Often    NA

The function returns 3, 9, 7 and NA, as expected. Because the Connector program records the same three items, this tested function can be reused on the program's own screening data, where no survey score will exist to compare against. A known-answer test of this kind is the check that remains available when there is no second method.

With the scores corrected and confirmed, the analysis moves to step 5 of Jordan's plan, the construction of the age groups, where a second silent error appears.

Check in action: building the age groups

Age groups are built by cutting a continuous variable at chosen boundaries, and errors in grouping arise at those boundaries. Box 8.6 gives Jordan's prompt for step 5 and Box 8.7 the assistant's reply. Worked code 8.7 shows the code as proposed, the checks that expose its two errors and the correction.

Box 8.6: Prompt

Step 5: create a column called age_group with four groups: 16 to 29, 30 to 44, 45 to 64, and 65 and older.

Box 8.7: Output (abridged)

The function cut() divides a number into intervals at the break points you give it and labels each interval. [The code shown in the R box below.]

RWorked code 8.7: Check 2: the age groups as the assistant proposed them

The code runs without a message. Jordan counts the groups, including NA, and then asks for the youngest and oldest age in each group, which is a quick way to see where each boundary actually falls. The function group_by() splits the data by age group, and summarise() computes one row of results per group.

age_labels <- c("16 to 29", "30 to 44", "45 to 64", "65 and older")

lonely <- lonely |>
  mutate(age_group = cut(age, breaks = c(16, 30, 45, 65, Inf),
                         labels = age_labels))

table(lonely$age_group, useNA = "ifany")

lonely |>
  group_by(age_group) |>
  summarise(n = n(), youngest = min(age), oldest = max(age))
    16 to 29     30 to 44     45 to 64 65 and older         <NA>
        1619         1203          774          439           10

# A tibble: 5 x 4
  age_group        n youngest oldest
  <fct>        <int>    <int>  <int>
1 16 to 29      1619       17     30
2 30 to 44      1203       31     45
3 45 to 64       774       46     65
4 65 and older   439       66    100
5 <NA>            10       16     16

The missing-value count shows the first problem: 10 respondents have no age group, although no ages are missing, and the summary shows that all 10 are aged 16. The youngest and oldest columns show the second problem: the group labelled "16 to 29" contains 30-year-olds, "45 to 64" contains 65-year-olds, and "65 and older" starts at 66. By default, cut() builds intervals that include their upper boundary and exclude their lower one, so the first interval runs from just above 16 up to and including 30, and the labels describe groups that the intervals do not contain.

# CORRECTED: right = FALSE makes each group include its lower boundary
lonely <- lonely |>
  mutate(age_group = cut(age, breaks = c(16, 30, 45, 65, Inf),
                         labels = age_labels, right = FALSE))

lonely |>
  group_by(age_group) |>
  summarise(n = n(), youngest = min(age), oldest = max(age))
# A tibble: 4 x 4
  age_group        n youngest oldest
  <fct>        <int>    <int>  <int>
1 16 to 29      1379       16     29
2 30 to 44      1395       30     44
3 45 to 64       785       45     64
4 65 and older   486       65    100

With right = FALSE, each group includes its lower boundary, every respondent has a group, and the youngest and oldest ages match the labels. The four groups add up to 4,045. The error had removed 47 people aged exactly 65 from the "65 and older" group (486 compared with 439), about one in ten of the group that matters most for the Connector program.

With the scores and the age groups corrected, the remaining steps save the cleaned file, compute the descriptive table and draw two plots, which the next part presents.

The cleaned file, the descriptive table and two plots

The last step of Jordan's plan produces the results that answer the team lead's question. Worked code 8.8 saves the cleaned file and reads it back, and Worked code 8.9 computes the descriptive table and confirms its last column by a second method.

RWorked code 8.8: The cleaned file, saved and read back

The cleaned file keeps the variables the analysis needs and adds screen_positive, which is TRUE for a score of 6 or higher (the Connector threshold). After saving the file with write_csv(), Jordan reads it back and counts its rows, which confirms that nothing was lost on the way to the file.

clean <- lonely |>
  mutate(screen_positive = ucla_score >= 6) |>   # Connector threshold
  select(age, age_group, gender, province, ucla_score, screen_positive)

write_csv(clean, "cscs2021_loneliness_clean.csv")
nrow(read_csv("cscs2021_loneliness_clean.csv", show_col_types = FALSE))
[1] 4045
RWorked code 8.9: The descriptive table, with a second method for its last column

The table removes respondents without a score, groups the rest by age and computes four summaries: the number of respondents, the mean and standard deviation of the score, and the percentage scoring 6 or higher. The mean of a TRUE or FALSE column is the proportion of TRUE values, so 100 * mean(screen_positive) gives a percentage. The survey also contains its own yes or no version of the same cut-off, LONELY_ucla_loneliness_scale_score_y_n, which provides an independent second method.

table1 <- clean |>
  filter(!is.na(ucla_score)) |>
  group_by(age_group) |>
  summarise(n          = n(),
            mean_score = round(mean(ucla_score), 2),
            sd_score   = round(sd(ucla_score), 2),
            pct_6_plus = round(100 * mean(screen_positive), 1))
table1
sum(table1$n)   # respondents with a score

# Second method: the survey team's own yes/no version of the same cut-off
check <- table(clean$age_group, cscs$LONELY_ucla_loneliness_scale_score_y_n)
round(100 * prop.table(check, margin = 1), 1)
# A tibble: 4 x 5
  age_group        n mean_score sd_score pct_6_plus
  <fct>        <int>      <dbl>    <dbl>      <dbl>
1 16 to 29      1251       5.49     1.46       49.6
2 30 to 44      1222       5.4      1.59       45.5
3 45 to 64       677       5.99     2.03       57.9
4 65 and older   433       5.65     2.09       51.7

[1] 3583


               No (3-5) Yes (6-9)
  16 to 29         50.4      49.6
  30 to 44         54.5      45.5
  45 to 64         42.1      57.9
  65 and older     48.3      51.7

The filter removed the 462 respondents without a score, and the four groups contain the remaining 3,583. The second method gives the same four percentages (49.6, 45.5, 57.9 and 51.7), so the percentage column is confirmed. Among respondents aged 65 and older, 51.7 percent scored 6 or higher, compared with 57.9 percent at ages 45 to 64 and 45.5 percent at ages 30 to 44. The mean scores lie close together, from 5.4 to 5.99, while the standard deviations rise with age, from 1.46 in the youngest group to 2.09 in the oldest.

Plotting is the third check, and here it also adds to the description. The script draws both plots with ggplot2 and saves them as image files; Figure 8.3 and Figure 8.4 redraw them from the numbers that R printed. The R code for the first plot also prints the percentages it shows.

Worked code 8.10 gives the ggplot2 code for both plots.

RWorked code 8.10: Two plots with ggplot2

The first block counts respondents at each score in two groups (under 65, and 65 and older) and converts the counts to percentages of each group. In ggplot2, aes() maps columns to the parts of the plot (score on the horizontal axis, percentage on the vertical axis, colour by group), and geom_col() draws bars of those heights. The second plot draws the last column of the table.

dist <- clean |>
  filter(!is.na(ucla_score)) |>
  mutate(group = if_else(age >= 65, "65 and older", "Under 65")) |>
  count(group, ucla_score) |>
  group_by(group) |>
  mutate(pct = round(100 * n / sum(n), 1)) |>
  ungroup()
print(dist, n = 14)

p1 <- ggplot(dist, aes(x = factor(ucla_score), y = pct, fill = group)) +
  geom_col(position = "dodge") +
  labs(x = "UCLA Loneliness Scale score (3 to 9)",
       y = "Percent of age group", fill = NULL,
       title = "Loneliness scores, CSCS 2021") +
  theme_minimal()
ggsave("ucla_distribution_by_age.png", p1, width = 7, height = 4, dpi = 150)

p2 <- ggplot(table1, aes(x = age_group, y = pct_6_plus)) +
  geom_col(fill = "#0B7B6B") +
  geom_text(aes(label = paste0("n = ", n)), vjust = -0.4) +
  labs(x = "Age group", y = "Percent scoring 6 or higher",
       title = "Screen-positive for loneliness, CSCS 2021") +
  theme_minimal()
ggsave("screen_positive_by_age_group.png", p2, width = 7, height = 4, dpi = 150)
# A tibble: 14 x 4
   group        ucla_score     n   pct
   <chr>             <dbl> <int> <dbl>
 1 65 and older          3   100  23.1
 2 65 and older          4    51  11.8
 3 65 and older          5    58  13.4
 4 65 and older          6    81  18.7
 5 65 and older          7    40   9.2
 6 65 and older          8    39   9
 7 65 and older          9    64  14.8
 8 Under 65              3   394  12.5
 9 Under 65              4   439  13.9
10 Under 65              5   748  23.7
11 Under 65              6   797  25.3
12 Under 65              7   357  11.3
13 Under 65              8   167   5.3
14 Under 65              9   248   7.9
Loneliness scores by age, CSCS 2021 (percent of each group) Under 65 (n = 3,150) 65 and older (n = 433) 0 10 20 30 12.5 23.1 3 13.9 11.8 4 23.7 13.4 5 25.3 18.7 6 11.3 9.2 7 5.3 9.0 8 7.9 14.8 9 Three-item UCLA Loneliness Scale score (6 or higher meets the Connector referral threshold) Percent of age group
Figure 8.3. Distribution of UCLA Loneliness Scale scores among respondents under 65 and respondents aged 65 and older, redrawn from the percentages that R printed in Worked code 8.10 (Canadian Social Connection Survey, 2021 wave, 3,583 respondents with a score).
Percent scoring 6 or higher, by age group, CSCS 2021 0 10 20 30 40 50 60 70 49.6% 16 to 29 n = 1,251 45.5% 30 to 44 n = 1,222 57.9% 45 to 64 n = 677 51.7% 65 and older n = 433 Percent of age group
Figure 8.4. Percentage of respondents scoring 6 or higher on the three-item UCLA Loneliness Scale in each age group, redrawn from the descriptive table in Worked code 8.9 (Canadian Social Connection Survey, 2021 wave).

Figure 8.3 shows something the table's means conceal. Respondents aged 65 and older are spread toward both ends of the scale: 23.1 percent have the lowest possible score of 3 and 14.8 percent have the highest score of 9, compared with 12.5 percent and 7.9 percent of younger respondents. Younger respondents cluster in the middle, at scores of 5 and 6. This pattern is consistent with the larger standard deviation in the oldest group, and it means that "about half of older respondents screen positive" describes two quite different subgroups, one with little loneliness and one with a great deal. The plot also serves as a check: drawn from the uncorrected score, the same chart would have shown bars at 10 and 12 on a scale that ends at 9.

Jordan's summary for the team lead reports these figures as descriptive context and states their limits. The survey recruited respondents online and is cross-sectional, so the percentages describe the people who took part in 2021 and are not estimates for the Canadian population or for Cedar Valley, and no statistical test was run, so the differences between age groups are described without any claim that they would persist in another sample.

These results came from code that ran and checks that passed. Whether a descriptive table was the right analysis for the question is a separate decision, which the next part considers.

The choice of analysis belongs to the analyst

The assistant would have written equally fluent code for a regression model or a test across all thirteen provinces and territories. It cannot judge whether an analysis answers the question or whether its assumptions hold, and it tends to accept the framing it is given. Deciding that a descriptive table with a stated threshold answered the team lead's question was Jordan's decision, made with the epidemiologist. The methods themselves are taught in HSCI 410: Lesson 2 covers data cleaning and descriptive analyses, Lesson 3 covers regression, and Lesson 6 covers visualization with ggplot2.

Choosing one analysis also raises the question of what happens when an assistant makes many analyses easy to run, which the next part takes up.

Many analyses and the garden of forking paths

AI makes another analysis almost free. An analyst can ask for loneliness by province, by gender, by income, with a threshold of 6 or 7, with or without the youngest respondents, and receive working code for each in minutes. Reporting the comparison with the most striking difference invites a false positive, because among many comparisons some will differ by chance alone. This is a form of selective reporting, and an untiring assistant makes it easier, often without any intention to mislead.

Gelman and Loken (2014) described a subtler version of the same problem, which they called the garden of forking paths. Every analysis involves choices about which respondents to exclude, how to code variables, which subgroups to compare and which cut-offs to use. If those choices are made after looking at the data, a different dataset could have led to different choices, so even a single analysis can produce a statistically significant result that does not reflect a real pattern. The authors stressed that this can happen without any conscious fishing through the data, through ordinary and well-intentioned judgement applied after the results are visible. Interactive 8.1 shows the simpler case of many analyses: in a world in which no real differences exist, it estimates how often at least one of several analyses crosses the conventional threshold of p < 0.05.

Interactive 8.1: How many analyses before a false positive?

Each square is one simulated study in which no real difference exists. The analyst tries several analyses per study and reports a "finding" if any of them gives p < 0.05. When there is no real difference, a correctly computed p-value is equally likely to fall anywhere between 0 and 1. Move the slider and run the simulation.

Expected share of studies with at least one false positive (independent analyses): 5.0 percent

Press the button to simulate.

With one analysis per study, about 5 percent of studies report a false positive, which is the rate the threshold is designed to allow. With 5 independent analyses the expected share is 22.6 percent, with 10 it is 40.1 percent and with 20 it is 64.2 percent. Real analyses of the same data are usually correlated, which slows this rise, but the direction is the same. The simulation shows only the deliberate version of the problem; Gelman and Loken's point is that choices made after seeing the data inflate the error rate even when only one analysis is run.

A pre-specified analysis plan

The protection against both problems is a pre-specified analysis plan, a short document written and dated before the results are seen. It states the question, the data and the rules for including respondents, the definition of each variable (including how missing-value codes and scale scores are handled), the groups and thresholds, the statistics to be reported, any tests and how they will be interpreted, and how any departure from the plan will be reported. HSCI 410 Lesson 1 shows how to write the plan down, with a date, before modelling begins. A plan has two further uses in AI-assisted work. It can be pasted into the prompt, so that the assistant's code follows the plan, and the assistant can be asked to compare a finished script with the plan and list any differences.

Box 8.8 sets out Jordan's plan for the age-group analysis: the question, the data, the scoring rule, the statistics to be reported and the decision to run no tests.

Box 8.8: Jordan's plan, written before step 6 was run

Question: the percentage of 2021 CSCS respondents scoring 6 or higher on the three-item UCLA Loneliness Scale, in four age groups (16 to 29, 30 to 44, 45 to 64, 65 and older). Data: all 4,045 respondents in the 2021 wave; respondents without a complete score are excluded and counted. Scoring: items scored 1 to 3 and added; "Presented but no response" treated as missing. Statistics: number, mean, standard deviation and percentage scoring 6 or higher in each group; two plots. No statistical tests; no other subgroups. Any further analysis requested later will be reported as additional to this plan.

Box 8.9 asks the reader to decide how Jordan should respond when the assistant offers to run many further comparisons.

Box 8.9: Try it: judge an assistant's offer

After producing Jordan's table, the assistant adds: "Would you like me to test whether loneliness differs by province, gender, relationship status, income, employment, education and eight other variables, and report the strongest associations?" Decide how Jordan should respond, and explain why with reference to Box 8.8 and to the garden of forking paths.

Show a model answerv

Jordan should decline the offer as an addition to this analysis. The plan specified four age groups, descriptive statistics and no tests, and the team lead's question concerned age. Fourteen further comparisons, with the strongest reported, would almost guarantee at least one apparently significant association even if none were real: with 14 independent tests at the 0.05 level, the chance of at least one false positive is about 51 percent. If the team later needs to know whether loneliness differs by, for example, income, Jordan can write a new plan for that question before running it, ask the epidemiologist which variables and methods suit it, and report it as a separate analysis. The assistant's offer is a typical example of how easy AI makes it to multiply analyses, which is why the decision belongs with the analyst.

Jordan's analysis now rests on a plan, a cleaned file, a table confirmed by a second method and two plots. Every step used only public, de-identified survey data described through a dictionary. Section 4 turns to the rules that apply when the data are confidential.

Before that, the reflection and knowledge check that follow review the four checks, the two silent errors and the case for a pre-specified analysis plan.

Reflection

An assistant wrote R code that computed the mean nightly sleep of 2,000 survey respondents as 10.6 hours. The data dictionary says that sleep_hours ranges from 0 to 24 and that the value 99 means "Don't know". In fact, 80 respondents answered Don't know, and the other 1,920 respondents have a mean of 6.9 hours. (a) Describe three checks that would reveal the problem, and say what each one would show. (b) Explain how to correct the code and what the corrected result should be. (c) The team then asks you to "find which groups sleep least", using twelve available demographic variables, with a test for each. Explain the risk in this request and how you would respond.

Model answer

(a) A range check with summary(sleep_hours) would show a maximum of 99, which is impossible for hours in a night. Counting values with table() or sum(sleep_hours == 99) would show 80 respondents with the code. A second method, such as computing the mean after excluding values above 24, would give 6.9 hours, far from 10.6, and a histogram would show a separate spike at 99. A known-answer test on four invented rows, one of them coded 99, would show the code including 99 in the mean.

(b) The fix converts the code to missing before averaging, for example mutate(sleep_hours = na_if(sleep_hours, 99)), followed by mean(sleep_hours, na.rm = TRUE). The corrected mean is 6.9 hours for 1,920 respondents, and the report should state that 80 Don't know answers were excluded. The check is consistent: (1,920 × 6.9 + 80 × 99) / 2,000 = 10.6, which reproduces the wrong figure.

(c) Twelve tests with no real differences would give at least one p-value below 0.05 about 46 percent of the time if the tests were independent, and choosing comparisons after looking at the data adds the forking-paths problem. I would ask what decision the result will inform, write a dated plan that names the groups and the statistics in advance, and report any further comparisons as exploratory.

Minimum 20 characters required.

✓ Reflection saved
Knowledge Check: this section

Question 1: The assistant's scoring code produced UCLA scores of 10 and 12 on a scale that runs from 3 to 9. What caused this?

For a factor, as.numeric() returns the position of each answer's level, and "Presented but no response" is the fourth level. Any respondent with that answer received 4 for the item. Option d is wrong because the survey's score never exceeded 9 and matched the corrected score exactly.

Question 2: Of the 41 respondents scored wrongly by the assistant's code, 22 received scores between 6 and 9. What does this show about checking?

Only 19 wrong scores were impossible values; the other 22 looked plausible. Comparing with the survey's independently computed score found all 41. Every one of them was also at or above the Connector threshold of 6, so the error would have inflated the screen-positive count (option a).

Question 3: The assistant built age groups with cut(age, breaks = c(16, 30, 45, 65, Inf), labels = age_labels). With the default settings of cut(), where do respondents aged exactly 65 end up?

By default cut() uses right = TRUE, so each interval includes its upper boundary and excludes its lower one; 65 falls in the interval ending at 65, labelled 45 to 64. Only values equal to the lowest break (16) become NA, which is option c's error applied too broadly.

Question 4: Which statement about the garden of forking paths (Gelman and Loken, 2014) is accurate?

Gelman and Loken argued that data-dependent choices about exclusions, coding and comparisons can produce misleading significant results even when only one analysis is run and without any conscious fishing, which makes option a too narrow. A pre-specified plan addresses the problem.
Section 4 of 5

Data Governance for AI-Assisted Analysis

⏱ Estimated reading time: 35 minutes
Section 4 of 5

Data Governance for AI-Assisted Analysis

Privacy, research data platforms, safe ways to get help with code, and OCAP®.

Why the rule exists

Confidential data stay out of consumer AI services

Control

Inputs are stored by the provider under its terms.

Law

FIPPA governs personal information held by health authorities.

Consent and agreements

They specify who may see the data, and where.

Re-identification

Combinations of details can identify people.

Classify first

Four kinds of data, four rules

  • Public data can be used with any assistant.
  • Aggregate, non-confidential data can go to an approved institutional assistant.
  • Confidential records are described, never uploaded.
  • Identifiable data go only into systems approved for them.
A research data platform

Population Data BC's conditions

  • Ethics review is complete before a data access request is submitted.
  • Data stewards approve each request, and team members complete privacy training.
  • Agreements limit use to the approved purpose.
  • Analysis takes place in a secure research environment, with imports and exports monitored.
Help without sharing

Four ways to get AI help with confidential data

Dictionary

Names, types and codes, without small counts.

Synthetic stand-in

Invented rows with the real columns and codes.

Local model

Data stay on an approved computer.

Approved institutional tool

Used within the limits of its approval.

OCAP®

First Nations data and AI tools

Ownership

Collective ownership of information.

Control

Control of research and information processes, including AI use.

Access

Access to the community's own data, and decisions about others' access.

Possession

Physical control of the data.

Strengths and limits

What AI does well, and the check that applies

Does well

Writing code from a clear description, and explaining code and error messages.

Does poorly

Missing-value codes, group boundaries, and the choice and interpretation of an analysis.

Each task has a matching check, from row counts and second methods to a dated plan and data classification.

Next steps

Reflection and final assessment

  • The reflection below applies the governance rules to confidential program records.
  • The final assessment has fifteen questions and a final reflection.
  • Try changing one choice in the script, predicting the output before you run it.

Learning Objectives for this section

  • Explain why identifiable or confidential health data must not be uploaded to consumer AI services.
  • Classify data by sensitivity and match each class to the kinds of AI assistance that may be used with it.
  • Describe the conditions that Population Data BC attaches to data access and what they imply for AI-assisted analysis.
  • Use data dictionaries, synthetic data and local models to obtain AI help with code without sharing confidential records.
  • Apply the OCAP® principles to a proposal to use AI tools with First Nations data.

Jordan's analysis used a public, de-identified survey file and described it to the assistant through a data dictionary. Much of the analysis done in public health uses data of a different kind: clinic and program records, surveillance and laboratory data, linked administrative data and research data collected under consent. Personal and confidential data of this kind come with legal duties, consent terms and agreements that decide who may see them and where they may be stored, and those rules apply to AI tools as they apply to any other software. This section sets out the reasons behind the rules, the conditions that a research data platform attaches to access, the ways an analyst can still obtain AI help with code, and the principles that govern First Nations data.

Why identifiable and confidential data stay out of consumer AI services

Personal health information is information about an identifiable person's health or health care, such as a diagnosis, a clinic visit or a screening score linked to a name, a health number or a combination of details that points to one person. Confidential data include personal information of other kinds, research data collected under consent, data shared under an agreement and unpublished work belonging to others. Lesson 3 taught students to classify information before entering it into any tool. For analysis, the classification decides which kinds of AI assistance may be used, as Figure 8.5 summarizes.

Kind of data AI assistance that may be used Public data CSCS public file, published reports Any assistant, including consumer tools; describe or upload Aggregate, non-confidential Counts and rates without small cells An approved institutional assistant, such as the Cedar Valley pilot tool Confidential record-level De-identified records, research data No upload: describe the dictionary, use synthetic data or an approved local model Identifiable data and PHI Names, health numbers, linked records Only systems approved for these data by the data steward or privacy office
Figure 8.5. Data sensitivity and the AI assistance that may be used, from least to most sensitive (PHI: personal health information). An organization's own rules decide the exact boundaries, and the stricter rule applies when two rules differ.

Several reasons support the rule that identifiable and confidential data stay out of consumer AI services, and each applies even when the analyst means only to fix a line of code. The first concerns control: text and files entered into a consumer service are stored by the provider under its terms of service, which Lesson 3 showed can differ markedly from the terms of an institutional tool in how long inputs are kept and whether they may be reviewed or used to improve models. The second is legal. In British Columbia, the Freedom of Information and Protection of Privacy Act (FIPPA) governs personal information held by public bodies, including health authorities. Since amendments in 2021, a public body may disclose personal information for storage outside Canada only in accordance with the regulations, and a new program or system that stores sensitive personal information outside Canada requires a privacy impact assessment, an assessment that no individual's improvised use of a consumer chatbot has received. The third concerns consent and agreements: research participants agree to particular uses of their data by particular people, and ethics approvals and data agreements specify who may access the data and where. The fourth is that de-identified is not the same as anonymous. Removing names does not prevent identification when a combination of details, such as age, sex, community and a rare condition, points to one person, which is a particular risk in small rural communities such as many of those Cedar Valley serves.

Box 8.10 applies these reasons to the Cedar Valley pilot, both to Jordan's survey analysis and to a possible next analysis of the Connector program's own participant records.

Box 8.10: Case: the Cedar Valley pilot's rules applied to analysis (fictional)

The Cedar Valley population health team's enterprise assistant was procured after a privacy impact assessment and operates under the Responsible AI Policy Framework for the B.C. Health Sector (Ministry of Health, June 2026). Under the pilot's rules it may receive public documents, internal non-confidential documents and aggregate data, and it must never receive personal health information or other identifiable data. For Jordan's survey analysis, every step complied: the data were public and de-identified, and the assistant received only dictionary entries and output summaries. Suppose the team's next analysis used the Connector program's own participant records, which hold names, dates of birth, clinic, connector notes and UCLA scores at referral and follow-up. The assistant could then help with code written against the dictionary and tested on synthetic stand-ins, while the records themselves stayed on the health authority's secure network and the code ran there. Jordan's tested score_item() function from Section 3 would carry over unchanged.

The same reasons shape the conditions that research data platforms attach to access, and the next part describes them through the example of Population Data BC.

Research data platforms: the example of Population Data BC

Population Data BC (PopData) supports research access to linked administrative data in British Columbia, as HSCI 207 Lesson 1 explained. Its access process shows the conditions that research data platforms typically attach to sensitive data. According to PopData's guidance for researchers, research ethics and peer review must be completed before a data access request is submitted. Data stewards, the people or bodies responsible for each data source, review and approve or reject each request. The principal investigator and every team member who needs access must complete PopData's Privacy Training 101 for Researchers, and the team signs a research agreement and confidentiality pledges, with the data to be used only for the purpose requested. The analysis takes place inside PopData's secure research environment, which is reached only through an encrypted virtual private network with two-factor authentication; all imports into and exports from the environment are monitored, ethics approval must be kept current, and research materials must be submitted to the data stewards before public dissemination.

These conditions answer the question of AI use in practice. Record-level data cannot be moved from a secure environment into an outside AI service, since the agreement restricts their use and the platform monitors what leaves the environment. AI help with code therefore happens outside the environment, through the data dictionary and synthetic stand-ins, and the finished code is brought into the environment through the platform's own process. Unreleased output belongs under the same rules: an analyst who wants help interpreting a table should wait until the data stewards have approved its release, or describe its structure without its numbers. Any AI tool offered inside a secure environment by the platform itself is a different matter, governed by the platform's rules.

Keeping the records inside the secure environment still leaves several ways to obtain AI help with code, which the next part sets out.

Getting AI help without sharing the data

An analyst working with confidential data can still use an assistant for most of the coding work described in this lesson. The four approaches below can be combined, and all of them keep the records where the rules require them to be.

The data dictionary approach of Section 2 gives an assistant the names, types, permitted values and missing-value codes it needs, without a single record. For confidential datasets, the dictionary itself should be checked before it is shared, because a dictionary that lists counts for each category can reveal small groups. Variable names and codes are usually safe to share; frequencies for rare categories may not be.

Synthetic data are invented records that copy the structure of the real data: the same column names, types and codes, with made-up values. A synthetic stand-in lets an analyst share data with an assistant, get code working and reproduce an error without exposing anyone. The simplest stand-in, shown in Worked code 8.11, uses random values. More realistic synthetic data that preserve the statistical relationships in the real data can be generated with tools such as the R package synthpop (Nowok, Raab and Dibben, 2016); because such data are derived from real records, they can carry some disclosure risk and are subject to the data steward's approval.

A local model is an open-weight language model that runs on the analyst's own computer, for example through a desktop application such as Ollama, so that prompts and data never leave the machine. Local models remove the transfer of data to a provider. They are usually smaller than the models behind commercial assistants and make more errors in code, so every check in Section 3 still applies, and their use with confidential data still requires that the computer and the arrangement be approved for those data.

An organization can procure an assistant under terms it has negotiated, after a privacy impact assessment, as the Cedar Valley Health Authority did for its pilot. Such a tool may be approved for more kinds of data than a consumer service, but its approval has limits, and the limits are written into the rules for its use. Approval for aggregate data does not extend to records, and a user who is unsure which class a dataset belongs to asks the privacy officer before entering it.

Worked code 8.11 builds the simplest kind of synthetic stand-in: six invented respondents with the real column names and answer codes, and values drawn at random.

RWorked code 8.11: A synthetic stand-in that can be shared with an assistant

This block creates six invented respondents with the real column names and the real answer codes, including the missing-value code. The function sample() draws values at random from the options given, and set.seed() fixes the random draw so that the same six rows appear every time the script runs. No row corresponds to a real person, so the stand-in can be pasted into any assistant together with a question about code.

set.seed(488)
fake <- data.frame(
  age           = sample(16:100, 6, replace = TRUE),
  gender        = sample(c("Man", "Woman", "Non-binary",
                           "Presented but no response"), 6, replace = TRUE),
  companionship = sample(c("Hardly Ever", "Some of the time", "Often",
                           "Presented but no response"), 6, replace = TRUE))
fake
  age                    gender             companionship
1  34                Non-binary Presented but no response
2  52                Non-binary Presented but no response
3  49                       Man                     Often
4  53 Presented but no response Presented but no response
5  54                Non-binary               Hardly Ever
6  38                       Man          Some of the time

The values are random, so the proportions mean nothing (three of the six "respondents" are non-binary, for example), and a stand-in of this kind is useful only for testing code. Its value lies in the codes: because "Presented but no response" appears in two columns, any code tested on these rows must deal with it, which is exactly the case that the assistant's scoring code mishandled in Section 3.

Each of these approaches keeps confidential records where the rules require them to be. Data about First Nations communities carry further obligations, set out in the principles of OCAP®, which the next part describes.

OCAP® and First Nations data

The First Nations principles of OCAP® were established in 1998 by First Nations leadership, and OCAP® is a registered trademark of the First Nations Information Governance Centre (FNIGC). The four principles set out how First Nations data are to be collected, protected, used and shared. HSCI 207 Lessons 4 and 5 introduced them in the context of engagement and research ethics, and they apply with particular force to AI tools, which move data and derived information across organizational and national boundaries.

The four flip cards that follow describe the principles of ownership, control, access and possession.

OwnershipClick to explore
ControlClick to explore
AccessClick to explore
PossessionClick to explore

In practice, an analyst who works with First Nations data under a data sharing agreement asks the community's data governance body, through the channels the agreement names, before using any AI tool in the analysis, including tools that receive only a description of the data. A data sharing agreement can address AI explicitly by stating whether AI tools may be used, which ones, with what kinds of data, and how the community will see the code and the results. The safe default for an analyst is to treat any AI use that the agreement does not mention as unapproved until the community has decided. Lesson 10 extends these ideas with the CARE Principles for Indigenous Data Governance and relates them to the commitments in the B.C. framework.

Box 8.11 asks the reader to classify five requests from a health authority's analytics team under rules like those of the Cedar Valley pilot.

Box 8.11: Try it: classify five requests

For each of the following requests from members of a health authority's analytics team, decide whether it is acceptable, acceptable with conditions, or not acceptable, under rules like those of the Cedar Valley pilot (an approved enterprise assistant for public, internal non-confidential and aggregate data; never personal health information or other identifiable data). (1) Pasting the data dictionary of the program's referral database, with variable names and codes but no counts, into the enterprise assistant to get help writing code. (2) Uploading a spreadsheet of 300 referral records with names removed but dates of birth and postal codes kept, to a free consumer chatbot, to find a bug. (3) Asking the enterprise assistant to help interpret a table of referral counts by clinic, in which two clinics have fewer than five referrals. (4) Running an open-weight model on a personal laptop to write code for the referral records. (5) Pasting a synthetic stand-in with the real column names into the enterprise assistant.

Show a model answerv

(1) Acceptable: a dictionary of names and codes contains no records, provided it lists no counts for small categories. (2) Not acceptable: the records are confidential and potentially identifiable through dates of birth and postal codes, and a consumer chatbot is outside the pilot entirely; a synthetic stand-in with the same columns would serve the same purpose. (3) Acceptable with conditions: aggregate data are permitted, but counts below five can identify people in small communities, so the small cells should be suppressed or combined before the table is shared. (4) Not acceptable as described: a local model keeps data on the machine, but a personal laptop is not an approved environment for program records; the analyst would need the privacy office's approval for both the model and the computer. (5) Acceptable: synthetic rows describe no real person, which makes this the standard way to get help with code for confidential data.

Box 8.12 draws the lesson together. Table 8.5 sets out, for six tasks in data analysis and coding in R, how AI performs and the check that applies.

Box 8.12: Strengths and limits: AI for data analysis and coding in R

Table 8.5. How AI performs at six tasks in data analysis and coding in R, and the check that applies to each.

TaskHow AI performsThe check that applies
Writing R code for a clearly described stepFast and usually correct when the data are described through the dictionary; guesses names and codes when they are notRun each step and confirm row counts and missing values
Explaining code and error messagesStrong: clear explanations at any level of detailTest the explanation by running the code or a changed version of it
Handling missing-value codes and scoring rulesWeak unless told the codes; the code can run and still give wrong answersRecompute by a second method and run a known-answer test
Grouping and filteringProne to errors at boundaries and to silent loss of rowsCheck the youngest and oldest value in each group and count rows after each filter
Choosing and interpreting the analysisRuns any analysis requested and offers more; tends to accept the user's framingA pre-specified analysis plan and the methods taught in HSCI 410
Working with confidential dataConsumer tools are not permitted; approved tools have stated limitsClassify the data first; use the dictionary, synthetic stand-ins or an approved local model

An assistant allows a student with no programming experience to carry out a real analysis in R and to learn the language while doing it, and the checks in this lesson make the results dependable. A good next step is to rerun the lesson's script with one change, such as a threshold of 7 or two age groups in place of four, and to predict each output before running it.

This section has set out why identifiable and confidential data stay out of consumer AI services, the conditions that research data platforms attach to access, the ways to obtain help with code without sharing the data, and the principles that govern First Nations data. The reflection and knowledge check that follow review these rules.

Reflection

A health authority's population health team uses an enterprise AI assistant that was approved, after a privacy impact assessment, for public documents, internal non-confidential documents and aggregate data, and never for personal health information or other identifiable data. An analyst must compare loneliness scores at referral and at six months for 640 participants in a community program. The program records contain names, dates of birth, postal codes, clinic, notes written by staff, and three loneliness items at each time point. The records of 85 participants come from a First Nation whose data are shared with the health authority under a data sharing agreement that does not mention AI tools. The OCAP® principles are Ownership (the community owns its information collectively), Control (the community has the right to control research and information processes that affect it), Access (the community must have access to its data and decides who else may) and Possession (physical control of the data, the means by which ownership is protected). Explain what the analyst may and may not do with the assistant, how the analyst can still get help writing and testing the R code, and what the OCAP® principles require for the 85 participants' records.

Model answer

The records are identifiable and contain personal health information, so none of them may be entered into the assistant, even with names removed: dates of birth and postal codes can identify people, and staff notes may contain anything. The analyst may give the assistant the data dictionary (variable names, types, codes and how the loneliness items are scored), provided it contains no counts for small categories, and may ask it for code in small steps. To test that code, the analyst can build a synthetic stand-in with the real column names and codes and invented values, including missing-value codes, and share it with the assistant. The code then runs on the real records on the health authority's secure network. Aggregate results, such as mean scores by time point with small cells suppressed, may be discussed with the assistant once they are approved for that use.

For the 85 First Nation participants, the agreement does not mention AI, so the analyst should treat any AI use as unapproved until the community's data governance body has decided. Control means the community decides whether AI tools are used, even for code that only touches its records. Possession means the records must not leave the agreed environment. Access means the community can see the code and the results, and Ownership means that findings about the community are reported as the agreement and the community direct.

Minimum 20 characters required.

✓ Reflection saved
Knowledge Check: this section

Question 1: An analyst wants help fixing a bug and considers pasting 50 program records, with names removed but dates of birth and postal codes kept, into a free consumer chatbot. What should the analyst do?

Dates of birth and postal codes can identify people, so the records are confidential, and a consumer chatbot is not approved for them. A synthetic stand-in reproduces the structure needed to fix the bug without exposing anyone. Deleting a conversation afterward (option b) does not undo the transfer.

Question 2: Which set of conditions does Population Data BC attach to data access, according to its guidance for researchers?

PopData requires ethics review before a data access request, data steward approval, Privacy Training 101 for Researchers, a research agreement and confidentiality pledges, and analysis inside a secure research environment reached through an encrypted connection, with imports and exports monitored.

Question 3: Under the OCAP® principles, what does Possession refer to?

Possession, or stewardship, refers to the physical control of data and is the mechanism by which First Nations ownership is asserted and protected. Uploading First Nations data to an external AI service moves the data out of that control. Option b reverses the principle of collective First Nations ownership.

Question 4: Which statement about running an open-weight model locally on an analyst's computer is accurate?

A local model avoids sending data to a provider, but local models are usually smaller and make more coding errors, so every check still applies, and the computer and arrangement must be approved for the data. Running locally does not make a model more accurate (option c).
Section 5 of 5

Final Assessment

⏱ Estimated time: 25 minutes

Bringing It All Together

This lesson followed one analysis from a data dictionary to a cleaned file, a descriptive table and two plots, with an AI assistant writing most of the R code and a student with no programming experience directing and checking it. The evidence on AI and programming speed showed why that division of labour matters. Gains are large on well-defined tasks of a familiar kind and can disappear when the user must review complex code, and people's sense of how much an assistant helped them is an unreliable guide.

The workflow kept every step small enough to read: describe the data through its dictionary, ask for one step and what to expect, run it, read the output, check it and save it. Two of the assistant's steps ran without any message and were wrong. A missing-value code was counted as an answer worth 4, and the age groups put 65-year-olds in the wrong group. A range check, a comparison with an independently computed score, a known-answer test and a look at the youngest and oldest age in each group found both errors, and the corrected code reproduced the survey team's own results exactly. A dated analysis plan then guarded against the easiest temptation that AI creates, which is to run many analyses and report the most favourable.

The same workflow carries over to confidential data with one change: the records stay where the rules require. Data dictionaries, synthetic stand-ins, approved institutional tools and local models give an analyst most of the benefit of AI help with code, while privacy law, data access agreements and the OCAP® principles decide where the data may go and who decides how they are used.

Key Takeaways from this lesson

  • AI coding assistance comes in four main forms (chat assistants, editor assistants, coding agents and code-executing data tools) that differ in what they can see and in who runs the code.
  • In a controlled experiment, programmers with an assistant finished a defined task 55.8 percent faster (Peng and colleagues, 2023), while in a randomized trial experienced developers on their own large projects took 19 percent longer and believed they had been faster (METR, 2025).
  • The gains from AI coding tools depend on the task and on the user's ability to judge the code, so a beginner benefits most by reading, running and checking every step.
  • An assistant knows only what it is told about the data, and the data dictionary is the most efficient way to tell it, without sharing a single record.
  • Asking for code in small steps, with the expected output stated in advance, keeps each block short enough to read and makes errors easy to locate.
  • Code that runs without an error message can still give a wrong answer, most often where the code meets missing-value codes, scoring rules and group boundaries.
  • Four checks catch most silent errors: confirming row counts and missing values, recomputing by a second method, plotting the data and testing on data with a known answer.
  • AI makes it easy to run many analyses, and choices made after seeing the data can produce false positives even in a single analysis, so a dated, pre-specified analysis plan comes before the results.
  • Identifiable and confidential health data stay out of consumer AI services; dictionaries, synthetic stand-ins, approved tools and local models provide AI help with code while the records stay in place.
  • The OCAP® principles give First Nations control over whether and how AI tools are used with their data, and an agreement that does not mention AI should be treated as not permitting it until the community decides.

Core Concepts Reviewed

Section 1: forms of AI coding assistance, what an assistant can see, the controlled experiment of Peng and colleagues (2023), the METR (2025) randomized trial, the gap between perceived and measured speed, and the jagged frontier of AI help with R.

Section 2: the six-step workflow, describing data through a data dictionary, small steps with expected output, reading R notation, warnings and errors, asking for explanations, scripts, Quarto documents and the AI use log.

Section 3: silent errors, the four checks, a missing-value code counted as a number, boundary errors in cut(), second methods and known-answer tests, descriptive tables and plots, the analyst's responsibility for the analysis, the garden of forking paths and the pre-specified analysis plan.

Section 4: personal health information and data classification, reasons to keep confidential data out of consumer AI services, FIPPA, the access conditions of Population Data BC, synthetic data, local models, approved institutional tools and the OCAP® principles.

The final reflection asks you to bring the lesson's workflow and checks together for a public dataset and to adapt them for confidential records.

Reflection

Describe, step by step, how you would use an AI assistant to produce a descriptive table and one plot in R from a public survey file, from the first prompt to the saved script, and name the check you would apply at each step. Assume the file has a data dictionary, a text code for questions left blank, a scale score already computed by the survey team, and an age variable that you need to group into four age groups. Then explain which parts of your workflow would change if the data were confidential program records, and which parts would stay the same.

Model answer

My first prompt describes the data through its dictionary entries, states the question and asks for a plan and the code for step 1 only. Step 1 loads the data and keeps the rows I need; I check the row count against the dictionary. Step 2 selects and renames the variables, and glimpse() confirms names and types. Step 3 converts the blank-answer code to NA, and the count of missing values must rise by exactly the number of codes. Step 4 computes my own score from the items with an explicit rule; I compare it with the survey team's score as a second method and run a known-answer test on a few invented rows. Step 5 creates the age groups; I check the youngest and oldest age in each group and confirm that no one is left without a group. Before step 6 I write a dated plan naming groups and statistics. Step 6 produces the table, confirmed by a second method, and the plot, inspected for impossible values. I save the script, rerun it from a fresh session and log the assistant's part.

With confidential records, the steps and checks would stay the same. What would change is where the data go: no record would enter the assistant, the dictionary would be checked for small counts, code would be tested on a synthetic stand-in, the analysis would run in an approved environment, and any First Nations data would need the community's approval for AI use.

Minimum 30 characters required.

✓ Reflection saved

Final Knowledge Assessment

Final Assessment, this lesson: AI for Data Analysis and Coding in R (15 Questions)

Question 1: Which statement best summarizes what the two productivity studies in this lesson show when read together?

Peng and colleagues found large gains on a defined task, while METR found a slowdown for experts on their own complex projects, together with a mistaken sense of speed. Both studies were randomized (option d), so the difference lies in the tasks and the users.

Question 2: An assistant proposes ten lines of R that run without any warning or error. What is the most dependable next action?

Code can run cleanly and still be wrong, as both errors in Section 3 showed. Rerunning identical code (option d) reproduces the same error, and an assistant's assessment of its own code is not an independent check.

Question 3: Jordan's table showed that 51.7 percent of respondents aged 65 and older scored 6 or higher. Which sentence reports this accurately?

The survey recruited respondents online and is cross-sectional, so the figure describes respondents and is not a population estimate for Canada or Cedar Valley (options a and b). No test was run, so a claim of significance (option c) is unsupported.

Question 4: Which of the following is a missing-value code?

A missing-value code is a value that stands for a missing answer and must be converted before analysis. NA is R's own marker for a missing value, which R already treats as missing (option a). A score of 3 and an age of 100 are valid values.

Question 5: In the corrected age-group code, what does the argument right = FALSE change?

With right = FALSE, cut() builds intervals that include their lower boundary and exclude their upper one, so 16-year-olds join the youngest group, 30-year-olds the second and 65-year-olds the oldest, matching the labels.

Question 6: Which form of coding assistance is most likely to hide the code behind its answer unless the user asks to see it?

Code-executing data tools run code out of sight and return tables or answers directly, so the user must ask for the code to check or reproduce a result. They also receive the uploaded data, which raises the governance issues in Section 4.

Question 7: Why is a known-answer test useful for the score_item() function even after the comparison with the survey's own score passed?

The Connector program records the same three items, and its data will have no independent score to compare with. A function tested on invented rows with hand-worked answers can be reused with confidence. The survey's score matched the corrected score exactly (option b).

Question 8: An analyst plans to compare loneliness across 14 variables and report the strongest association. If no real associations exist and the tests are independent, what is the approximate chance of at least one p-value below 0.05?

The chance is 1 − 0.9514, about 51 percent. About 5 percent (option a) is the false-positive rate for a single test; it grows with each additional test.

Question 9: Which of the following belongs in a pre-specified analysis plan?

A plan is written and dated before results are seen, and it fixes the choices that could otherwise be made after looking at the data. The other options all depend on results that the plan is meant to precede.

Question 10: Which approach lets an analyst get AI help with code for confidential program records while keeping the records in place?

The dictionary and a synthetic stand-in give the assistant the structure it needs without any real record, and the finished code then runs where the records are kept. The other options all move confidential records to places they are not approved to go.

Question 11: Under British Columbia's Freedom of Information and Protection of Privacy Act, as amended in 2021, what applies when a public body stores sensitive personal information outside Canada through a new program or system?

The 2021 amendments replaced the strict data-residency rule: disclosure for storage outside Canada must follow the regulations, which require a privacy impact assessment for new programs, projects or systems that store sensitive personal information outside Canada. Option a describes the rule before 2021 too broadly.

Question 12: Which OCAP® principle most directly holds that a First Nation decides whether AI tools may be used in research that affects it?

Control affirms the right of First Nations and their representative bodies to control all aspects of research and information management processes that affect them, which includes the choice of analytic tools. Ownership concerns the collective relationship to the data, and Possession concerns their physical control.

Question 13: In Figure 8.3, 23.1 percent of older respondents scored 3 and 14.8 percent scored 9, compared with 12.5 and 7.9 percent of younger respondents. What does this add to the table of means?

The older group has more people at both extremes, consistent with its larger standard deviation (2.09), so a mean of 5.65 and a single percentage describe two quite different subgroups. Option d states the reverse of what the plot shows.

Question 14: A student wants to understand a ten-line R script written by an assistant. Which prompt is most likely to build the student's own understanding?

A line-by-line explanation of the exact code being run teaches what each function and argument does, and the student can test the explanation by running the code. A yes or no verdict (option b) adds no understanding and is not an independent check.

Question 15: Which statement about the METR (2025) trial is accurate?

METR's 16 experienced developers took 19 percent longer on real tasks in their own large projects, and METR described the result as a snapshot of early-2025 tools in one setting, not a finding about all developers (option a). Option c describes Peng and colleagues (2023).
✦ Complete the final reflection above before submitting

Congratulations!

You have successfully completed this lesson: AI for Data Analysis and Coding in R.

You can now use an AI assistant to carry out a real analysis in R: describing a dataset through its data dictionary, requesting and reading code in small steps, asking for explanations of functions and errors, checking results with row counts, second methods, plots and known-answer tests, and protecting confidential and First Nations data while still getting help with code. A good way to keep building these skills is to rerun this lesson's script with one change at a time, predicting each output before you run it, and then to try the same workflow on another variable in the survey's data dictionary.

Lesson 9, AI in Public Health Practice, moves from individual analysis to the uses of AI across public health: surveillance and epidemic intelligence, prediction models and decision support, language models in health communication, and the everyday tasks in which AI already saves public health staff time, together with the rules that govern them.

Continue to Lesson 9 →
Reference

Glossary: Key Terms, People & Frameworks

📚 Reference page, available throughout the lesson

Key terms, tools and people from this lesson on using AI assistants to write, understand and check analyses in R.

Core Concepts
AI coding assistant A tool built on a language model that writes, explains or fixes code from a plain-language request, for example a chat assistant or an assistant built into a code editor.
Coding agent An AI system that plans a programming task, edits files across a project, runs the code and reads the results, repeating these steps until it judges the task complete.
Code-executing data tool An AI tool that writes and runs code on a file the user uploads and returns tables, charts or answers directly, often without showing the code unless asked.
Data dictionary A document that lists each variable's name, meaning, type, permitted values and missing-value codes. Describing data to an assistant through its dictionary avoids sharing records.
Script A plain text file of instructions that a program runs in order. An R script records every step of an analysis so that it can be rerun and checked.
Quarto document A file with the extension .qmd that combines text and chunks of code; Quarto runs the code and inserts its output into an HTML, Word or PDF report.
Function A named operation in R that takes inputs, called arguments, inside parentheses and returns a result, for example nrow(cscs).
Pipe The R operator |>, which passes the result on its left into the function on its right; it can be read as "and then".
Factor R's type for a categorical variable, stored as categories called levels in a fixed order. Converting a factor with as.numeric() returns the positions of its levels.
Missing-value code A value that stands for a missing answer, such as 999 or the text "Presented but no response". It must be converted to NA before analysis.
Silent error An error in code that runs without any warning or error message and produces a plausible but wrong result.
Second-method check Recomputing a result in an independent way, such as a different function or a variable computed by someone else, and confirming that the two results agree.
Known-answer test Running code on a small invented dataset whose correct result has been worked out by hand, to confirm that a function or rule behaves as intended.
Boundary error An error in which values at the edge of a group are placed in the wrong group, such as 65-year-olds counted in a 45 to 64 group.
Garden of forking paths Gelman and Loken's term for the way data-dependent choices about exclusions, coding and comparisons can produce false positives even when only one analysis is run.
Selective reporting Running several analyses and reporting only those with favourable or significant results, which inflates the rate of false positives.
Pre-specified analysis plan A dated document, written before results are seen, that fixes the question, inclusion rules, variable definitions, groups, thresholds and planned statistics.
Personal health information Information about an identifiable person's health or health care, such as a diagnosis or screening score linked to a name, health number or identifying details.
De-identified data Data from which direct identifiers such as names have been removed. Combinations of remaining details can still identify people, so de-identified records are not anonymous.
Synthetic data Invented records that copy the structure of real data (names, types and codes), and sometimes their statistical properties, so that code can be developed without real records.
Local model An open-weight language model run on the analyst's own computer, so that prompts and data do not leave the machine.
Secure research environment A protected computing environment in which approved researchers analyze sensitive data without removing them; Population Data BC's is reached through an encrypted connection with two-factor authentication.
Data steward The person or body responsible for a data source, who reviews requests for access and approves the release of results.
Frameworks & Tools
R A free, open-source language for statistics and graphics, created at the University of Auckland in the early 1990s and extended by thousands of packages.
dplyr An R package for working with data frames through functions such as filter(), select(), mutate(), group_by() and summarise().
ggplot2 An R package that builds plots by mapping columns of a data frame to visual properties and adding layers such as bars or points.
RStudio and Positron Code editors for R developed by Posit. In 2026, Posit Assistant can work inside both and read the analyst's live R session.
Canadian Social Connection Survey (CSCS) A survey of social connection and wellbeing in Canada whose public, de-identified data are used across the series; its 2021 wave has 4,045 respondents.
Three-item UCLA Loneliness Scale A short loneliness measure in which three items are scored 1 to 3 and added, giving totals from 3 to 9; the Cedar Valley Connector program refers people who score 6 or higher.
Population Data BC A British Columbia organization that supports research access to linked administrative data, with ethics review, data steward approval, privacy training, agreements and a secure research environment. See popdata.bc.ca.
OCAP® The First Nations principles of Ownership, Control, Access and Possession, established in 1998; OCAP® is a registered trademark of the First Nations Information Governance Centre.
Freedom of Information and Protection of Privacy Act (FIPPA) British Columbia's law on personal information held by public bodies, including health authorities. Since 2021, storage outside Canada must follow the regulations, with a privacy impact assessment for sensitive information in new programs.
Key People
Sida Peng First author, with Eirini Kalliamvakou, Peter Cihon and Mert Demirer, of the 2023 controlled experiment in which programmers with GitHub Copilot finished a defined task 55.8 percent faster. See arXiv 2302.06590.
Joel Becker First author, with Nate Rush, Beth Barnes and David Rein, of METR's 2025 randomized trial in which experienced open-source developers took 19 percent longer with AI tools. See arXiv 2507.09089.
Andrew Gelman Statistician at Columbia University who, with Eric Loken, described the garden of forking paths (Gelman and Loken, 2014, American Scientist).
Eric Loken Statistician and co-author with Andrew Gelman of "The Statistical Crisis in Science" (2014), which explained how data-dependent analysis produces false positives.
Hadley Wickham Statistician and chief scientist at Posit who created the R packages ggplot2 and dplyr.
Ross Ihaka Statistician who, with Robert Gentleman, created the R language at the University of Auckland in the early 1990s.
Robert Gentleman Statistician who, with Ross Ihaka, created the R language at the University of Auckland in the early 1990s.
No matching entries. Try a different search term.