Beginner

Operators in R

Learn arithmetic, comparison, logical, and assignment operators in R, including vectorized operations and the pipe operator, with Docker-ready examples

Operators are the symbols R uses to combine and transform values: adding numbers, comparing quantities, testing logical conditions, and binding results to names. Understanding them is the foundation for writing any expression in R.

What sets R apart from most languages is that nearly every operator is vectorized. When you add two vectors, R adds them element by element and returns a new vector—no explicit loop required. This reflects R’s roots as an array-oriented language for statistics, where you routinely work with whole columns of data at once rather than individual scalars. A “single number” in R is really a numeric vector of length one, so the same operators that work on one value work on thousands.

This tutorial covers arithmetic, comparison, logical, and assignment operators, plus R-specific operators like the modulo (%%), integer division (%/%), sequence (:), membership (%in%), and the native pipe (|>). Each example is a complete script you can run with Docker.

Arithmetic Operators

R provides the standard arithmetic operators, plus a few that are especially handy for statistical work. Note that R uses %% for modulo and %/% for integer (floor) division, and ^ for exponentiation.

Create a file named operators_arithmetic.R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Arithmetic operators in R
a <- 17
b <- 5

cat("a + b   =", a + b, "\n")    # addition
cat("a - b   =", a - b, "\n")    # subtraction
cat("a * b   =", a * b, "\n")    # multiplication
cat("a / b   =", a / b, "\n")    # division (always returns a double)
cat("a %% b  =", a %% b, "\n")   # modulo (remainder)
cat("a %/% b =", a %/% b, "\n")  # integer division
cat("a ^ b   =", a ^ b, "\n")    # exponentiation

Unlike many languages, dividing two integers with / in R always produces a decimal result (3.4), never a truncated integer. When you specifically want the whole-number quotient, use %/%.

Vectorized Operators

This is where R shines. Operators apply to entire vectors element by element. When operands have different lengths, R recycles the shorter one—this is why multiplying a vector by a single number scales every element.

Create a file named operators_vectorized.R:

1
2
3
4
5
6
7
8
# Operators work element-wise on vectors
v1 <- c(1, 2, 3, 4)
v2 <- c(10, 20, 30, 40)

print(v1 + v2)   # paired element-wise addition
print(v1 * 2)    # 2 is recycled across all elements
print(v2 / v1)   # element-wise division
print(v1 ^ 2)    # square each element

There is no loop here. R handles the iteration internally, which is both more concise and far faster than writing an explicit loop in interpreted R code. This vectorized style is the idiomatic way to compute in R—reach for it before reaching for a for loop.

Comparison and Logical Operators

Comparison operators return logical values (TRUE/FALSE), and they are vectorized too. R distinguishes between the vectorized logical operators & and | (which compare element by element) and the scalar operators && and || (which expect a single value and short-circuit). In R 4.3 and later, using && or || on a vector of length greater than one is an error, so use &/| for vectors.

Create a file named operators_comparison.R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Comparison operators return logical values
x <- 8
y <- 3

print(x > y)    # greater than
print(x == y)   # equality
print(x != y)   # inequality

# Scalar logical operators (single TRUE/FALSE values)
print(TRUE & FALSE)   # logical AND
print(TRUE | FALSE)   # logical OR
print(!TRUE)          # logical NOT

# Vectorized comparisons produce logical vectors
vec <- c(2, 5, 8, 11)
print(vec > 5)            # which elements exceed 5?
print(vec > 2 & vec < 11) # combine two conditions element-wise

Logical vectors like these are the engine behind R’s filtering: you can use a result such as vec > 5 to subset data, count matches with sum(), or drive conditional logic across an entire dataset at once.

Assignment and the Pipe Operator

R is unusual in offering several assignment operators. The leftward arrow <- is the idiomatic choice and the one you’ll see in most R code. The equals sign = also assigns, and R even supports a rightward arrow ->. R 4.1 introduced the native pipe |>, which passes the result of the left expression as the first argument to the function on the right—making data transformations read top to bottom.

