Quick summary
Summarize this blog with AI
R modulo uses floor division
x %% y returns the remainder associated with R's integer division operator, x %/% y. For ordinary integer inputs with a nonzero divisor, the relationship is x = y * (x %/% y) + (x %% y). A nonzero remainder follows the divisor's sign. Run the following blocks sequentially in base R 4.5.1.
Check positive and negative operands
cases <- data.frame(x = c(5, -5, 5, -5), y = c(3, 3, -3, -3))
cases$quotient <- cases$x %/% cases$y
cases$remainder <- cases$x %% cases$y
print(cases)
# x y quotient remainder
# 5 3 1 2
#-5 3 -2 1
# 5 -3 -2 -1
#-5 -3 1 -2
stopifnot(identical(cases$remainder, c(2, 1, -1, -2)))
stopifnot(all(cases$x == cases$y * cases$quotient + cases$remainder))
In particular, 5 %% -3 is -1 and -5 %% 3 is 1. Reason from the quotient rounded downward rather than importing a remainder rule from another language.
Filter even values without losing missingness
values <- c(-3, -2, -1, 0, 1, 2, NA)
is_even <- values %% 2 == 0
print(is_even) # FALSE TRUE FALSE TRUE FALSE TRUE NA
even_values <- values[!is.na(values) & is_even]
print(even_values) # -2 0 2
stopifnot(identical(even_values, c(-2, 0, 2)))
This definition is for integer-valued inputs. The remainder is vectorized, so no per-value loop is needed. A missing input remains unknown; the explicit filter excludes it here. If dropping unknown values would affect your analysis, count and report them instead.
Wrap a position using one-based indexing
labels <- c("A", "B", "C")
positions <- 1:8
index <- ((positions - 1L) %% length(labels)) + 1L
print(labels[index]) # A B C A B C A B
stopifnot(identical(index, c(1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L)))
current <- 1L
previous <- ((current - 2L) %% length(labels)) + 1L
stopifnot(previous == 3L)
Subtract one before modulo and add one afterward because R indices start at one. The divisor must be positive here; an empty label vector has no valid position to wrap to. Cycling labels produces a repeating allocation, not a random split. For a train/test split, row order may contain trends that make such an allocation inappropriate.
Keep a circular buffer state explicit
buffer <- rep(NA_real_, 3)
next_index <- 1L
for (value in c(10, 20, 30, 40, 50)) {
buffer[next_index] <- value
next_index <- (next_index %% length(buffer)) + 1L
}
print(buffer) # 40 50 30: storage order, not time order
time_order <- ((next_index - 1L + 0:2) %% length(buffer)) + 1L
print(buffer[time_order]) # 30 40 50
stopifnot(identical(buffer[time_order], c(30, 40, 50)))
After five insertions, the buffer is full and next_index points to the oldest stored value. The chronological reconstruction above assumes the buffer has filled; before that, track the count of inserted values and exclude unused slots. There is no Python-style global statement in R.
Treat zero divisors and decimals as separate cases
stopifnot(is.nan(5 %% 0), is.na(NA_real_ %% 3))
divisor <- 3
stopifnot(length(divisor) == 1, is.finite(divisor), divisor != 0)
decimal_remainder <- 1 %% 0.2
print(decimal_remainder)
# Binary floating-point need not represent decimal fractions exactly.
Do not use exact decimal remainder equality as a money or time validation rule. Use appropriate integer units when your application defines them, and validate their range. Floating-point precision and large quotients can affect remainders; the integer examples above deliberately avoid that ambiguity.