Which observations belong in an analysis? Selecting variables keeps columns; selecting observations keeps rows. In either case, keeping some of the data also excludes the rest. Here we will distinguish selecting by a row’s current position from selecting by its recorded values.
By the end of this chapter, you will be able to:
Select the first or specified rows of a table.
Combine conditions to retain the observations you need.
Explain what happens when a condition is unknown.
Define and check an analysis subset without changing the source data.
This chapter builds on selecting columns, sorting observations, and the pipe. We continue with the familiar country table throughout, selecting observations by their positions and recorded measurements. Comparisons and missing-value checks are introduced here as we need them. No optional programming chapter is required.
Continue with the packaged country data used in the preceding chapters. Keep the country name, region, life expectancy, income per person, and safe-water coverage:
# A tibble: 68 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl> 1 Albania 1 74 870 49
2 Armenia 1 70 570 83
3 Austria 1 78 27810 97
4 Azerbaijan 1 64 510 49
5 Belarus 1 68 1540 82
6 Belgium 1 77 26530 100
7 Bosnia and Herzegovina 1 74 1230 87
8 Bulgaria 1 71 1440 90
9 Croatia 1 72 NA 85
10 Czechia 1 75 6140 95
# ℹ 58 more rows
Each row is one country. region contains the labelled region codes introduced in Chapter 3; lexp records life expectancy in years, gnppc records income per person in current US dollars, and safewater records safe-water access in percent. Most measurements refer to 1998; water coverage refers to 2000. Missing measurements remain NA. All 68 countries remain. A row position tells us where a record currently appears; the country name identifies it when the order changes.
6.1 First rows
To inspect the beginning of the records, pass the table to the familiar head(). Its default result contains the first six rows:
The result contains six countries and the same five columns, showing the beginning of the current order. These positions do not identify typical countries. The original countries still contains all 68 rows.
head() accepts n, the number of rows to return. We can store the first three records separately:
first_three <-head(countries, n =3)first_three
# A tibble: 3 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Albania 1 74 870 49
2 Armenia 1 70 570 83
3 Austria 1 78 27810 97
nrow(first_three)
[1] 3
nrow(countries)
[1] 68
first_three has three rows; countries has 68. The printing helpers in Chapter 4 display selected rows while returning the complete object. head() selects data. Later calculations on first_three would use only those three countries.
6.2 Row positions
Suppose we need the fifth record in the current table. slice() selects rows by position:
countries |>slice(5)
# A tibble: 1 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Belarus 1 68 1540 82
# A tibble: 67 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl> 1 Albania 1 74 870 49
2 Armenia 1 70 570 83
3 Austria 1 78 27810 97
4 Azerbaijan 1 64 510 49
5 Belgium 1 77 26530 100
6 Bosnia and Herzegovina 1 74 1230 87
7 Bulgaria 1 71 1440 90
8 Croatia 1 72 NA 85
9 Czechia 1 75 6140 95
10 Denmark 1 76 33440 95
# ℹ 57 more rows
The first result is country Belarus, which currently occupies row 5. In the second call, 1:5 supplies the sequence of positions 1 through 5, so five records remain. The negative position in the last call excludes row 5 and keeps the other 67 countries. It does not change any recorded life expectancy. Use positive positions to keep rows or negative positions to exclude them; do not mix the two in one selection.
The c() function stored quoted column names in Section 4.1. It can also combine several numeric row positions. Their order determines the returned order:
countries |>slice(c(5, 1))
# A tibble: 2 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Belarus 1 68 1540 82
2 Albania 1 74 870 49
Country Belarus appears before country Albania. Repeating a position would repeat its row. This flexibility is useful, but a position is not a permanent identity. For example, sorting by life expectancy before selecting position 1 changes the result:
countries |>arrange(lexp) |>slice(1)
# A tibble: 1 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Haiti 2 57 410 NA
The result is the first country among those tied at the lowest recorded life expectancy. Without another sorting key, a tie retains the previous row order. All columns moved together during sorting. To select a particular country regardless of order, use a condition on the country name, following the equality examples below.
6.3 Conditions
6.3.1 Read a condition
A comparison asks whether a condition holds. Store a number to use in the following comparisons:
x <-8
Is x at least 5, greater than 8, or equal to 8?
x >=5
[1] TRUE
x >8
[1] FALSE
x ==8
[1] TRUE
The results are TRUE, FALSE, and TRUE. These answers are logical values: var_type(TRUE) returns "logical". Write TRUE and FALSE in full without quotation marks; "TRUE" is text.
Table 6.1 collects all six comparison operators using x <- 8.
Table 6.1: Comparison operators with x equal to 8.
Operator
Question
Example
Result
<
Less than?
x < 8
FALSE
<=
Less than or equal to?
x <= 8
TRUE
>
Greater than?
x > 8
FALSE
>=
Greater than or equal to?
x >= 8
TRUE
==
Equal to?
x == 8
TRUE
!=
Different from?
x != 8
FALSE
The two equals signs in == ask about equality. They neither assign a value nor name an argument. Text comparisons are case-sensitive:
text <-"hello"text =="hello"
[1] TRUE
text =="Hello"
[1] FALSE
Only the first comparison is TRUE. The capital letter changes the string.
6.3.2 Combine simple conditions
To check whether x falls between 5 and 10, inclusive, combine two conditions with &, meaning and:
(x >=5) & (x <=10)
[1] TRUE
Both comparisons hold for 8, so the result is TRUE. Parentheses separate the two comparisons. Use |, a vertical bar, for or:
(x ==2) | (x ==8)
[1] TRUE
This is TRUE because the second comparison holds. This is inclusive “or”: at least one condition must hold; both may hold when the conditions allow it.
%in% asks whether a value matches any member of a collection:
x %in%c(2, 8)
[1] TRUE
c() combines these two numbers into a vector, preserving their order. Commas separate the inputs. The membership condition gives the same TRUE result as the preceding example. The ! operator means not:
!(x %in%c(2, 8))
[1] FALSE
The parentheses group the membership test. Its result was true, so negating it returns FALSE. A single function call can also follow ! directly, as in !is.na() later.
6.3.3 One comparison per value
Compare two numbers with the same threshold:
c(2, 8) >=5
[1] FALSE TRUE
R returns FALSE and TRUE, in that order. It compares each value with 5. This is an elementwise operation: one result for each input, in the same position. Table columns work on this principle, which lets filter() decide whether to retain each row.
6.3.4 Apply conditions to rows
We now apply these comparisons to countries. Positions describe where rows are; conditions describe which recorded values meet our question. filter() takes a table and retains the rows for which a condition is TRUE. Inside it, a column name refers to the whole column. We do not need to write countries$gnppc.
< means less than. The comparison returns TRUE when a recorded income meets the rule, FALSE when it does not, and NA when income is unknown. Which countries have recorded income per person below $10,000?
The result contains 48 countries, with their original columns and values. The comparison applies to each income. The 1 country with unknown income does not appear: an unknown income is not known to be below $10,000.
For a country name, use == to test equality and quote it exactly as recorded:
countries |>filter(country =="Canada")
# A tibble: 1 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Canada 2 79 20900 99
countries |>filter(gnppc ==870)
# A tibble: 2 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Albania 1 74 870 49
2 Georgia 1 70 870 59
The first result locates Canada independently of its row position. The second finds countries with recorded income per person of $870. That measurement is numeric, so its value is unquoted. Two countries share this value; income is not a country identifier. Text comparisons are case-sensitive: "Canada" differs from "canada". These calls return selected tables; neither changes countries.
6.3.5 Combining conditions
To retain countries with life expectancy from 70 through 75 years inclusive, both bounds must hold. Use & for and. >= means greater than or equal to, and <= means less than or equal to:
countries |>filter((lexp >=70) & (lexp <=75))
# A tibble: 28 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl> 1 Albania 1 74 870 49
2 Armenia 1 70 570 83
3 Bosnia and Herzegovina 1 74 1230 87
4 Bulgaria 1 71 1440 90
5 Croatia 1 72 NA 85
6 Czechia 1 75 6140 95
7 Georgia 1 70 870 59
8 Hungary 1 71 4420 83
9 Lithuania 1 71 2870 70
10 North Macedonia 1 72 2010 80
# ℹ 18 more rows
countries |>filter(lexp >=70, lexp <=75)
# A tibble: 28 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl> 1 Albania 1 74 870 49
2 Armenia 1 70 570 83
3 Bosnia and Herzegovina 1 74 1230 87
4 Bulgaria 1 71 1440 90
5 Croatia 1 72 NA 85
6 Czechia 1 75 6140 95
7 Georgia 1 70 870 59
8 Hungary 1 71 4420 83
9 Lithuania 1 71 2870 70
10 North Macedonia 1 72 2010 80
# ℹ 18 more rows
Both commands retain 28 countries. In filter(), separate conditions divided by commas also mean that all must hold. The parentheses in the first version make the two comparisons clear. If a required measurement were unknown, the row would be excluded unless the combined condition could still be known to be true.
Use | for inclusive or. To keep Poland or countries with safe-water coverage of at least 95 percent:
countries |>filter((country =="Poland") | (safewater >=95))
A country qualifies if either condition is true. Poland is retained because its name meets the first condition, even though its water coverage is unknown. Cuba, whose water coverage is also unknown, does not qualify: one condition is false and the other is unknown. The vertical bar | combines conditions; the pipe |> passes a result to another function. They serve different purposes.
When several exact values are acceptable, %in% asks whether each value matches one of the supplied alternatives. c() combines those alternatives, with commas between them:
countries |>filter(country %in%c("Canada", "Albania", "Armenia"))
# A tibble: 3 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Albania 1 74 870 49
2 Armenia 1 70 570 83
3 Canada 2 79 20900 99
countries |>filter(gnppc %in%c(960, 870))
# A tibble: 4 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Albania 1 74 870 49
2 Georgia 1 70 870 59
3 Honduras 2 62 960 42
4 Bolivia 3 61 960 NA
The first result contains those three countries in their original row order. Unlike slice(c(5, 1)), the order of alternatives inside c() does not rearrange the rows. The second selects four countries whose recorded income is exactly $960 or $870, again retaining their original order. Membership can test text or numerical values. Use & and | for column conditions; the similar-looking && and || are intended for single logical decisions rather than these elementwise comparisons.
A condition can run without an error and still select the wrong countries. Suppose we want the same recorded incomes, $960 or $870, but omit the second comparison. Compare the row counts:
income_alternatives_wrong <- countries |>filter(gnppc ==960|870)income_alternatives_checked <- countries |>filter(gnppc %in%c(960, 870))nrow(income_alternatives_wrong)
[1] 68
nrow(income_alternatives_checked)
[1] 4
The first result keeps all 68 countries; the second keeps 4. In the first condition, R interprets the bare nonzero number 870 as TRUE. The OR therefore succeeds for every row, even Croatia’s unknown income. Write a complete comparison on both sides, gnppc == 960 | gnppc == 870, or use the membership test shown above. Check whether the selected records answer the question, rather than treating the absence of an error as evidence that the condition is correct.
6.4 Unknown conditions
filter() keeps only TRUE. It excludes both FALSE and NA, but those conditions convey different information. To examine a column outside a table operation, write the table name, $, and the column name. countries$gnppc returns the income vector in the same country order, including missing values. It neither removes the column nor changes countries. This extraction is useful even if you skipped the optional extraction example in Chapter 5.
We can see the distinction before selecting:
(countries$gnppc >=10000) |>head()
[1] FALSE FALSE TRUE FALSE FALSE TRUE
The comparison creates one logical result per country; head() selects the first six results for inspection. Known incomes at least $10,000 return TRUE, lower incomes return FALSE, and unknown incomes return NA. Croatia, farther down the table, has the missing income measurement.
is.na() takes values and returns one logical result per input: TRUE for an unavailable value and FALSE otherwise. First follow it on plain inputs:
is.na(c(870, NA, 960))
[1] FALSE TRUE FALSE
Only the second result is TRUE. The function tests whether a value is missing; comparing with == NA would ask for equality with an unknown value and would itself be unknown. We can use this test alone to find the country whose income is unavailable:
countries |>filter(is.na(gnppc))
# A tibble: 1 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Croatia 1 72 NA 85
The result contains Croatia and its other recorded measurements. We have selected the missing record without assigning an income to it. To keep countries with income of at least $10,000 and also retain unknown incomes for inspection, state both possibilities:
high_or_unknown <- countries |>filter((gnppc >=10000) |is.na(gnppc))high_or_unknown
This result retains countries with recorded income of at least $10,000, together with Croatia, whose income is unknown. It is a table of high or unknown incomes, not a table in which every country is known to have high income. To retain only known incomes, filter(!is.na(gnppc)) explicitly excludes missing measurements.
Use != for not equal to. To negate membership, put ! before the parenthesized %in% test. ! reverses true and false, but an unknown comparison stays unknown:
countries |>filter(gnppc !=870)
# A tibble: 65 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl> 1 Armenia 1 70 570 83
2 Austria 1 78 27810 97
3 Azerbaijan 1 64 510 49
4 Belarus 1 68 1540 82
5 Belgium 1 77 26530 100
6 Bosnia and Herzegovina 1 74 1230 87
7 Bulgaria 1 71 1440 90
8 Czechia 1 75 6140 95
9 Denmark 1 76 33440 95
10 Estonia 1 69 3820 92
# ℹ 55 more rows
countries |>filter(!(gnppc %in%870))
# A tibble: 66 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl> 1 Armenia 1 70 570 83
2 Austria 1 78 27810 97
3 Azerbaijan 1 64 510 49
4 Belarus 1 68 1540 82
5 Belgium 1 77 26530 100
6 Bosnia and Herzegovina 1 74 1230 87
7 Bulgaria 1 71 1440 90
8 Croatia 1 72 NA 85
9 Czechia 1 75 6140 95
10 Denmark 1 76 33440 95
# ℹ 56 more rows
The first result contains countries with a known income other than $870. It excludes Croatia’s missing measurement. The second also keeps Croatia: %in% returns FALSE for NA when the alternatives contain only 870, and negation makes that result TRUE. Neither operation has discovered Croatia’s income.
If the intended rule is “known income other than $870,” make the missingness decision explicit:
countries |>filter(!is.na(gnppc), !(gnppc %in%870))
# A tibble: 65 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl> 1 Armenia 1 70 570 83
2 Austria 1 78 27810 97
3 Azerbaijan 1 64 510 49
4 Belarus 1 68 1540 82
5 Belgium 1 77 26530 100
6 Bosnia and Herzegovina 1 74 1230 87
7 Bulgaria 1 71 1440 90
8 Czechia 1 75 6140 95
9 Denmark 1 76 33440 95
10 Estonia 1 69 3820 92
# ℹ 55 more rows
Only countries with a recorded income other than $870 remain. Selecting an analysis subset therefore requires more than a convenient expression: we need to decide how unknown measurements affect inclusion.
For the next abstract checks, set x <- NA; c() combines the stated alternatives.
x <-NA
%in% tests membership and never returns NA. Compare:
x ==2
[1] NA
x %in%c(2, 6)
[1] FALSE
The comparison returns NA; the membership test returns FALSE because the stored value NA does not match either alternative. This does not establish what the missing number would have been.
Sometimes the known part of a combined condition settles the result even when another part is missing:
FALSE&NA
[1] FALSE
TRUE|NA
[1] TRUE
The first is FALSE: both parts cannot be true if one is already false. The second is TRUE: at least one part is true regardless of the unknown part. An expression containing NA can therefore have a known result.
In the next chapter, Section 7.3, na.rm = TRUE asks summary functions to omit missing inputs from a calculation. It does not fill in missing values or delete them from the original object.
6.5 A country analysis subset
Suppose our next analysis will compare income and water coverage among countries with life expectancy of at least 75 years. Life expectancy alone defines this subset; we are not yet requiring reported income or water coverage.
Use a condition, then the familiar select() to keep the country identifier and measurements needed for this question. Assign the result a new name:
high_lifeexp <- countries |>filter(lexp >=75) |>select(country, lexp, gnppc, safewater)head(high_lifeexp)
# A tibble: 6 × 4
country lexp gnppc safewater
<chr><dbl><dbl><dbl>1 Austria 78 27810 97
2 Belgium 77 26530 100
3 Czechia 75 6140 95
4 Denmark 76 33440 95
5 Finland 77 25810 94
6 France 79 25740 97
nrow(countries)
[1] 68
nrow(high_lifeexp)
[1] 23
The original has 68 countries; high_lifeexp contains 23. Each retained row has life expectancy of at least 75 years. Missing water measurements remain, and head() displays only the first six rows. The rule defines the countries available for the proposed comparison; it does not require a measurement for every variable or establish why life expectancy differs. The original countries is unchanged.
6.6 Optional selection tools
The following tools are useful for particular tasks. They are not prerequisites for the exercises.
6.6.1 Display or select rows?
The printing helpers discussed in Chapter 4 and later in Section 8.1.1 display rows from both ends or at intervals while returning the full table unchanged. They do not select a smaller dataset. Use the slice() positions taught above when you need to retain particular rows for a later calculation. Evenly spaced display positions are not a probability sample.
6.6.2 A random subset
slice_sample() is a variant of slice() for selecting rows at random rather than by stated positions. Here n = 3 requests three different records: sampling is without replacement by default. It returns the selected rows with all original columns; assignment keeps that subset separately.
For a reproducible illustration, first use set.seed() to set the random-number generator’s starting state. Its numeric argument is a chosen starting value, not a requested country or sample size:
set.seed(2026)
Now select the records:
random_three <- countries |>slice_sample(n =3)random_three
# A tibble: 3 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Tajikistan 1 62 170 36
2 Canada 2 79 20900 99
3 Sweden 1 79 31100 99
The output has three rows and the original five columns. Rerunning the seed command followed by the selection in the same software environment reproduces it. It is a random subset of the countries in this table. Randomly selecting records from a dataset does not by itself make them representative of a wider population.
6.6.3 Distinct values and records
distinct() returns non-repeated combinations of the columns supplied. With just region, it returns the three observed region codes:
countries |>distinct(region)
# A tibble: 3 × 1
region
<hvn_lbll>1 1
2 2
3 3
The result has one column and one row for each observed region code. Its rows describe distinct recorded values, not distinct countries. If the supplied column contained missing values, one missing value would also remain.
Two different countries can have the same life expectancy and region. We can inspect the combinations without treating them as duplicate countries:
combinations <- countries |>select(lexp, region)combinations |>distinct()
With no columns specified, distinct() compares all columns in combinations. It returns one row per different life-expectancy–region combination, treating repeated missing values as the same value for this purpose. The second command also keeps one row per combination, but .keep_all = TRUE retains the country name, income, and water coverage. Those values belong to the first country encountered with each combination; they do not describe every country sharing it.
These commands discard repeated combinations for a particular inspection task; they do not establish that a country was entered twice. Selecting by country name would answer a different question. Repeated country names can be legitimate in data observed across years, so investigate the observation unit before removing records.
6.7 Review
head() returns the first rows; slice() selects rows by their current positions.
filter() retains rows whose conditions are TRUE, excluding both FALSE and NA conditions.
& requires both conditions, | requires either, %in% tests membership, and ! negates a result.
is.na() identifies unknown values; decide explicitly whether they should remain, especially when negating membership.
nrow() checks how many observations remain; assign the subset separately to preserve the source.
Optional:slice_sample() selects random rows, set.seed() makes that selection reproducible in the same environment, and distinct() returns unique values or combinations.
The next part begins with Chapter 7. We continue with the country table and use the selection skills learned here to inspect and describe its contents.
6.8 Exercises
Keep country, region, lexp, gnppc, and safewater from lifeexp as countries. Select positions 5 and 1, in that order, then repeat after sorting by life expectancy and country name. Explain why the countries can change while their measurements remain correctly aligned.
First set x <- 4. Test whether it lies between 5 and 10, inclusive, and whether it belongs to c(2, 4, 6). Set x <- NA and repeat the interval test. Explain all three answers. Then keep countries with life expectancy from 70 through 75 years inclusive and income per person below $5,000. Report how many remain. Explain why Croatia is excluded and whether that establishes its income.
Compare filter(gnppc != 870) with filter(!(gnppc %in% 870)). Count the additional countries retained by the second rule. Write a rule requiring known income other than $870.
Using lifeexp, create long_lived containing country, lexp, and safewater for countries with life expectancy of at least 75 years. Report its row count and explain whether missing water measurements have been removed. Check that the original row count is unchanged.
Worked answers
NotePositions and order
Each answer can start in a fresh session with the packaged country data:
# A tibble: 2 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Belarus 1 68 1540 82
2 Albania 1 74 870 49
countries |>arrange(lexp, country) |>slice(c(5, 1))
# A tibble: 2 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Kazakhstan 1 63 1340 63
2 Haiti 2 57 410 NA
The first result contains the countries at the fifth and first positions in the original table. Sorting moves whole records, so the second draws from the low end of the life-expectancy distribution. Country name resolves tied life expectancies explicitly. Row positions change; a country’s measurements move together.
NoteConditions and two requirements
x <-4(x >=5) & (x <=10)
[1] FALSE
x %in%c(2, 4, 6)
[1] TRUE
x <-NA(x >=5) & (x <=10)
[1] NA
The first is FALSE because 4 is below 5. Membership is TRUE because 4 is one of the alternatives. The last condition is NA: we cannot place an unknown value inside or outside the interval.
# A tibble: 6 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl>1 Albania 1 74 870 49
2 Armenia 1 70 570 83
3 Bosnia and Herzegovina 1 74 1230 87
4 Bulgaria 1 71 1440 90
5 Georgia 1 70 870 59
6 Hungary 1 71 4420 83
nrow(selected_countries)
[1] 21
The result contains 21 countries. All three comparisons must be true. Croatia’s life expectancy is in the requested range, but its income is unknown, so the combined condition is unknown and the country is excluded. That does not establish whether its income is below or above $5,000.
NoteNegating an unknown value
library(tidyverse)library(tidystata)countries <-select(lifeexp, country, region, lexp, gnppc, safewater)comparison <- countries |>filter(gnppc !=870)membership <- countries |>filter(!(gnppc %in%870))nrow(membership) -nrow(comparison)
[1] 1
countries |>filter(!is.na(gnppc), !(gnppc %in%870))
# A tibble: 65 × 5
country region lexp gnppc safewater
<chr><hvn_lbll><dbl><dbl><dbl> 1 Armenia 1 70 570 83
2 Austria 1 78 27810 97
3 Azerbaijan 1 64 510 49
4 Belarus 1 68 1540 82
5 Belgium 1 77 26530 100
6 Bosnia and Herzegovina 1 74 1230 87
7 Bulgaria 1 71 1440 90
8 Czechia 1 75 6140 95
9 Denmark 1 76 33440 95
10 Estonia 1 69 3820 92
# ℹ 55 more rows
The difference is 1, exactly the number of missing income measurements. The inequality comparison is unknown for Croatia; membership against the supplied value is false, so negating membership retains it. The final rule explicitly excludes unknown incomes.
The subset contains 23 countries with life expectancy of at least 75 years. The final check is TRUE: the original still has 68 rows. Missing safewater values remain because no condition required known water coverage. The result describes these countries at the recorded dates; it does not show change over time.