Create a file named operators_assignment.R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# R supports multiple assignment styles
z <- 10        # leftward assignment (idiomatic)
20 -> w        # rightward assignment
result = 5     # equals also assigns

cat("z =", z, "\n")
cat("w =", w, "\n")
cat("result =", result, "\n")

# The native pipe operator chains operations left to right
nums <- c(4, 9, 16, 25)
nums |> sqrt() |> sum() |> print()  # sqrt, then sum, then print

The pipe reads naturally: take nums, take the square root of each element (2, 3, 4, 5), sum them (14), then print. Without the pipe you would write the nested form print(sum(sqrt(nums))), which has to be read inside-out.

Operator Precedence and Special Operators

Operators follow a defined precedence, and R has a few special operators worth knowing. Exponentiation (^) is right-associative, the colon (:) builds integer sequences, and %in% tests membership.

Create a file named operators_precedence.R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Precedence: multiplication before addition
print(2 + 3 * 4)     # 3*4 evaluated first
print((2 + 3) * 4)   # parentheses force addition first

# Exponentiation is right-associative
print(2 ^ 3 ^ 2)     # evaluated as 2 ^ (3 ^ 2) = 2 ^ 9

# The colon operator creates integer sequences
print(1:5)

# The %in% operator tests membership
print(3 %in% c(1, 2, 3))
print(7 %in% 1:5)

When in doubt about precedence, add parentheses—they make intent explicit and cost nothing. The : and %in% operators are everywhere in real R code: : for generating index ranges and %in% for checking whether values belong to a set.

Running with Docker

Docker gives you a consistent R 4.4.2 environment without a local install. Pull the official image once, then run each script with Rscript.

1
2
3
4
5
6
7
8
9
# Pull the official image
docker pull r-base:4.4.2

# Run each operators example
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript operators_arithmetic.R
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript operators_vectorized.R
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript operators_comparison.R
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript operators_assignment.R
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript operators_precedence.R

Expected Output

Running operators_arithmetic.R:

a + b   = 22 
a - b   = 12 
a * b   = 85 
a / b   = 3.4 
a %% b  = 2 
a %/% b = 3 
a ^ b   = 1419857 

Running operators_vectorized.R:

[1] 11 22 33 44
[1] 2 4 6 8
[1] 10 10 10 10
[1]  1  4  9 16

Running operators_comparison.R:

[1] TRUE
[1] FALSE
[1] TRUE
[1] FALSE
[1] TRUE
[1] FALSE
[1] FALSE FALSE  TRUE  TRUE
[1] FALSE  TRUE  TRUE FALSE

Running operators_assignment.R:

z = 10 
w = 20 
result = 5 
[1] 14

Running operators_precedence.R:

[1] 14
[1] 20
[1] 512
[1] 1 2 3 4 5
[1] TRUE
[1] FALSE

Key Concepts

  • Operators are vectorized: arithmetic, comparison, and logical operators apply element by element across entire vectors—the idiomatic alternative to explicit loops in R.
  • Recycling: when operands differ in length, R repeats the shorter one to match the longer, which is why vec * 2 scales every element.
  • Division vs. integer division: / always returns a double, while %/% gives the floor quotient and %% gives the remainder.
  • <- is the idiomatic assignment: though = and -> also work, the leftward arrow is the R community standard.
  • &/| vs. &&/||: the single-character forms are vectorized; the double forms expect a single value and short-circuit, and error on long vectors in modern R.
  • ^ is right-associative: 2 ^ 3 ^ 2 evaluates as 2 ^ (3 ^ 2), not (2 ^ 3) ^ 2.
  • Special operators: : builds integer sequences and %in% tests set membership—both appear constantly in real R code.
  • The native pipe |> (R 4.1+) chains operations left to right, replacing deeply nested function calls with a readable top-to-bottom flow.

Running Today

All examples can be run using Docker:

docker pull r-base:4.4.2
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining