Preamble

Logical Conditions

In the previous lab, we introduced logical values as one of the basic types of data in R. Logical values can take one of two values: TRUE or FALSE. We can create logical values by asking R whether some condition is true.

For example, the comparison operators <, >, <=, >=, ==, and != can be used to compare values:

5 > 3
## [1] TRUE
5 == 3
## [1] FALSE
5 != 3
## [1] TRUE

Each of these expressions asks R a question and returns either TRUE or FALSE. The == operator asks whether two values are equal, while != asks whether they are not equal. Notice that == is different from the assignment operators <- and = that we used in the previous lab.

Logical conditions become especially useful when applied to vectors. Because many operations in R are vectorized, comparing a vector to a value performs the comparison separately for every element of the vector:

x = c(2, 5, 8, 11, 14)
x > 7
## [1] FALSE FALSE  TRUE  TRUE  TRUE

The result is another vector containing TRUE or FALSE for each element of x. This is called a logical vector. Each value tells us whether the corresponding element of x satisfies the condition x > 7.


Lab

The remainder is to be worked through by you.

Logical Operations

A few logical operators you should know of are:

Operator | Description

== | equal to

!= | not equal to

> | greater than

>= | greater than or equal to

< | less than

<= | less than or equal to

&| and

| | or

! | negation (“not”)

Question 1: Using the vector scores below complete the following parts:

scores = c(4, 7, 9, 3, 7, 10, 6)
  • A. Write a logical condition that determines which values are greater than 6. How many values in the scores vector satisfy the condition? (count the TRUE’s)

  • B. Write a logical condition that determines which values are less than or equal to 7.

  • C. Write a logical condition that determines which values are exactly equal to 7.

  • D. Write a logical condition that determines which values are not equal to 7.

  • E. Pick one of B-D. In plain text, briefly describe what each TRUE and FALSE in that resulting vector represents.

Identifying and Subsetting Values

On its own, a vector of TRUE’s and FALSE’s is not always the most helpful format for identifying the actual values that meet a condition. These next examples will continue to use the scores vector.

scores = c(4, 7, 9, 3, 7, 10, 6)
# Returns a logical vector using the condition "> 6"
scores > 6
## [1] FALSE  TRUE  TRUE FALSE  TRUE  TRUE FALSE

The which() function can be used to identify the indices (positions) where a logical condition evaluates to TRUE:

which(scores > 6)
## [1] 2 3 5 6

These indices can then be used to subset the original vector. Recall from the previous lab that square brackets [ and ] can be used to access elements of a vector by their position:

# Store the indices where scores > 6
keep_idx = which(scores > 6)

# Use those indices to extract the corresponding values
scores[keep_idx]
## [1]  7  9  7 10

We can also combine these steps into a single line without having to save the index to our Environment.

scores[which(scores > 6)]
## [1]  7  9  7 10

Notice the difference between these three operations by pasting them into your R console.

scores > 6 # Which positions satisfy the condition? TRUE/FALSE
which(scores > 6) # What are the indices of those positions?
scores[which(scores > 6)] # What values are stored at those positions?

Question 2: Using the scores vector, complete the following:

  • A. Use which() to find the indices of all values that are less than or equal to 7.

  • B. Use these indices to extract the corresponding values from scores.

  • C. In a single line of code, use which() and indexing to return all values of scores that are exactly equal to 7. How many are there?

Logical Operations and Data

Often we want to access all data that meet certain criteria, and so these logical condition operations can be used on data sets. For example, we may want to analyze all countries (using data from the previous lab) with a life expectancy above 80.

# load in data
HappyPlanet = read.csv("https://remiller1450.github.io/data/HappyPlanet.csv")

## This returns a logical vector using the condition "> 80"
HappyPlanet$LifeExpectancy > 80
##   [1] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE
##  [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [25]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
##  [49] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE
##  [61] FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE
##  [73] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [85] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [97] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [109] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
## [121] FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [133] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

For each country (row) in our data set, this vector tells us whether or not it meets this condition (Life expectancy greater than 80).

On its own, the previous list of TRUE’s and FALSE’s for the countries is not in a super helpful format for identifying the actual countries.

Just as with the scores vector, we can use which() to find the indices in the HappyPlanet data where this condition is TRUE. In a data frame, these indices correspond to rows.

## This returns the positions where the condition evaluated to TRUE
which(HappyPlanet$LifeExpectancy > 80)
##  [1]   6  25  47  57  59  65  66  68 119 122 123

Looking better! This result could then be used as an index to subset HappyPlanet (keep only the rows/countries of data meeting this condition):

## sub-setting via indices
keep_idx <- which(HappyPlanet$LifeExpectancy > 80)
my_subset <- HappyPlanet[keep_idx, ]

Again, the approach shown above is a bit cumbersome. As a more convenient alternative, we can use the subset() function alongside a logical expression, which will give us the desired rows without having to do the indexing:

## Example #1 (try this in your R console)
Ex1 <- subset(HappyPlanet, LifeExpectancy > 80)

In example #1, the data frame Ex1 will contain the subset of countries with life expectancy above 80. Notice how the subset() function knows that LifeExpectancy is a component of HappyPlanet.

## Example #2 (try this in your R console)
Ex2 <- subset(HappyPlanet, LifeExpectancy <= 70 & Happiness > 6)

