Binary Operators

We already know ho to define R objects. We will now turn to the question how to work with them. Generally speaking, we can work with R objects by performing operations on them. There are two ways of doing so: one is to use binary operators. Another way to perform operations on R objects is to feed them into functions as function arguments. Here, we will focus on binary operators.

Prelude - the $ operator

Before we delve into binary operators, we need to learn about a very useful operator specific to data frames and lists with named elements.

This operator is $. It is not a binary operator because it requires only one object.

Specifically, the $ operator allows us to extract a named element from a data frame or list.

To do so, we need two first write the name of the data frame (or list), immediately follow it with the $ operator, and then follow with the name of the element we want to obtain.

Here is what the code looks like, generally speaking:

data_frame$variable_name

Here is an example, in which we obtain a variable called age from a data frame called my_data_frame that, among others, contains this variable.

# create a data frame
my_data_frame = data.frame(
  ID = 1:5,
  gender = c('m', 'f', 'f', 'nb', 'm'),
  age = c(32, 66, 37, 41, 38)
)

# obtain age from the data frame
my_data_frame$age

If we run this code, R will display the information contained in the column labelled age in the console with the output looking as follows:

[1] 32 66 37 41 38

We could also define the extracted column as a separate object and save it in the Environment.

One extremely useful feature in R is that we can use the $ operator to create new elements in a data frame or list. For example, we can add a new variable to the data frame from the previous example.

# add a variable to the data frame coding whether 
# the participants is currently employed
my_data_frame$employed = c(T, T, F, T, T)

Once we run the code, R will update the data frame in the Environment. It will now be shown as “5 obs. of 4 variables” instead of the original “5 obs. of 3 variables”. Upon inspection, we can confirm that it now contains the new variable employed as its last column.

Here is what the data frame would look like in the console:

  ID gender age employed
1  1      m  32     TRUE
2  2      f  66     TRUE
3  3      f  37    FALSE
4  4     nb  41     TRUE
5  5      m  38     TRUE

Back to binary operators

Binary operators are symbols that R uses to represent a specific operation involving two objects. These operations can be arithmetic or logical. Before we jump into action, we need to have a look at how R’s binary operators look like.

Arithmetic binary operators

R has seven built-in arithmetic binary operators (you will probably not use the last two, but we will include them for the sake of completeness).

Operator Operation What R does
+ addition computes the sum of two numbers
- subtraction subtracts the second number from the first
* multiplication computes the product of two numbers
/ division divides first number by the second
^ power takes the first to the power of the second
%% modulo takes the remainder of division
%/% integer division division rounded down to whole numbers

Logical binary operators

Besides arithmetic operators, R has several built-in logical binary operators. Logical binary operators also require two objects as arguments. They compare the object to the left of the operator to the object on its right and check if the result of this comparison is TRUE or FALSE. In other words, they return a Boolean value. Here is the list of logical operators:

Operator What R tests
< the first value is less than the second
<= the first value is less than or equal to the second
> the first value is greater than the second
>= the first value is greater than or equal to the second
== the first value is exactly equal to the second
!= the first value is not equal to the second

Using binary operators on single values

The simplest way to use binary operators is to use them on single values. In the case of arithmetic operators, this comes down to adding, subtracting, multiplying etc. two numbers. As the two arguments for the operators, we can use values we defined as objects prior to the operation, values that we enter as is, or a combination of both. Here are a few examples.

a = "hello"   # a character value
b = FALSE     # a Boolean value
d = 13        # a numeric value
e = 2         # another numeric value

a != "hello"  # tests if a is unqeual to the string "hello" (this is FALSE)

b == FALSE    # tests if b is the Boolean value FALSE (which is TRUE)

d + 3         # adds 3 to the object d (for a total of 10)

d ^ e         # takes d to the power of e (the result is 169)

This is what appears in the console:

[1] FALSE
[1] TRUE
[1] 16
[1] 169

Note In the example above, we named the four objects a, b, d, and e. This was neither an oversight nor an expression of dislike toward the letter c. The simple reason is that there is a function called c(), and it is prudent to avoid giving objects the same name as existing functions.

Technically, it is possible to assign an object the name of a function, but it may lead to confusion or problems with the R code. Therefore, it is best avoided.

We can create more complex operations by combining multiple arithmetic and/or logical operators involving multiple values, and we can save the result by defining it as another object.

Note that R follows the basic rules of arithmetic operations. That is, power takes precedence over multiplication or division, which, in turn, take precedence over addition or subtraction. Just as in school maths, we need to use parentheses to organize our operations accordingly.

We can also use parentheses in the same fashion when combining multiple logical operations.

When combining arithmetic and logical operators, the arithmetic operations take precedence over the logical ones.

Example 1:

x = 3   # define a numeric value
y = 2   # define another value

z = x ^ 2 / (x * y - x)

In the example above, we defined a numeric value called z. Since power takes precedence over division, R first computes x to the power of 2 (for a total of 9). It then divides 9 by the expression in the parentheses. Within the parentheses, multiplication takes precedence over subtraction, which means that R first computes the product of x and y (which is 6) and then subtracts x for a total of 3. So our code boils down to dividing 9 by 3. We can easily verify that R did that by inspecting the new object z.

z
[1] 3

Example 2:

x = 3      # define a numeric value
y = 2      # define another value
z = 'red'  # define a character value

z != 'red' & (x > 2 | x + y == 7)

In this example, we test whether the two conditions combined by & (logical AND) are simultaneously TRUE. First, whether the value z equals the string ‘red’, and second, whether at least one of the following two statements combined with the | (logical OR) is TRUE: the value x is greater than 2 OR the sum of x and y equals 7.

