Chapter 6: Missing Values

Recognizing and Handling Missing Data in R

Overview

In this chapter, you will practice R habits that help prevent your analysis from being quietly wrong. Code can run without producing a useful or trustworthy result. This chapter focuses on checking assumptions, understanding function defaults, and handling missing values deliberately.

You will work with special values like NA and NaN, practice summary functions, detect and locate missing values, and compare two basic ways to handle missing data: removing missing values and replacing them with a simple summary value. You will also practice nesting functions, which means putting one function inside another to solve multi-step problems.

In Chapter Six, you will practice how to:

  • 🕳️ Recognize NA as missing information and NaN as an undefined numeric result.

  • 🦠 Describe how missing values can affect calculations in R.

  • ⚙️ Recognize that many R functions have default arguments that affect results.

  • 🧹 Use na.rm = TRUE to tell summary functions to ignore missing values.

  • 🚨 Use anyNA() to check whether a vector contains missing values.

  • 🔍 Use is.na() and which() to identify where missing values are located.

  • 📋 Use sum(is.na(x)) and mean(is.na(x)) to summarize how much data is missing.

  • 🩹 Recognize the difference between removing missing values and imputing missing values with a summary value.

  • 🧅 Read nested function calls from the inside out.

  • 🕸️ Explain why compact code can be efficient but harder to read and debug.

  • 🦁 Practice documenting what you understand, what remains uncertain, and when to change strategies or ask for help.

🦁 In this chapter, courage means making your thinking visible even when it is unfinished. When a result surprises you or code becomes difficult to follow, write down what you understand, name what remains uncertain, and choose a next step. You might check a help page, break the code into smaller steps, or ask for help when you stop making progress.

Open an R Notebook

Before you follow along with Chapter 6, set yourself up the same way you did in Chapter 5. Your work will be readable and useful when you review.

🎯 Please open a new R Notebook and name it chapter6_notes.Rmd. Save it in the folder you use for this chapter, and add a date to the YAML header. Use this document to take notes, practice the chapter content, and memo about what you are learning.

The YAML header is the setup block at the very top of an R Notebook. It tells R Markdown the document title, author, date, and output type.

  1. Open RStudio → File → New File → R Notebook.

  2. Save it in the folder you use for this chapter as chapter6_notes.Rmd.

  3. In your YAML header at the top:

    • Update the title to match this chapter.

    • Include a date (hard-coded or dynamic, your choice).

    • Add your name as the author.

For example, your YAML header might look like this:

---
title: "Chapter 6 Notes"
author: "[Your Name]"
date: "`r Sys.Date()`"
output: html_notebook
---

The date can be typed directly, such as 2026-05-20, or it can be dynamic using `r Sys.Date()` so the document updates the date automatically when it renders.

  1. Keep the YAML header at the top of the notebook. Delete only the sample body text and example code below the second --- line.

🧐 Notice: The YAML header is the block between the two --- lines at the top. Keep that block because it controls the notebook’s title and output format.

  1. Use headings as you work. At minimum, create headings for # Missing Values Workflow, ## Special Values, ## Detecting Missing Values, and # Nesting Function Calls. This creates a readable outline.

  2. Memo as you go.

    • Inside chunks, use # for code comments. Otherwise, R tries to run the text as code.

    • Outside chunks, use 🗣 Comment to explain what the code or output shows and 🪞 Reflect to record what you understand, what remains uncertain, and what you would try next.

    • When a resource helps you, add a 👀 Cite memo naming the resource and explaining how it helped.


Next, you’ll focus on what NA means, how missing values “propagate” through calculations, and how to detect and locate missing values.

Missing Values Workflow

The na.rm argument in the mean() function has a default value of FALSE. This means that mean() does not remove missing values before calculating. If the input contains NA, the result is usually NA.

Special Values

In R, missing values are represented by the special value symbol NA.

