Telling jstats about missing values
This page is a hands-on run-through of one small move that protects every statistic you compute: telling jstats which numbers in a column are not really data, and choosing the form jstats should keep that information in.
This page is about coded missing values — a −99 or a 999 standing in for a refusal or a “not applicable.” You need this page if the data you use, now or later, may carry such codes, and most survey data do, whatever the format: a CSV file (comma-separated values, the plainest spreadsheet format) brings the codes with nothing to say what they mean, and a file from SPSS, Stata, or SAS usually arrives with them declared, but not always. If your data have no missing values at all, leave this page for later. Missing entries that are simply blank are a different matter. In a variable of numbers they load as R’s ordinary NA (short for “not available”) and need no declaring; in a text variable they load as a category of their own, <blank>, which jdeclare_missing() makes missing with codes = "". Either way, every analysis still has to decide what to do with them, a topic of its own that the books cover in full. No copy of SPSS, Stata, or SAS is needed to follow along; those names appear only because the three ways of recording a missing value are named after the programs that introduced them.
To follow along you’ll need:
- R, RStudio, and jstats installed. If they’re not, work through Get Started, Steps 1-5 first and come back.
- An RStudio Project open, so your working directory and files are already set up.
- A new, empty R script open (File > New File > R Script). You’ll copy each numbered block into it, one at a time, running each before moving on.
The blocks on this page build one script. Paste each block into the same R script file as you reach it, in order — by the end, the whole file runs cleanly from top to bottom, except in the few places where a comment says an error is deliberate (so you can see what one looks like).
A few mechanics worth knowing:
- Every block starts with a dashed comment header (
## ---- 1. ... ----). It marks where one block ends and the next begins in your growing script — and RStudio reads those headers as sections, so they appear in the script’s outline for easy jumping around. - Run lines the way the RStudio Orientation showed: cursor on the line, then Ctrl+Enter (Cmd+Enter on a Mac) or the Run button; highlight several lines to run them together. Experiment freely as you go — change a number, rerun a line, poke at things. Building the script yourself is the point.
- We recommend working through the blocks one at a time first — you learn far more by building the script yourself and watching each piece run than by reading a finished file. Once you’ve done that (or if you’d rather grab everything at once), the complete, clean script — without your experiments, and finished so it runs top to bottom — is available to download at the bottom of the page.
This box is the same on every hands-on page in this group, so once you’ve read it here, you can skip it elsewhere.
You don’t need to memorize any of this yet. Right now the goal is simply to run each line and watch what happens — the ideas settle in as you go. A few things R doesn’t care about, so you don’t have to either: which line numbers your code lands on, and how many blank lines sit between commands — paste each block anywhere below your previous code and it all works the same. Lines starting with # are comments: notes for humans that R skips entirely (extra hash marks, like ##, are still just a comment). And running a line a second time is almost always harmless — you’ll simply get the same result again. If a line doesn’t work, check that jstats loaded without errors, then try it again.
Experimenting means the occasional wrong turn — a setting changed, the practice dataset overwritten, output that suddenly looks different. When something feels off and you’d like a clean footing without restarting RStudio, run these two lines in the Console:
## Put jstats' output settings back to their defaults
joutput("standard") # output level, and decimal places back to 3
## Point saving and loading back at your working directory
joptions(data.dir = NULL)That resets jstats itself. To clear away objects you created while following along — data frames like MyData_r, or a value like MyObject — the simplest way is the little broom icon at the top of the Environment pane (upper-right): one click sweeps the pane clean. It clears everything in the Environment, so use it when you’re between run-throughs, not in the middle of one. (You can also remove a single object by name, e.g. rm(MyData_r).) The built-in practice datasets come right back whenever you want them: jload("community"), or jload("clinic").
This box is the same on every hands-on page in this group.
1. Load the data, and see what jstats already noticed
We’ll work on clinic, the deliberately messy practice dataset that ships with the package. The first two lines are a pair you wouldn’t normally write — clinic is available by name the moment jstats is loaded, and juse() usually comes once per session — but together they guarantee a fresh copy, whatever you did to clinic on another page. Watch what jstats says as the data load, then ask for the descriptives of MoodRating, a column that is meant to run from 1 to 10.
## ---- 1. Load the data, and see what jstats already noticed ------------
## Start from a fresh copy of the clinic practice dataset
## You wouldn't normally write this pair: clinic is there by name as soon
## as jstats is loaded, and juse() usually comes once per session. The
## reload makes this page start clean whatever you did on another page
jload("clinic", overwrite = TRUE)
juse(clinic)
## MoodRating is meant to run from 1 to 10. Ask for its descriptives
jdesc(MoodRating)
The command lines are shown; the standalone ## comment lines aren’t – running a comment just echoes it back and does nothing.
ConsoleTerminal×Background Jobs×> jload("clinic", overwrite = TRUE) Loaded the jstats example dataset 'clinic' 4 variables have SPSS-style missing values: Stress: -99 ["Refused"], -98 ["Don't know"] SleepHours: -99 ["Refused"], -98 ["Don't know"] Medication: -99 ["Refused"] Anxiety4: -99 ["Refused"], -98 ["Don't know"] jstats analyses treat these codes as missing. Base R functions do not. To make them missing in base R as well, convert: jconvert(clinic, to = "stata", modify = TRUE) Missing-value codes detected: MoodRating: -99 (4), -98 (3) Anxiety2: -99 (3), -98 (3) These codes match the missing.convention.codes setting (-99, -98, -97) but are not formally declared, so they are not treated as missing. Declare them as missing if they are; leave as-is if real. No missing-value convention is selected, so these codes cannot be declared yet. Choose one for this session: joptions(missing.convention = "stata") Lowercase markers behave as true NAs in base R. Recommended if you also run base R or AI-generated code. joptions(missing.convention = "spss") Codes stay visible numbers; jstats treats them as missing. Base R does not. joptions(missing.convention = "sas") Like Stata, with uppercase markers (.A-.Z). To make the choice permanent, put the same line in your .Rprofile. Then, to declare one variable's codes as missing: jdeclare_missing(clinic, MoodRating, codes = c(-99, -98), modify = TRUE) To declare and label them in one call, replacing the placeholder names: jdeclare_missing(clinic, MoodRating, codes = c("label1" = -99, "label2" = -98), modify = TRUE) > juse(clinic) Default data frame set to: clinic > jdesc(MoodRating) Descriptive Statistics Using default data frame: clinic 70 Cases in the 1 Variable Pool Variable Total Non_missing Min Max Mean SD ---------- ----- ----------- --- --- ------ ------ MoodRating 70 70 -99 9 -4.943 31.477 > |
Two things happened. As clinic loaded, jstats looked over its missing values and reported on two groups of columns. Four of them — Stress, SleepHours, Medication, and Anxiety4 — arrived with their codes already declared, and those codes are listed with labels such as “Refused”. Two others, MoodRating and Anxiety2, carry codes that match the numbers jstats watches for as missing-value codes, but that nobody has declared. For those, jstats says that no missing-value convention is chosen yet, lists the three to choose from, and then offers a ready-to-run example, naming MoodRating. Then the descriptives showed why that second group matters: the minimum of MoodRating is a large negative number and the mean has been dragged well below the 1–10 range. Those stand-in codes are being averaged in as if they were genuine ratings — the usual state of affairs after a plain CSV or Excel import, where the file carries no record of what the numbers mean.
Open that same CSV in SPSS and the codes sit in Data View looking like any other number, Variable View shows no missing values defined, and Descriptives averages them in without a word. The check jstats just ran on arrival is one the commercial programs don’t make: they trust the file. The four columns that arrived already declared are the equivalent of a .sav whose Variable View was filled in properly — jstats read those definitions and kept them.
2. What a missing-value convention is
Before we fix MoodRating, a piece of background that the rest of the page rests on. Every statistics program needs some way to mark a value as missing while also remembering why — refused, don’t know, not applicable. Base R was not built with that need in mind: it has a single NA that says “missing” and nothing more. The commercial programs came earlier and each solved the problem in its own way, so three ways exist, one per program — and since much of the data you will meet still arrive in SPSS, Stata, or SAS files, or from survey platforms that export to them, one of those three is usually what your data already use. jstats supports all three rather than inventing a fourth, and leaves the choice of which to use to you. It calls that choice a missing-value convention.
- SPSS-style. The code stays in the data as a visible number — a −99 is still a −99 in the column — and a note attached to the column says “treat −99 and −98 as missing.” Every jstats function reads that note. The advantage is that nothing is hidden: you can see the codes in a frequency table and in the raw data.
- Stata-style. The code is replaced by a marker:
.afor the first missing reason,.bfor the second, and so on up to.z, each with its label. The markers are genuine missing values as far as R is concerned — which is why the package recommends this form; block 3 explains, and block 5 shows it live. - SAS-style. The same idea as Stata, with uppercase markers (
.Ato.Z).
jstats will not pick one of these for you. The next block clears any convention you may already have chosen, so that whatever you did on another page, this page starts where a first-time reader starts — with no choice made. Block 3 then shows what happens when you try to work without one.
## ---- 2. What a missing-value convention is ----------------------------
## jstats can keep missing-value codes in any of three forms, one for each
## major program. It will not choose one for you. We start with no
## convention chosen -- the package default -- so you see what happens
## (If you've chosen one on another page or in your Project's .Rprofile,
## this line clears it for the current session only; restarting R brings
## your .Rprofile choice back)
joptions(missing.convention = "none")ConsoleTerminal×Background Jobs×> joptions(missing.convention = "none") Options Settings Missing-value convention: None selected Run joptions() to see all settings. > |
If you have handled missing values in SPSS, Stata, or SAS, one of these three will already be familiar — here it is under its own name. SPSS-style is what MISSING VALUES in Variable View produces: the codes stay put and a definition rides alongside. Stata-style is Stata’s extended missing values (.a through .z); SAS-style is SAS’s (.A through .Z). The one new idea is that jstats lets you choose between them for data that are now in R — regardless of which program the file came from — and converts from one form to another on request. Which form suits you is a question about how you work, not a verdict on the programs: it depends on whether your data go back to one of them, and on whether you also use base R alongside jstats.
3. Declare, and meet the gate
jdeclare_missing() is the function that tells jstats which codes in a column mean “missing,” and it takes the labels at the same time. The load note in block 1 already said a convention has to be chosen first; run the declaration now anyway, exactly as you will run it successfully in the next block — with no convention chosen, it stops and asks.
## ---- 3. Declare, and meet the gate ------------------------------------
## Declare -99 and -98 as missing on MoodRating, with labels
## With no convention chosen, jstats stops and asks instead of guessing
clinic <- jdeclare_missing(clinic, MoodRating, # an error, on purpose
codes = c(Refused = -99, "Don't know" = -98))ConsoleTerminal×Background Jobs×> clinic <- jdeclare_missing(clinic, MoodRating, # an error, on purpose + codes = c(Refused = -99, "Don't know" = -98)) Error: jdeclare_missing(): no missing-value convention is selected, so these codes cannot be declared. Choose one for this session: joptions(missing.convention = "stata") Lowercase markers behave as true NAs in base R. Recommended if you also run base R or AI-generated code. joptions(missing.convention = "spss") Codes stay visible numbers; jstats treats them as missing. Base R does not. joptions(missing.convention = "sas") Like Stata, with uppercase markers (.A-.Z). To make the choice permanent, put the same line in your .Rprofile. > |
This is the one place on the site where jstats refuses to do something it plainly could do, and the refusal is deliberate. The three forms behave differently once the data leave jstats — in base R, in other packages, in a file bound for another program — so silently picking one would be choosing on your behalf a consequence you had not seen. Instead the error lays out the three choices, each with the one fact that separates it from the others, and puts its recommendation first.
The recommendation is Stata-style, and the reason is the line under it in the error: the markers behave as true NAs in base R. That holds for the SAS-style markers just as much — both forms replace the code with a genuine missing value, and the only difference between them is which letters they use. The real divide is between those two and SPSS-style. Under SPSS-style, a −99 is still a −99, and base R — which knows nothing about jstats’s notes — will average it in and hand you a plausible-looking wrong number. Under either marker form the code has become a real missing value, so base R’s mean() returns NA instead: an answer you cannot mistake for a result, and one line fixes it. In short, SPSS-style fails silently in base R; the marker forms fail loudly, and are fixable. Stata-style is the one the recommendation names because it is the more widely used of the two. Block 5 shows the contrast live, on the same column.
That still leaves room for judgment. If you write base R alongside jstats, or paste in code an AI assistant wrote for you — which today will usually be base R — choose Stata-style. If your data live in SPSS and go back there for a collaborator, staying SPSS-style is a reasonable choice, and jstats will read your declarations correctly either way. And notice the error’s last line: the same joptions() command, placed in your Project’s .Rprofile, makes the choice once and for all — block 7 says where that file lives.
Why do the programs differ at all? They made different trade-offs. SPSS keeps the code visible as a number and attaches a definition beside it, which can suit a survey codebook in the tradition that grew up around paper questionnaires, where the code written on the form is itself the record of the refusal. Stata and SAS fold the reason into the missing value itself, so that arithmetic can never touch it by accident. Neither is wrong; they are answers to different working habits. Book 1 covers what a missing-value code is, and what an undeclared one does to any statistic that treats it as an ordinary number — the mean is simply the easiest place to see it; Book 2 covers the three conventions, how jstats converts between them, and what survives the trip to each file format — in more detail, with worked examples. The help page ?jdeclare_missing documents the options.
4. Choose, declare, check
Choose the recommended form, then run the same declaration again. It goes through this time, and two checks show what changed: the descriptives, and a frequency table. Then one more call — joptions() with nothing inside the parentheses — shows your current settings, this new one among them.
## ---- 4. Choose, declare, check ----------------------------------------
## Choose the Stata-style convention -- the one jstats itself recommends
## for anyone who also runs base R
joptions(missing.convention = "stata")
## Now the same declaration goes through
clinic <- jdeclare_missing(clinic, MoodRating,
codes = c(Refused = -99, "Don't know" = -98))
## The mean sits inside 1-10 again, and the codes count as missing
jdesc(MoodRating)
## A frequency table shows the codes set aside, with their labels
jfreq(MoodRating)
## With nothing in the parentheses, joptions() shows your current
## settings, including this one
joptions()ConsoleTerminal×Background Jobs×> joptions(missing.convention = "stata") Options Settings Missing-value convention: Stata-style Run joptions() to see all settings. Note: the clinic data frame uses SPSS-style missing values. To convert it to match this setting, run: jconvert(clinic, to = "stata", modify = TRUE) > clinic <- jdeclare_missing(clinic, MoodRating, + codes = c(Refused = -99, "Don't know" = -98)) Declared and converted to Stata-style missing values on MoodRating: .a ["Refused"] (from -99) .b ["Don't know"] (from -98) This call changes clinic only if you assign the result: clinic <- jdeclare_missing(clinic, MoodRating, ...) To change clinic directly, rerun with modify = TRUE: jdeclare_missing(clinic, MoodRating, ..., modify = TRUE) Note: MoodRating uses Stata-style missing values, but other columns in clinic are SPSS-style. To align MoodRating with the rest, run: jconvert(clinic, vars = "MoodRating", to = "spss", modify = TRUE) > jdesc(MoodRating) Descriptive Statistics Using default data frame: clinic 70 Cases in the 1 Variable Pool Variable Total Non_missing Min Max Mean SD ---------- ----- ----------- --- --- ----- ----- MoodRating 70 63 1 9 5.460 1.702 > jfreq(MoodRating) Frequencies Using default data frame: clinic 70 Cases in the 1 Variable Pool MoodRating Freq Total % Valid % Cum. % ----------------- ---- ------- ------- ------ Valid 1 1 1.43 1.59 1.59 2 1 1.43 1.59 3.17 3 7 10.00 11.11 14.29 4 8 11.43 12.70 26.98 5 14 20.00 22.22 49.21 6 15 21.43 23.81 73.02 7 10 14.29 15.87 88.89 8 5 7.14 7.94 96.83 9 2 2.86 3.17 100.00 Total valid 63 90.00 100.00 Missing .a ["Refused"] 4 5.71 -- -- .b ["Don't know"] 3 4.29 -- -- Total missing 7 10.00 Total 70 100.00 > joptions() Options Settings Missing-value convention: Stata-style SPSS-style missing value codes: -99, -98, -97 Data folder: Working directory Correlation layout: wide Missing-value detail: per_code Message width: Auto (currently 79) > |
Read the box from the top. Choosing the convention produced a note straight away: clinic arrived with four SPSS-style columns, and jstats points out that they no longer match your setting — the next block deals with that. The declaration then reports what it did: each code became a marker (.a, .b) carrying its label, and the note beneath reminds you that a call like this changes clinic only because you assigned the result back with the assignment operator, <- (the modify = TRUE form it mentions does the same job in place — either is fine). It also points out, correctly, that MoodRating now differs in form from the rest of clinic.
Then the payoff. The descriptives report a smaller non-missing count, a minimum and maximum inside 1–10, and a mean that finally looks like a mood rating. The line above each table still reads 70 cases, exactly as it did before the declaration, and that is worth noticing: no rows were dropped. The declared codes are missing within MoodRating rather than whole rows knocked out of the dataset, and it is the non-missing count — 63 of 70 — that records them. The frequency table is the clearest view of all: the two codes sit in their own Missing section under their markers and labels, counted in the totals and left out of the valid percentages, and each section ends on a total line of its own. And the settings listing confirms the convention is in place for the rest of your session. (The row beneath it is always listed: those are the numbers jstats would use if you ever converted your markers to the SPSS form.)
Every one of the commercial programs has a version of this move, and all three get to the same analytical place: the codes stop counting as data, and no rows are thrown away. They get there differently, though, and the difference is worth knowing before you migrate. SPSS declares: MISSING VALUES MoodRating (-99, -98), with the matching VALUE LABELS, leaves the −99s sitting in the cells and flags them — clear the declaration and they are ordinary values again. Stata and SAS replace: the code is overwritten by the program’s own marker. In Stata that is mvdecode MoodRating, mv(-99=.a \ -98=.b), with label define supplying the matching labels; in SAS it is a data-step assignment — if MoodRating = -99 then MoodRating = .A;, and the same for .B — with PROC FORMAT for the labels. Afterward the original number is not recoverable from the data, though the labels still record what each marker meant. The difference here is that the declaration you just made is not tied to any of the three: it can be converted to either of the other forms without going back to the raw data. Converting to the SPSS form puts numeric codes back, but which numbers get used is set by a joptions() setting (missing.convention.codes), because the original values are gone. The value labels, however, survive every conversion unchanged.
The frequency table above is close to what SPSS’s Frequencies gives you: a Valid section and a Missing section, each closed by its own total, with valid percentages computed on the valid cases alone. In SAS, PROC FREQ with the MISSPRINT option comes nearest, listing the missing values in the table but leaving them out of the percentages. Stata’s tabulate gives one view or the other: without the missing option it leaves the markers out of the table, and with it, it counts them in the percentages like any other value. What has no counterpart in any of them is the settings listing. Each program has only its own form, so there is nothing to choose; in jstats there is, and joptions() is where you see what you chose.
5. Bring the rest in line
clinic arrived with four columns already declared, SPSS-style — the common case, since Qualtrics, LimeSurvey, and most public data archives hand out SPSS-style files. jstats reads those declarations correctly under either setting, so nothing is wrong yet. But base R does not read them, and this block shows what that means before and after one line of conversion. Stress is one of the four columns.
## ---- 5. Bring the rest in line ----------------------------------------
## Stress arrived with its codes declared SPSS-style. Ask base R's mean()
## for its average, with and without na.rm = TRUE (which tells mean() to
## drop NAs). To base R the codes are ordinary numbers, so both go in
mean(clinic$Stress) # base R -- the codes are averaged in
mean(clinic$Stress, na.rm = TRUE) # na.rm has no NAs to drop yet
## One line converts every SPSS-style column in clinic to the Stata form
clinic <- jconvert(clinic, to = "stata")
## Now base R sees the missing values too: the mean is NA (loud, not wrong),
## and na.rm = TRUE gives the right answer
mean(clinic$Stress)
mean(clinic$Stress, na.rm = TRUE)ConsoleTerminal×Background Jobs×> mean(clinic$Stress) # base R -- the codes are averaged in [1] 8.685714 > mean(clinic$Stress, na.rm = TRUE) # na.rm has no NAs to drop yet [1] 8.685714 > clinic <- jconvert(clinic, to = "stata") Converted to Stata-style missing values in 4 variables: Stress -99 ["Refused"] -> .a -98 ["Don't know"] -> .b SleepHours -99 ["Refused"] -> .a -98 ["Don't know"] -> .b Medication -99 ["Refused"] -> .a Anxiety4 -99 ["Refused"] -> .a -98 ["Don't know"] -> .b This call changes clinic only if you assign the result: clinic <- jconvert(clinic, ...) To change clinic directly, rerun with modify = TRUE: jconvert(clinic, ..., modify = TRUE) > mean(clinic$Stress) [1] NA > mean(clinic$Stress, na.rm = TRUE) [1] 15.18182 > |
There is the argument from block 3, live. Before the conversion, base R returned a mean for Stress that looks perfectly reasonable and is wrong — the −99s and −98s went into it — and na.rm = TRUE changed nothing, because to base R those codes are not NAs; they are numbers. After jconvert() the same column, holding the same information, gives NA: base R now sees the missing values, refuses to average across them, and na.rm = TRUE does exactly what it is for. The conversion report lists what moved, column by column. This is a change to the data as they sit in R; what happens to declarations when you save to a file and load it back is covered on Loading, Saving, Converting Data.
The block is the base R equivalent, shown live. mean() and its na.rm argument are base R’s whole toolkit for missing values, and they only know one kind: NA. A declared SPSS-style code is a jstats (and haven) idea layered on top — base R never looks at the note, which is why the first mean is quietly wrong. The conversion itself is haven’s tagged_na() under the hood: the same proven library the other pages mention, doing the fiddly part (one marker per code, labels carried across, all four columns at once) so you don’t. If you would rather see the effect on one column than on all four, jconvert() takes a vars argument; ?jconvert has the details.
6. Finding undeclared codes in your own data
clinic announced its problem columns at the door, because it arrived through jload(). Your own data will not always be so polite — a data frame handed to you by a colleague, or built by other code, comes with no message attached. So it is worth knowing what an undeclared code looks like from the inside. jscreen() screens the whole data frame; then a frequency table of the one column still carrying the problem.
## ---- 6. Finding undeclared codes in your own data ---------------------
## jstats named MoodRating and Anxiety2 as clinic loaded. Data that arrive
## some other way come with no message, so learn the symptoms: screen the
## whole data frame...
jscreen()
## ...then look at the one column still carrying the problem
jfreq(Anxiety2)ConsoleTerminal×Background Jobs×> jscreen() Data Screening Using default data frame: clinic Cases: 70 Variables: 16 Cases with missing data: 24 Variables with outliers: 3 Variable Types Variable jstats Class Sub-class Unique Values ------------- ------------ ---------- ------------- ClientID Categorical identifier 70 Stress Numeric 25 SocialSupport Numeric 21 SleepHours Numeric 37 Flourishing Numeric 39 ScreenTime Numeric 43 PriorTherapy Categorical dichotomy* 2 SoughtHelp Categorical dichotomy 2 Medication Categorical dichotomy 2 Condition Categorical 4-category 4 MoodRating Numeric 9 Anxiety1 Categorical Likert 5 Anxiety2 Numeric 7 Anxiety3 Categorical 5-category 5 Anxiety4 Categorical Likert 5 Anxiety5 Categorical Likert 5 * coded other than 0/1; mean is not a proportion Missing Data & Outliers (outliers > 3 SD from mean) Variable Missing % Missing Outliers ----------- ------- --------- -------- Stress 4 5.7 1 SleepHours 4 5.7 -- Flourishing -- -- 1 Medication 5 7.1 -- MoodRating 7 10.0 -- Anxiety2 -- -- 6 Anxiety4 6 8.6 -- > jfreq(Anxiety2) Frequencies Using default data frame: clinic 70 Cases in the 1 Variable Pool Anxiety2 Freq Total % Valid % Cum. % -------------- ---- ------- ------- ------ -99 3 4.29 4.29 4.29 -98 3 4.29 4.29 8.57 1: Not at all 8 11.43 11.43 20.00 2: A little 13 18.57 18.57 38.57 3: Moderately 20 28.57 28.57 67.14 4: Quite a bit 16 22.86 22.86 90.00 5: Extremely 7 10.00 10.00 100.00 Total 70 100.00 > |
Nothing in the screen says “undeclared code” in so many words — that message came with the load. What the screen shows is the symptoms, and they are worth learning because they are what you will see in a data frame that arrived some other way. Look at Anxiety2 against its neighbors. Anxiety1, Anxiety4, and Anxiety5 are classified as Likert items; Anxiety2 is classified as plain Numeric, because two stray values have stretched its range past anything a five-point scale could hold. The same two values show up again in the outliers column, where an item like this should have none. And in the missing-data column Anxiety2 reports nothing at all, while Anxiety4 — the same kind of item, with its codes declared — reports the missing cases it has. A frequency table then settles it: the two codes sit in the table as rows of their own, each with a valid percentage, counted as if they were answers.
There are two ways to deal with a column like that, and jstats’s own messages always suggest the first. Declare the codes, as you did for MoodRating: nothing is deleted, the labels are kept, and the declaration can be revised or removed later. Or recode them to NA with jrecode(): that also removes them from every statistic, but it throws away the distinction between a refusal and a don’t-know, and there is no undo. Declaring is the careful default; recoding is for when the reason genuinely does not matter.
So here is the exercise the page has been building toward. jstats told you at the top of the page that Anxiety2 carries the same two codes as MoodRating; the screen and the frequency table have just shown you what that does. Declare them yourself — the call is the one from block 4 with the column name changed — then run the frequency table again and watch the codes move into the Missing section. The finished script at the bottom of the page carries the answer, if you want to check your work.
Base R has no way to declare a missing value; its only move is the recode. The syntax is clinic$Anxiety2[clinic$Anxiety2 %in% c(-99, -98)] <- NA, and it does exactly what the paragraph above warns about: both codes become the same anonymous NA, so the difference between a refusal and a don’t-know is gone, and the original values are gone with it. It is the right tool when the reason for the missingness is irrelevant. jdeclare_missing() exists because in most research it is not — the difference between “refused” and “not applicable” is often the finding — and commercial statistical software has always let you keep it.
7. Make it permanent
The convention you chose is a session setting: it lasts until you restart R. This page deliberately leaves it switched on when you move away — you chose it, so it should still be there on the next page you visit, and one last look confirms it is. To have jstats make the choice for you at the start of every session in this Project, put the same joptions(missing.convention = "stata") line in your Project’s .Rprofile file; the error in block 3 told you as much. Loading jstats automatically shows where that file lives and how to create it.
## ---- 7. Make it permanent ---------------------------------------------
## The convention you chose is a session setting: it lasts until you
## restart R. This page leaves it switched on -- one last look
joptions()
## To have it chosen automatically at the start of every session in this
## Project, put the same joptions(missing.convention = "stata") line in
## your Project's .Rprofile file. The Loading jstats automatically page
## shows where that file lives and how to create itConsoleTerminal×Background Jobs×> joptions() Options Settings Missing-value convention: Stata-style SPSS-style missing value codes: -99, -98, -97 Data folder: Working directory Correlation layout: wide Missing-value detail: per_code Message width: Auto (currently 79) > |
The download at the bottom of this page is the finished version of what you built — and finished means something specific. A goal every experienced programmer and statistician takes for granted, but new researchers are rarely told: a completed script should run from beginning to end without errors. On this page you met one error on purpose, because meeting it is how the choice gets explained. The download shows what the end of that process looks like, and it differs from the page blocks in three principled ways: it opens with library(jstats), which the page assumes you have already run, so the file works in a fresh session; the deliberate error in block 3 is commented out, with a note saying why; and block 6 carries the answer to the closing exercise — the Anxiety2 declaration you were asked to write yourself — so the file runs clean and complete. Nothing else changes: this page saves no files, so there are no overwrite arguments to add, and the reload at the top already guards against a second run.
To prove a script is finished, use the Source icon (to the right of Run in the script toolbar): it runs the entire file, top to bottom, in one go. Source shows the output without echoing each command back to the Console — that’s normal — and the small arrow beside Source offers “Source with Echo” if you’d like the commands shown too. jstats functions print their own tables, so their output always appears; but a bare base R line that merely displays a value — mean() on its own — stays quiet under Source, because R only auto-prints results when you run lines interactively.
The What you should see boxes under the blocks above reproduce that Console — the blue command lines, the gray notes, the colored tables, and one red error where it is meant to happen. They show the code being run, along with any note written on the end of a command line, but not the standalone ## comment lines (running one just echoes it back and does nothing).
One thing this page leaves behind, deliberately: the missing-value convention stays set for the rest of your session, because the whole point of choosing it is to have it in place. Nothing else is changed and nothing is written to disk. If you set a convention in your Project’s .Rprofile and block 2 cleared it, restarting R brings it back.
If you’re keeping the file — and it’s worth keeping, since finished scripts travel — give it a proper home. In the Files tab of the lower-right pane, click New Folder and type a name into the popup. “Scripts” is a good one, and it will sit alongside whatever else your project folder holds.
Take the whole script with you
That’s the full run-through. If you’d like the entire script in one file to keep, adjust, and reuse, download it here:
The file is close to what you have just built: the same code, in the same order, as though you had pasted every block above into one script. Three things differ, each on purpose — the file opens with library(jstats), the line that raises the deliberate error is commented out, and the finished file includes the answer to block 6’s exercise. The What’s the point? box under the last block explains why a finished script is written that way.