cumprod()
cumprod(x) The cumprod() function computes the cumulative product of a vector, returning a vector of the same length where each element is the product of all preceding elements. This makes it handy for compound growth, geometric sequences, and other multiplicative calculations.
Syntax
cumprod(x)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
x | numeric | required | A numeric vector |
The input vector can contain any real numbers. Each output element at position i equals the product of all input elements from position 1 through i, so the first element of the result always equals the first element of the input. This makes cumprod() distinct from prod(), which returns only the single final product.
Examples
Basic usage
x <- c(1, 2, 3, 4, 5)
cumprod(x)
# [1] 1 2 6 24 120
With the input c(1, 2, 3, 4, 5), the cumulative product builds as 1, 1×2=2, 2×3=6, 6×4=24, and 24×5=120. Each intermediate value depends on every preceding element, so changing any number in the sequence alters all downstream results. This chaining behavior is what makes cumprod() useful for modeling processes where growth compounds over time.
Working with financial data
# Cumulative returns
prices <- c(100, 102, 98, 105, 110)
returns <- prices / 100
cumprod(returns)
# [1] 1.00 1.02 1.00 1.05 1.10
In financial contexts, dividing each price by the starting price converts raw values into growth multipliers relative to the initial point. Applying cumprod() to these multipliers then recovers the cumulative return at each step, which tells you how much a dollar invested at the start would be worth after each period. This is the standard method for computing cumulative performance from a series of periodic returns.
Geometric progression
# Geometric progression via cumprod
cumprod(rep(2, 5))
# [1] 2 4 8 16 32
# Powers of a number
cumprod(rep(3, 4))
# [1] 3 9 27 81
Using rep() with cumprod() generates powers of a number: cumprod(rep(k, n)) produces the sequence k¹, k², …, kⁿ. This works because each successive element is multiplied by the same base value k. For exponentiation you can also use the built-in ^ operator directly, but the cumprod(rep()) pattern is instructive for understanding how cumulative products relate to geometric growth and integer powers.
Common patterns
Compound growth
# Growth rates
growth <- c(1.1, 1.05, 1.02, 1.08)
cumprod(growth)
# [1] 1.1000 1.1550 1.1781 1.2724
Each element in the result represents the total growth factor up to that point, so the final value 1.2724 means the investment grew by about 27.2% over the four periods. This pattern is common in portfolio analysis where you have quarterly or monthly return series and need to compute the cumulative return from inception. For annualized returns, take the nth root of the final cumulative value.
Factorial calculation
# Factorial of 5 = 5!
cumprod(1:5)[5]
# [1] 120
cumprod() in practice
cumprod() computes the running product, each position holds the product of all elements up to that point. The most common use is computing compound growth: a series of growth multipliers (like c(1.05, 1.03, 1.08)) converts to cumulative returns when passed to cumprod().
The cumprod(1:n)[n] idiom computes n! (factorial), but factorial(n) is cleaner and more readable for this purpose. Use cumprod() when the products themselves at each intermediate step matter, not just the final product.
cumprod() can underflow or overflow quickly. A series of values greater than 1 grows exponentially, and a long series of small probabilities underflows to zero. When working with probabilities, working in log-space with cumsum(log(p)) and converting back with exp() at the end is more numerically stable than cumprod(p).
When the input to cumprod() is a logical vector, R coerces it to integer first (TRUE becomes 1, FALSE becomes 0). This makes cumprod() useful for finding the first FALSE in a condition sequence: sum(cumprod(condition)) counts how many leading TRUE values there are before the first FALSE, since the product becomes zero at the first FALSE and stays zero thereafter.
cumprod() propagates NA values: the first NA makes all subsequent values NA. This is the standard behavior for all cumulative functions in R.
Like cumsum(), cumprod() is vectorized and implemented in C, making it fast for large vectors. It does not have an na.rm argument, missing values must be handled before calling the function. Use na.omit() or replace NA with 1 (the multiplicative identity) if you want to skip missing values.
One edge case to be aware of: cumprod() on integer vectors can overflow quickly — cumprod(1:20) exceeds .Machine$integer.max. R silently promotes to double at that point, but very large values become Inf. If you expect large products, convert to double first with as.double() or use log() to sum log-transformed values instead, then exponentiate the final result with exp().
Survival probabilities in a Markov chain
cumprod() computes the probability of surviving through a sequence of independent steps:
# Probability of surviving each year, given alive at start
annual_survival <- c(0.95, 0.92, 0.90, 0.88, 0.85)
# Cumulative probability of being alive after each year
cumprod(annual_survival)
# [1] 0.9500 0.8740 0.7866 0.6922 0.5884
# Probability of dying in each year (conditional on being alive)
1 - annual_survival
# [1] 0.05 0.08 0.10 0.12 0.15
After five years the cumulative survival probability drops to about 59%, even though each individual year has a reasonably high survival rate. This multiplicative erosion is why actuarial models, reliability engineering, and clinical trial analyses all lean on cumprod() — a sequence of individually mild risks compounds into a substantial cumulative risk over time. For large chains, the log-space alternative exp(cumsum(log(annual_survival))) avoids underflow when probabilities are very small.
Finding the first FALSE with cumprod on logicals
checks <- c(TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, FALSE)
# How many leading TRUE values before the first FALSE?
sum(cumprod(checks))
# [1] 3
# Which position is the first failure?
which(!checks)[1]
# [1] 4
Once a FALSE appears, the running product hits zero and stays zero, so sum(cumprod(logical_vec)) counts the number of consecutive leading TRUE values. The alternative which(!checks)[1] directly gives the position of the first failure — a pattern used in data validation pipelines to report the row number where a constraint first fails in a column of condition results.