R has several special values that represent non-standard data states.

  • NA (“Not Available”) signals missing information: the value is unknown or unavailable. A value may be missing because it was not recorded, could not be measured, or does not apply.
  • NaN (“Not a Number”) signals an undefined numeric result, such as 0 divided by 0.
# ⚡ Type this in your R Notebook
0 / 0      # Produces NaN – undefined arithmetic

NA         # Built-in constant representing a missing value

🗣 R prints NA and NaN in the Console so you can spot them quickly. Even though they look similar, they do not mean exactly the same thing.

  • NA means the value is absent or unknown.
  • NaN means the result is not a valid number, usually because the math operation is undefined.

We will mostly focus on NA in this chapter because missing values are common in real datasets. Missing values can occur in datasets because of incomplete data entries such as non-responses in surveys, or when joining two datasets. R will do the operation, but the result becomes NA because the value is unknown; this is called propagation.

📜 The SYNTAX (with na.rm default argument):

mean(x, na.rm = FALSE)

where na.rm is a logical argument that tells the function whether to remove NA values before calculating the mean.

🎯 Add NA value(s) to the cursed sequence and calculate the mean while handling missing values.

# ⚡ Type this in your R Notebook
cursed_seq <- c(4, 8, 15, 16, 23, 42, NA)  # includes a missing value
# cursed_seq <- c(cursed_seq, NA, NA)    # uncomment to add more missing values

mean(cursed_seq)                       # default is na.rm = FALSE → returns NA

mean(cursed_seq, na.rm = TRUE)         # Change default to TRUE to ignore missing values

🗣 The first mean() call returns NA because the default behavior leaves the missing value in the input, so the mean cannot be determined. The second call uses na.rm = TRUE to ignore the NA values, allowing the function to compute the mean of the remaining numbers.


In many summary functions, any NA in the vector causes the result to be NA unless you tell R to remove missing values first.

Function What it does Example
sum(), prod() Sum/product sum(x, na.rm = TRUE)
mean(), median() Central tendency mean(x, na.rm = TRUE)
min(), max(), range() Extrema/range range(x, na.rm = TRUE)
sd(), var() Spread sd(x, na.rm = TRUE)

Detecting Missing Values

A quick yes/no check prevents surprises later in your workflow.

  • anyNA(x) returns TRUE if any element in x is NA or NaN.

📜 The SYNTAX (basic):

anyNA(x)

where x is the vector you want to check for missing values.

🎯 Use anyNA() to check if there are any missing values in a vector.

# ⚡ Type this in your R Notebook
Vector_NA <- c(3, 7, NA, 12)  # Our sample data – one value is missing

anyNA(Vector_NA)              # TRUE means "Something is missing!"

🗣 If this were my data and I saw FALSE, I could relax; the vector has no gaps. Seeing TRUE tells me to investigate further.

🦁 If the result surprises you, pause before changing the code. Write what you expected, what R returned, and what you will check next.

Locating Missing Values

There are two useful functions to find the exact positions of missing values in a vector:

  • is.na(x) returns a logical vector: TRUE wherever x is NA or NaN.

  • which(logical_vec) converts TRUE positions into numeric indices, which are handy for slicing or replacing values using positional indexing.

🎯 Use is.na() and which() to locate missing values in Vector_NA.

# ⚡ Type this in your R Notebook
is.na(Vector_NA)          # Notice that it is TRUE at position 3

🗣 The is.na() function creates a logical vector indicating which elements are missing. The third element is TRUE, indicating that the third element in Vector_NA is NA.

Vectors can be quite long, so it’s often more useful to get the numeric indices of missing values. Use which() to extract those indices.

# ⚡ Type this in your R Notebook
logical_NA <- is.na(Vector_NA)
which(logical_NA)   # Returns index 3 directly

🗣 The is.na() function creates a logical vector logical_NA indicating which elements are missing, while the which() function extracts the indices of those missing values (see Positional Indexing).

Which Missing-Value Function Should I Use?

Several functions help you work with missing values. They answer different questions.