In example #2, the & operator is used to create a data frame, Ex2, containing all countries with a life expectancy of 70 or below and a happiness score above 6. A country must meet both conditions to be included.

## Example #3 (try this in your R console)
Ex3 <- subset(HappyPlanet, LifeExpectancy <= 70 | Happiness > 6)

In example #3, the | operator is used to create a data frame of all countries with a life expectancy of 70 or below, or a happiness score above 6. Notice the different dimensions of Ex2 and Ex3:

dim(Ex2) # 9 rows, 11 columns
## [1]  9 11
dim(Ex3) # 118 rows, 11 columns
## [1] 118  11

Question 3:

  • A. Create a data frame named Q3A that contains all countries with a population over 100 million. Write code that shows me how many rows are in this data frame.

  • B. Create a data frame named Q3B that contains all countries with a happiness score of 6 or lower. How many rows are in this data frame?

  • C. Using the data frame Q3A that you created in part A, create a new data frame named Q3C that contains only countries with a happiness score of 6 or lower. How many countries are in this data frame?

  • D. Create a data frame named Q3D by applying both conditions from parts A and B at the same time in a single subset() function. Confirm that Q3C and Q3D contain the same number of countries. In part C you applied the conditions one at a time; here you are applying both conditions at once.

  • E. Give me a vector of the names of the countries in data frame Q3D.


Summarizing Logical Conditions

There are situations where logical vectors can also be useful without subsetting our data. When logical values are used in numerical calculations, R treats TRUE as 1 and FALSE as 0.

For example, consider our scores vector again:

scores = c(4, 7, 9, 3, 7, 10, 6)

We can use sum() function on this logical vector (which normally adds up all values in a vector):

scores > 6 # should be 4 TRUEs
## [1] FALSE  TRUE  TRUE FALSE  TRUE  TRUE FALSE
sum(scores > 6)
## [1] 4

Because each TRUE is treated as 1 and each FALSE as 0, the sum gives us the number of values that satisfy the condition.

Similarly, we can use mean() function, which gives us the proportion of values that satisfy the condition.

mean(scores > 6) 
## [1] 0.5714286
sum(scores > 6) / length(scores) # verifying average
## [1] 0.5714286

We can apply the same idea to logical conditions involving variables in a data frame:

# Number of countries with life expectancy above 80
sum(HappyPlanet$LifeExpectancy > 80)
## [1] 11
# Proportion of countries with life expectancy above 80
mean(HappyPlanet$LifeExpectancy > 80)
## [1] 0.07692308

Question 4: Use logical conditions and the sum() or mean() functions to answer each of the following questions. Do not create a new subset of the data.

  • A. How many countries have a Happiness score greater than 7?

  • B. What proportion of countries have a Happiness score greater than 7?

  • C. What proportion of countries do not belong to Region 1?

  • D. How many countries have both a life expectancy of at least 75 years and an HDI value more than 0.7?

  • E. What proportion of countries have an HDI value larger than the mean HDI value? What does this suggest about the skew of this variable? (Hint: make sure to use na.rm=TRUE).


Matching Values

Up to this point, most of our logical conditions have involved comparing numerical values using operators such as < and >.

For example, we’ve used conditions like the following to identify countries belonging to a particular region, which worked because Region was coded as an integer variable.

HappyPlanet$Region == 1

We can also use == to match values in categorical (character) variables too, but we have to be careful with spelling and case-sensitivity of the name.

which(HappyPlanet$Country == "United States of America")
## [1] 136

This works well when we are interested in a single value, but we may instead want to identify observations that match any of several possible values.

Suppose I wanted to make a subset of this data frame that contains information for a select group of countries: United States of America, India, Brazil, Japan, South Africa, and Germany. Let’s consider how we could accomplish it using the logical operators we already know.

subset(HappyPlanet,
       Country == "United States of America" |
       Country == "India" |
       Country == "Brazil" |
       Country == "Japan" |
       Country == "South Africa" |
       Country == "Germany")
##                      Country Region Happiness LifeExpectancy Footprint  HLY
## 18                    Brazil      1       7.6           71.7       2.4 54.3
## 49                   Germany      2       7.2           79.1       4.2 56.8
## 60                     India      5       5.5           63.7       0.9 35.1
## 68                     Japan      6       6.8           82.3       4.9 55.6
## 118             South Africa      4       5.0           50.8       2.1 25.2
## 136 United States of America      2       7.9           77.9       9.4 61.2
##       HPI HPIRank GDPperCapita   HDI Population
## 18  61.01       9         8402 0.800     186.83
## 49  48.07      51        29461 0.935      82.47
## 60  53.03      35         3452 0.619    1094.58
## 68  43.25      75        31267 0.953     127.77
## 118 29.69     118        11110 0.674      46.89
## 136 30.73     114        41890 0.951     296.51

When given a list of values that we are trying to match in the data set, we can instead use the %in% to do this with less typing. The %in% operator checks each value to see whether it matches any of the values in a specified vector. For readability, we can first store the group of values we want to check in its own vector, but this is not necessary.

six_countries = c("United States of America", "Brazil", "India", "Japan", "South Africa", "Germany")
subset(HappyPlanet, Country %in% six_countries)

# or, directly
subset(HappyPlanet, Country %in% c("United States of America", "Brazil", "India", "Japan", "South Africa", "Germany"))

Question 5