The first statement is TRUE because z equals the character string ‘red’. The second statement is also true. While the sum of x and y clearly differs from 7 and is, thus, FALSE, x is greater than 2. Since the logical OR only requires one of the statements to be TRUE, the whole statement in parentheses is TRUE. Therefore, executing this code should return the Boolean TRUE, which we can check by inspecting the output in the console.

It looks as follows:

[1] TRUE

In theory, arithmetic operators should only work on numeric values (integer or double). Accordingly, R will complain if at lest one of the objects we use as arguments is a character string. Specifically, it will return an error message in the console stating that we assigned a non-numeric argument to the binary operator.

Something similar should happen if we assign at least one Boolean value, that is, a value stating either a logical TRUE or FALSE. Keep in mind, however, that R sometimes changes the type of an object so that it works with an operator or function (this is called coercion). If we use a Boolean value in an arithmetic operation, R will just treat it as a binary numeric variable (FALSE = 0, TRUE = 1).

Using binary operators on vectors and matrices

We can also use binary operators on vectors or matrices. The exact operation depends on the two objects involved. More specifically, if one of the two arguments of a binary operator is a vector or a matrix, then the operation differs depending on whether the second argument is a single vector or another vector or matrix.

Lets first look at the (simpler) case where one of the two arguments is a vector/matrix and the other is a single value. In this case, the exact same operation is performed on all elements of the vector or matrix. For example adding a single value and a vector/matrix means that the single value is added to each element of the vector/matrix, multiplying the vector/matrix by a number means that each element is multiplied by that number, testing whether a vector/matrix equals a certain character string tests for each element whether it equals that string, and so on. Let’s look at some examples.

v1 = c('red', 'green', 'blue')  # defines a character vector

m1 = matrix(1:9, nrow = 3)      # defines a numeric 3x3 matrix

a = 2         # defines a numeric value

v1 == 'red'   # tests for each element of v1 whether it equals the string 'red'

a ^ m1        # takes a (the value 2) to the power of each element of m1

m1 < 5        # tests for each element of m1 whether it is smaller than 5   

This is what appears in the console:

[1]  TRUE FALSE FALSE
     [,1] [,2] [,3]
[1,]    2   16  128
[2,]    4   32  256
[3,]    8   64  512
     [,1]  [,2]  [,3]
[1,] TRUE  TRUE FALSE
[2,] TRUE FALSE FALSE
[3,] TRUE FALSE FALSE

Irrespective of whether we enter the vector/matrix as the first or the second argument of the binary operator, the output has the same size and dimensions. That is, multiplying a vector of length 4 by a number yields a numeric vector of length 4. Multiplying a 3x3 matrix by a number yields another numeric 3x3 matrix. Testing whether a statement is true for a vector of length 7 returns a Boolean vector of length 7, and so on.

We can also use binary operators with both arguments being vectors or matrices. As a general rule, we must use objects of equal size in those operations, that is, if the object to the left of the operator is a vector of length 6, then the object to its right must also be a vector of length 6. Likewise, if we enter a 3x4 matrix to the left of the operator, our argument on its right will also have to be a 3x4 matrix.

Caveat: While R complains when we enter two matrices of unequal size as arguments of a binary operator (it will print an error message in the console), the same is not true for vectors. If we sue binary operators on vectors of unequal length, R will simply extend the shorter vector by starting over.

For example, when adding a vector containing the numbers from 1 to 3 to another vector containing the numbers from 1 to 5, R will turn the first vector into a vector of length 5 by repeating the first two elements (the resulting vector then has the elements 1, 2, 3, 1, 2).

This built-in feature of R can cause problems because situations, in which we actually want to perform binary operations on vectors of unequal length, are rare. Since R will run the code without any complaints, we run the risk of missing bugs in our code. Therefore, it is prudent to double-check the code whenever we use binary operators with two vectors.

When using binary operators with two vectors or matrices, R performs an element-wise operation. That means that R pairs the first element of the first vector or matrix with the corresponding element of the second vector or matrix when performing the operation. The second element of the first object is paired with second element of the second object, and so on. Let’s again look at a few examples.

Vectors first.

v1 = c(2, 4, 6, 8)    # defines a numeric vector

v2 = 1:4              # lazy way of creating a vector of length 4

v1 + v2               # performs element-wise addition of the vectors

v1 != v2              # takes each element of v1 and tests whether it is
                      # different from the corresponding element of v2

This is what appears in the console:

[1]  3  6  9 12
[1] TRUE TRUE TRUE TRUE

Now for some matrices.

m1 = matrix(c(2, 4, 6, 8), nrow = 2)    # defines a 2x2 matrix

m2 = matrix(1:4, ncol = 2)              # defines another 2x2 matrix

m1 - m2               # performs element-wise subtraction of the matrices

m1 < m2               # takes each element of m1 tests whether it is less than 
                      # the corresponding element of m2   

This is what appears in the console:

     [,1] [,2]
[1,]    1    3
[2,]    2    4
      [,1]  [,2]
[1,] FALSE FALSE
[2,] FALSE FALSE

As we can see, performing arithmetic operations on matrices using binary operators yields numeric matrices of the same size as the input matrices as result. When using logical binary operators instead, the result will be a Boolean matrix of equal size as the two input matrices.

Note that using the binary operator for multiplication (*) on matrices does not do what we would expect from regular matrix algebra. If we want to do classic matrix multiplication, we need to use a special operator, namely %*%.