Question Function What it returns
Are there any missing values? anyNA(x) One TRUE or FALSE answer
Which values are missing? is.na(x) A logical vector
What positions are missing? which(is.na(x)) Numeric index positions
How many values are missing? sum(is.na(x)) A count
What proportion is missing? mean(is.na(x)) A proportion

The key is to choose the function that matches your question. If you only need a quick yes/no check, use anyNA(). If you need the exact positions, use which(is.na(x)).

Nesting Function Calls

Nesting function calls means putting one function call inside another function call. The output of the inner function becomes an argument to the outer function. This works because functions return values. R can immediately pass a returned value into the next function.

You just used is.na() and which() in separate steps. You can also nest them.

🎯 Nest is.na() inside which() to find the missing position in Vector_NA.

# ⚡ Type this in your R Notebook
which(is.na(Vector_NA))

🗣 R runs is.na(Vector_NA) first, returning FALSE FALSE TRUE FALSE. Then which() returns the position of the TRUE value: 3. This is the same result you got when you stored the logical vector as logical_NA in the previous section.

🦁 If nested code is difficult to understand or debug, change strategies by separating it into smaller steps. Store and check each result before combining the functions again. The next section might be challenging to read, review 🦁 Change strategies: Check each step.

🎯 Use sample() to randomly select 5 numbers from the vector travel_jumps, then calculate the mean of those sampled numbers by nesting the sample() function inside the mean() function.

# ⚡ Type this in your R Notebook
travel_jumps <- seq(from = 1887, to = 2052, by = 33)
mean(sample(travel_jumps, size = 5, replace = TRUE))  # sample then average

R runs sample() first and passes its five selected values to mean(). You can also put the seq() call inside sample(), so all three steps appear in one expression:

# ⚡ Type this in your R Notebook
mean(sample(seq(from = 1887, to = 2052, by = 33), size = 5, replace = TRUE))
Note🦁 Change strategies: Check each step

Now test each part of that expression. Start with the innermost seq() call, check its result, and then use that result in sample() and mean().

# ⚡ Type this in your R Notebook
travel_jumps <- seq(from = 1887, to = 2052, by = 33)
travel_jumps                # ✅ Check: six numbers, from 1887 to 2052

sampled_jumps <- sample(travel_jumps, size = 5, replace = TRUE)
sampled_jumps                # ✅ Check: five sampled numbers

mean_jump <- mean(sampled_jumps)
mean_jump                    # ✅ Check: one mean

🗣 Separating the steps makes each intermediate result visible. If a result is unexpected, you can identify which step needs attention. Each call to sample() makes a new random draw, so the means from separate runs may differ even when the code follows the same steps.

Nesting can express several steps in one line, but the separated version is often easier to check. In later chapters, you will learn another way to write multi-step workflows using pipes.

Locating Missing Values with Nesting

🎯 Create a large vector with some missing values using sample() and c().

# ⚡ Type this in your R Notebook
set.seed(100)
LargeVec <- sample(c(1:10, rep(NA, 10)))
print(LargeVec)

🗣 We used the c() function in Chapter 2. Here we have nested c() inside of sample(). We also used the rep() function, which is covered in more detail in a later chapter.

🎯 Use which() and is.na() to find the positions of missing values in a large vector by nesting the is.na() function inside the which() function.

# ⚡ Type this in your R Notebook
which(is.na(LargeVec))                    # ▶ Where are the gaps?

🗣 The is.na() function creates a logical vector indicating which elements are missing, and the which() function determines the indices of those TRUE values. The numbers are the numeric indices (positions) of the NA values.

Summarizing “Missingness”

As a researcher, knowing how much data is missing helps guide your next step, but the amount is only one part of the decision.

A single NA may matter if it occurs in a key measurement. A larger number of missing values may create a serious problem, depending on where the missing values occur and why they are missing.

