Vector Operations

R vectors are dimensionless objects. Unlike mathematical column vectors, R vectors don’t have rows or columns, but they do have length. We can check the length of a vector using the length() function.

y <- 1:4
length(y)
[1] 4

We can check the atomic mode of a vector (i.e., what type of data it represents) with the typeof() function.

typeof(1:3)
[1] "integer"
typeof("foo")
[1] "character"
typeof(FALSE)
[1] "logical"

Predict: What will the following code print?

The first line uses vector() to create a length-three complex vector called x. So, the second line prints "complex", since that’s the atomic mode of x.

Arithmetic

Arithmetic in R works element-wise when applied to vectors. That is, the requested operation is performed between matched elements of the vectors.

Going Further

If you’re familiar with matrix algebra or other programming languages, the way R handles arithmetic with vectors may seem strange. For arithmetic operations, R vectors behave much like arrays would in other programming languages.

(x <- rep(2, 4))
[1] 2 2 2 2
y + x
[1] 3 4 5 6
y - x
[1] -1  0  1  2
y / x
[1] 0.5 1.0 1.5 2.0
y * x
[1] 2 4 6 8

Recycling

Tricky Topic

Recycling is a ubiquitous, and often silent, feature of R. Recycling is often happening behind the scenes, and you won’t be explicitly notified (via a warning or error) in most cases. Take your time with this section and try to really understand the concept of recycling before progressing.

If two vectors involved in an element-wise operation have different lengths, R will recycle the elements of the shorter vector until each element in the longer vector can be matched with an element from the shorter vector.

In the following code chunk, R is using recycling to calculate the elements of w. Can you see how?

Do: Explain how the recycling algorithm works in this example.

z <- c(1, 2)
w <- y - z
w
[1] 0 0 2 2

In this example, y has four elements, but z has only two. So, R re-uses the two elements in z to define matches for all four elements in y and specify the four differences needed to define the elements of w.

  1. \(w_1 = y_1 - z_1\)
  2. \(w_2 = y_2 - z_2\) \(\leftarrow\) After calculating the second difference, we’ve run out of elements in z.
  3. \(w_3 = y_3 - z_1\) \(\leftarrow\) Beginning with the third difference, we start recycling the elements of z.
  4. \(w_4 = y_4 - z_2\)
Important

Notice how the example above runs without any explicit indication of that recycling has been used. If the length of the longer vector is divisible by the length of the shorter vector, R will recycle silently (i.e., without any messages, warnings, or errors). So, you need to vigilant and make sure you don’t accidentally trigger recycling when you don’t want it.

In cases where the length of the shorter vector doesn’t evenly divide the length of the longer vector, R will still use execute the requested operation and apply recycling, but it will also return a warning.

a <- 1:4
b <- 1:2
c <- 1:5

# Silent recycling
a + b
[1] 2 4 4 6
# Recycling with warning
a + c
Warning in a + c: longer object length is not a multiple of shorter object
length
[1] 2 4 6 8 6

Why Recycling is Useful

At this point, if you have any predilection toward strictly typed programming languages, all this recycling business might sound like insanity, but there’s a method to the madness. Let’s consider a few examples that demonstrate how recycling can actually be very helpful in day-to-day programmatic data analysis.

The following code looks like it should implement scalar multiplication, and the printed vector looks like the result of a scalar multiplication. But R doesn’t have scalars, and vector arithmetic in R doesn’t follow standard linear algebraic rules, so our intuitive reading of this expression can’t be correct. Can you see what R is actually doing?

Do: Explain how R implements the following calculation.

3 * y
[1]  3  6  9 12

In fact, the preceding code is actually using recycling to do element-wise multiplication of a length-one vector, 3, and a length-four vector, y. Recycling the elements of the length-one vector resolves the length difference. In this example, recycling provides two benefits.

  1. We get to write code that reads like the mathematical representation of the operation we’re trying to implement.
  2. Our code produces the same result as the mathematical operation it appears to represent.

The following three expressions follow the same pattern as the last example but provide more compelling evidence for the real-world usefulness of recycling. These expressions don’t represent valid operations under the conventional rules of linear algebra, and they wouldn’t be valid commands in a highly structure programming language like C. Yet, the intended operations are intuitively obvious, and R will use recycling to execute the operations we expect.

# Subtract 1 from every element in y
y - 1
[1] 0 1 2 3
# Add 3 to every element in y
y + 3
[1] 4 5 6 7
# Divide every element in y by 3
y / 3
[1] 0.3333333 0.6666667 1.0000000 1.3333333

Logical Tests

Logical comparisons between vectors test some logical relation between the vectors and return a logical vector that encodes the answer to whatever TRUE/FALSE question we asked with the test. These comparisons follow the same rules as vector arithmetic:

  • Operators are applied element-wise
  • Recycling is used to resolve length differences
# Check which elements of y are equal to 4
y == 4
[1] FALSE FALSE FALSE  TRUE
# Check which elements of y are equal to 4 or 2
y == 4 | y == 2
[1] FALSE  TRUE FALSE  TRUE

We need to be especially mindful of the vector operation rules when applying logical comparisons to vectors. When defining logical tests, intuition can easily fail us. For example, it would be natural to read the following code as implementing the same test as the previous expression, but the two results are clearly different. Why?

Do: Explain why the following command returns a different result from the previous command.

y == c(4, 2)
[1] FALSE  TRUE FALSE FALSE

As you’ve probably surmised, recycling is causing the discrepancy. We can clarify the differences between the two tests by extending the shorter vectors to manually implement recycling.

# Manual recycling for y == 4 | y == 2
y == c(4, 4, 4, 4) | y == c(2, 2, 2, 2)
[1] FALSE  TRUE FALSE  TRUE
# Manual recycling for y == c(4, 2)
y == c(4, 2, 4, 2)
[1] FALSE  TRUE FALSE FALSE

Now, the two tests don’t appear very similar, and their different outcomes aren’t particularly surprising.

Practice
  1. Create the object myVec by running the two lines of code below.
  2. Programmatically create a logical vector that indicates which elements of myVec are (strictly) less than 3.
set.seed(235711)
myVec <- sample(1:5)

myVec < 3
[1]  TRUE FALSE FALSE FALSE  TRUE
Knowledge Check
  • What does it mean for R to “recycle” a vector, and when does it happen?
  • Why does R warn you in some recycling cases but not others?
  • y == c(7, 5) and y == 5 | y == 7 look like they should test the same thing, but they don’t. Explain why.
Back to top