For example, if 1 out of 20 values is missing, then 5% of the vector is missing. If 10 out of 20 values are missing, then 50% of the vector is missing. Those situations raise different concerns, but the percentage alone does not determine what you should do. Consider the amount and pattern of missing data, why the values may be missing, your analysis goal, the consequences of the decision, and guidance from your field.

🎯 Use sum() and mean() with is.na() to summarize missing values in a large vector.

# ⚡ Type this in your R Notebook
sum(is.na(LargeVec))    

mean(is.na(LargeVec))   

🗣 The sum(is.na(LargeVec)) expression counts the total number of missing values in LargeVec by summing the TRUE values from is.na(). The mean(is.na(LargeVec)) expression calculates the proportion of missing values by taking the mean of the logical vector returned by is.na(), where TRUE is treated as 1 and FALSE as 0. When 50% of the values are missing, the mean of the logical vector is 0.5.

Imputation

Now that you can detect and locate gaps, you have to decide what to do about them. There is no universal “best” choice because each option makes assumptions.

In this chapter, we’ll use mean imputation as a practice method for learning replacement logic. This is not a recommendation to use mean imputation in research. Replacing missing values with the mean can make the data look more complete than it really is, and it can distort the distribution, especially the variance.

🦁 If you do not know why values are missing, pause before removing or replacing them. Name what you do not know, check the data documentation, or ask someone who understands how the data were collected.

  • Option 1: Exclude missing values from a calculation or create a vector containing only observed values. This is simple, but it reduces the available data and can bias results if the missingness is systematic.

  • Option 2: Replace missing values with a summary value (simple imputation). This keeps the vector length the same, but it can distort the distribution, especially the variance.

In this chapter, we are practicing logical indexing on a single vector. In real analysis, values often live in rows of a dataset (one row = one person or observation) with multiple columns. If you remove rows because one column is missing, you also throw away valid information in other columns. That can:

  • shrink your sample size (less stable results),

  • change which groups are represented (bias),

  • change which observations contribute to plots, models, and group summaries.


🌳 Imagine a class dataset with student_id, major, exam1, and exam2. If exam2 is missing for some students, removing those students from the analysis (rows) means we’d also lose their exam1 scores and their major. If our goal is “average Exam 1 by major,” dropping rows because Exam 2 is missing can silently change the story. This is especially true if missing Exam 2 happens more in one major than another.


The bottom line is that there is no one-size-fits-all solution. The appropriate response depends on the amount and pattern of missing data, why values may be missing, your analysis goal, the consequences of the decision, and guidance from your field. For a summary of one variable, a summary function may omit NA values when doing so is defensible. In analyses with multiple variables, both removing and imputing values require justification. Keeping rows aligned or comparing groups does not, by itself, make imputation the right choice.

🦁 Whatever you choose, write a memo that documents the decision, the evidence you used, and what remains uncertain. Making your reasoning visible helps you evaluate the choice and ask a clearer question if you need help.

Next, you’ll practice both strategies on a vector so you can see the trade-off clearly. Later, you’ll apply the same decision logic to multi-column datasets.


Option 1

Remove missing values

🎯 Use indexing with !is.na() to keep only the observed (non-missing) values in LargeVec.

# ⚡ Type this in your R Notebook
clean_vec <- LargeVec[!is.na(LargeVec)]   # Keeps only observed values
clean_vec

🗣 is.na(LargeVec) returns a logical vector (TRUE for missing, FALSE for present). The ! flips it (so TRUE means “keep this value”). The brackets [...] then use that logical vector to select only the elements you want.

Note📖 Review: Check each part of the filter

Before using the full expression, run each part and inspect what it returns.

# ⚡ Type this in your R Notebook
print(LargeVec)              # See the original values
LargeVec[2]                  # Review: select one value by position
is.na(LargeVec)              # TRUE where a value is missing
!is.na(LargeVec)             # TRUE where a value is present
LargeVec[!is.na(LargeVec)]   # Keep only the present values

✅ Compare the last result with clean_vec. They should contain the same observed values.


Option 2

Impute missing values (replace NA with the mean)

Now that you can detect and locate gaps, you have to decide what to do about them; one option is to impute missing values by replacing them with a summary value. It means “fill in missing values.” A common simple imputation method is to replace missing values with the mean of the observed values.

🎯 Make a copy of LargeVec, then replace only the missing values with the mean of the observed values.

# ⚡ Type this in your R Notebook
imputed <- LargeVec     # Make a copy to preserve original

🗣 The reason we make a copy is that we want to keep the original vector with its missing values intact for comparison. This way, we can see the effect of imputation without losing the original data.

💡 Tip: Once we make a copy, we use that copy name everywhere in the imputation line. This new version of the vector is called imputed, and it is the version we will use next. imputed is the vector’s name. The name is descriptive of what the difference will be between it and the original LargeVec once we have replaced only the missing values with the mean of the observed values. It is good practice to name objects in meaningful ways.

We’re doing three actions on the same object (imputed):

🎯 (1) identify missing positions, (2) compute a replacement value, and (3) write the replacements back.

# ⚡ Type this in your R Notebook
imputed[is.na(imputed)] <- mean(imputed, na.rm = TRUE)
imputed

🗣 is.na(imputed) identifies exactly where the gaps are, and imputed[...] <- ... replaces only those positions. The na.rm = TRUE setting is needed here to calculate the replacement mean from the observed values. Without it, mean(imputed) would return NA because the input contains missing values.

The object we are editing is on the left side: imputed[is.na(imputed)].

The object we are using to find the missing positions is inside brackets: is.na(imputed).

The general replacement pattern is:

object[positions_to_replace] <- replacement_value

🧐 Notice: You might think, “The missing values came from LargeVec, so I should check LargeVec.” But the rule is to use the same object we’re editing, or we risk indexing the wrong thing later. The object we are summarizing to compute the replacement value is also imputed: mean(imputed, na.rm = TRUE)

🧐 Notice: You might think, “mean of observed values” means “mean of the original” and want to use LargeVec. But imputed currently equals LargeVec, so the mean is identical either way at that moment. As soon as you start changing imputed, mixing names can cause logic bugs. You might replace values in one object using positions or summaries computed from a different object.

In summary, wherever an element in imputed is missing, we replace that position with the mean of imputed while ignoring missing values so it can calculate the mean.

✅ Confirm your result by comparing sum(is.na(LargeVec)) with sum(is.na(imputed)).

# ✅ 
sum(is.na(LargeVec))      # Original vector – should print number 10
sum(is.na(imputed))       # Imputed vector – should print 0

🗣 The first sum(is.na(LargeVec)) call counts the number of missing values in the original vector, which should be 10. The second sum(is.na(imputed)) call counts the missing values in the imputed vector, which should be 0, confirming that all missing values have been replaced.


Summary

In this chapter, you strengthened your R workflow by learning how to recognize and handle special values in R. You practiced the difference between NA, which means information is missing, and NaN, which means a numeric result is undefined. You also learned that missing values can quietly affect your calculations because many R functions return NA unless you tell them how to handle missingness.

You built a practical missing-values workflow. You practiced using anyNA() to check whether missing values exist, is.na() to identify which values are missing, which() to locate their positions, sum(is.na(x)) to count missing values, and mean(is.na(x)) to calculate the proportion missing. You also practiced using na.rm = TRUE so summary functions can calculate results while ignoring missing values.

Finally, you practiced two basic strategies for handling missing values: removing missing values with logical indexing and replacing missing values with a simple imputed value. You also practiced nesting function calls so that one function’s result can be passed directly into another function. Your main takeaway is that strong R work depends on checking assumptions, understanding defaults, documenting your choices, and making sure your code is not only running, but producing results you can trust.

Chapter Terms

Impute: To replace missing or unusable values (e.g., NA) with substituted values, often using estimates based on the observed data (such as mean/median imputation or model-based predictions), so the dataset can be analyzed without dropping incomplete cases. Because imputed values are estimates, you should document what method you used and why.

Missing Values: Data values that are unknown or unavailable; in R they are represented by NA (“Not Available”). Depending on the context, a missing value may not have been recorded, may not have been measurable, or may not apply. NA is a special missing-value indicator (with typed versions like NA_integer_, NA_real_, and NA_character_), and many calculations will return NA if any missing values are present unless you explicitly remove them for that calculation (often via na.rm = TRUE, which defaults to FALSE in functions like mean()).

NA: A special constant that marks a missing value (“Not Available”), meaning the data point is unknown or unavailable. R also provides typed missing values like NA_integer_, NA_real_, NA_character_, and NA_complex_ to match the underlying data type.

NaN: A special numeric constant (“Not a Number”) that represents an undefined arithmetic result in floating-point math (for example, 0/0 or other invalid operations). NaN is non-finite (not a regular number).

Nesting Function Calls: Writing one function call inside another so the inner function runs first and its returned value becomes an argument to the outer function (for example, mean(sample(x, 5))). This works because a function call can appear anywhere an expression is allowed, and functions return a value that can be passed directly into the next call.

Positional Indexing: Indexing a vector using numeric positions (for example, x[1] for the first element or x[c(1, 3)] for the first and third). Positions are based on the current order of the vector.

Propagation: R’s default behavior where an unknown value “spreads” through a computation. If an expression includes NA, the result will usually be NA (e.g., 5 + NA returns NA) because the outcome can’t be determined without the missing information. This is often called NA propagation, and it’s why many summary functions return NA unless you explicitly remove missing values (e.g., na.rm = TRUE).

Special Values (NA, NaN, Inf, -Inf, NULL): Reserved constants in R that represent non-standard values: NA for missing data, NaN for undefined numeric results, Inf/-Inf for positive/negative infinity, and NULL for “no value” (an empty/null object). In practice, use is.na() to detect NA and NaN, is.nan() to detect NaN specifically (not NA), is.infinite()/is.finite() to handle infinities and other non-finite values, and is.null() to test for NULL.

📝 Practice Space

Fill in the blanks and answer the short prompts in your R Notebook. This Practice Space helps you check whether you can detect, locate, and count missing values; use function arguments deliberately; compare removal with simple mean imputation; and explain nested code. You will also continue using the notebook, memoing, and knitting practices introduced earlier.

📖 Review: Tasks 6 and 8 are brief cumulative-review tasks that revisit earlier skills. The other tasks focus on this chapter’s missing-values workflow.

Before you begin:

  1. Create a new R Notebook in RStudio.
  2. Keep the YAML header at the top. Delete only the sample body text and example code below the second --- line.
  3. Save the notebook in the folder you use for this chapter as chapter6_practice.Rmd.
  4. Complete each task in order.
  5. Add short memo notes throughout your notebook.

In this Practice Space, you will use three kinds of memos:

  • 🗣 Add Comments asks you to explain what the code or output shows.
  • 🪞 Reflect asks you to explain your thinking process, what confused you, what changed in your understanding, or what strategy you would use next.
  • 👀 Cite identifies a human or digital resource you used and explains how it helped.

Keep each memo short. One to three thoughtful sentences is enough. Answer the 🗣 Add Comments prompt in every task where it appears. Answer a 🪞 Reflect prompt when your prediction, understanding, confusion, or troubleshooting changed. Whenever a resource contributes to your work, add a 👀 Cite memo.

🦁 Sharing your thinking takes courage when it includes a mistake, uncertainty, or question. If two focused attempts or about 10 minutes do not produce new information, change strategies or ask for help. Share the relevant code or output, what you tried, and what still confuses you.

Follow the AI-use guidance that applies to your learning setting. If you use an AI tool, cite it as directed and explain what it contributed. Clearly mark any words you reproduce exactly as quoted text, using quotation marks or a block quote with >.

🦁 Test knit: Before you finish, clear your Environment, run the notebook from top to bottom, and knit to HTML. If knitting fails, read the error and make one informed change at a time.

Task 1

🧭 Help-file scavenger hunt (arguments and defaults)

  1. 🎯 Use ?sample to learn about the sample() function.

    • Focus first on the sample() Usage line, not the sample.int() line.
  2. In one sentence, explain how the Usage line shows which arguments have default values.

  3. Write the sample() Usage line exactly as it appears (copy/paste is fine), then label:

    • TWO arguments written without default values in the Usage line

    • TWO arguments shown with default values for sample(), written exactly as they appear in the Usage section

👀 Cite: Name the sample() help page and explain which section helped you identify the arguments and defaults.

🗣 Add Comments: Explain how the Usage line helped you distinguish arguments written without defaults from arguments shown with default values.

🪞 Reflect: What part of the help page was easiest to interpret? What part was still confusing or too technical?

Task 2

🛠 Build a random data set

🎯 Create scores as 28 values. That means you will create 25 random integers from 60 through 100, then add 3 missing values (NA) at the end.

  1. First, use seq() to create the possible score values from 60 through 100. Store that sequence as pool.

💡 Tip: For this task, practice using seq() with separate inputs for the starting value and ending value. Do not use the : shortcut inside seq() here. If you are unsure what arguments seq() expects, look at ?seq and focus on the Usage line.

  1. Then use sample() to draw 25 values from pool, with replacement. Store those sampled values as scores_numbers.

  2. Finally, use c() to add three NA values to the end of scores_numbers. Store the full vector as scores.

# ⚡ Type this in your R Notebook, Fill in the blanks
set.seed(1) 

pool <- ____                      # use seq() to create 60 through 100
scores_numbers <- ____            # use sample() to draw 25 values (with replacement)
scores <- c(____, NA, NA, NA)     # add 3 `NA` to the end of `scores_numbers` using `c()`

# ✅ Check the length of scores
_____(scores)                 

🗣 Add Comments: Explain why scores has 28 elements even though sample() only drew 25 values.

🪞 Reflect: Which part of the code built the possible values, which part randomly selected values, and which part added missing values? How did checking length(scores) confirm your thinking?

Task 3

⚠️ Handle missing data

🎯 Using scores from Task 2:

  1. Count missing values. Store as n_missing.

  2. Compute the mean with defaults. Store as average_raw.

  3. Compute the mean excluding NAs and store it as average_score.

  4. Explain why average_raw behaves the way it does (use the word “default”).

n_missing <- ____                 # hint: is.na() + sum()
# ✅ Check the number of missing values
____        # should print 3
# ⚡ Type this in your R Notebook, Fill in the blanks
average_raw <- mean(____)         # what happens?
# ✅ Check the result
____                              # should print NA
# ⚡ Type this in your R Notebook, Fill in the blanks
average_score <- mean(____, ____)
# ✅ print the mean
____

🗣 Add Comments: Explain why average_raw returns NA. Use the word default in your explanation.

🪞 Reflect: Before you ran the code, did you expect R to ignore the missing values or return NA? How did this task change your understanding of how R handles missing values?

Task 4

🔎 Locate missing values

🎯 Using scores from Task 2:

  1. Use is.na() to create a logical vector showing which elements of scores are missing; store it as scores_is_missing.

  2. Nest is.na() inside which() to find the positions (indices) of the missing values; store as missing_positions.

  3. Verify your result

# ⚡ Type this in your R Notebook, Fill in the blanks
scores_is_missing <- is.na(____)

missing_positions <- which(____(____))
# ✅ Check
print(____)        # should print a logical vector
print(____)        # should print indices of missing values
length(____)       # should print 3

🗣 Add Comments: Explain the difference between the output of is.na(scores) and the output of which(is.na(scores)).

🪞 Reflect: Which function from this chapter would you use if you wanted one TRUE or FALSE answer about whether scores contains missing values? Which output from this task would you use to inspect or replace the exact missing positions?

Task 5

📦 Nesting functions

🎯 Compute the mean of 5 random draws from scores in one line, and make sure it still works even if the draw includes NA. 💡 Tip: Read the nested code from the inside out. sample() should happen first. Then mean() should summarize the sampled values. Use na.rm = TRUE inside mean() so the mean can still be calculated if the sample includes NA.

# ⚡ Type this in your R Notebook, Fill in the blanks
set.seed(3)
mean_of_five <- ____   
# ✅ Check  
mean_of_five

🗣 Add Comments: Explain which function runs first in your nested code and why na.rm = TRUE is needed.

🪞 Reflect: Was the nested version easier or harder to understand than writing the steps separately? If you were debugging this code, would you keep it nested or break it into smaller objects? Explain why.

🦁 Naming that the nested version is difficult gives you useful information. You can change strategies by breaking the code into smaller objects and checking each result.

Task 6

🕰 Quick time-stamp

🎯 Use Sys.Date() and Sys.time() to create two separate objects, today_date and now_time.

# ⚡ Type this in your R Notebook, Fill in the blanks
today_date <- ____
now_time <- ____

🗣 Add Comments: Explain the difference between the output of Sys.Date() and Sys.time().

🪞 Reflect: Why might a researcher want a report to include the date or time when the code was run? How could a time-stamp support reproducibility or transparency?

Task 7

🧹 Handling Missing Values

🎯 Using scores from Task 2:

Part A: Remove NA values from a vector

  1. Create scores_clean by keeping only the observed (non-missing) values in scores.

  2. Verify that scores_clean has 3 fewer values than scores.

# ⚡ Type this in your R Notebook, Fill in the blanks

scores_clean <- scores[!______(_________)]

# ✅ Checks

length(scores)          # should print 28
length(scores_clean)    # should print 25

🗣 Add Comments: Explain what is.na(scores) identifies, what !is.na(scores) changes, and how bracket indexing uses that result to create scores_clean.

🪞 Reflect: Why is removing missing values simple for one vector but potentially risky in a larger dataset with many columns?

Part B: Impute: replace NA with the mean of present values

  1. Make a copy of scores named scores_imputed (so you don’t overwrite the original).

  2. Replace the missing values in scores_imputed with the mean of the observed values.

  3. Verify there are 0 missing values after imputation.

# ⚡ Type this in your R Notebook, Fill in the blanks
scores_imputed <- _________

scores_imputed[____(_)] <- mean(_____, na.rm = ______)
# ✅ Checks

sum(is.na(scores_imputed))     # should print 0
mean(scores_imputed)           # should print a number (not NA)

🗣 Add Comments: Explain why you made a copy named scores_imputed before replacing missing values.

🪞 Reflect: What assumption are you making when you replace missing values with the mean? Why should this choice be documented instead of treated as automatic?

Task 8

🗣 Reflection

In one sentence, explain the difference between a function object such as mean and a function call such as mean().

🪞 Reflect: What kind of mistake might happen if you type a function name without parentheses when you meant to run the function?

Finish and verify your notebook

🎯 Step 1: Sweep your Environment (start clean).
Click the 🧹 broom icon in the Environment pane to clear objects.

🎯 Step 2: Run everything top-to-bottom.
Use Run → Run All to execute every chunk in order.

🎯 Step 3: Save, knit, and inspect. After your notebook runs without errors, save the .Rmd file and knit it to HTML. RStudio will usually create a .nb.html file for an R Notebook, although some workflows create a .html file.

✅ Check: Open the HTML output and confirm that it includes the expected headings, code, output, and memos.

💡 Tip: The HTML output opens in a web browser, but it remains a file saved on your computer. If you need to share it, send or upload the HTML file itself rather than copying the local address from the browser.


🦁 Use the final reflection to make your thinking visible, including any uncertainty or change in strategy.

🪞 Final Reflection: Identify one place where your code did not behave as expected. What did you understand, what remained uncertain, and what did you check, change, or ask about next? How much time did you spend completing the Practice Space?