Reputation: 95
I have the following function to build a stock effect for a variable in one column. The variable creates a value in Column B that takes the value in ColumnA and adds a carry over (like e.g. 0.5) from the previous observation in Column B.
constructZ <- function(lag, N) {
r <- lag^(seq_len(N)-1)
m <- matrix(rep(r,N),nrow=N)
z <- matrix(0,nrow=N,ncol=N)
z[lower.tri(z,diag=TRUE)] <- m[row(m) <= (N+1-col(m))]
z
}
My problem is now that I have a panel data set that has in one column observations for many different cases. Each case has a specific indicator (numeric). Data looks like:
ColumnA Indicator Time
1 1 1
0 1 2
0 1 3
4 2 1
5 2 2
0 2 3
4 3 1
0 3 2
2 3 3
I now want the function to be applied to each case (Indicator) for all observations (Time).
Any idea how to achieve this? The Output should then look like:
ColumnA Indicator Time ColumnB
1 1 1 1
0 1 2 0.5
0 1 3 0.25
4 2 1 4
5 2 2 7
0 2 3 3.5
4 3 1 4
0 3 2 2
2 3 3 3
Any help or support is highly appreciated!
Many thanks in advance!
Upvotes: 0
Views: 1488
Reputation: 3954
Here is an alternative loop-free/functional programming solution. We are going to use the Reduce()
function which applies a binary function over every pair of items in a vector.
For example, Reduce(`+`, xs)
computes the sum of values in vector. If we set accumulate = TRUE
, we get a rolling/cumulative sum.
Reduce(`+`, 1:6)
#> [1] 21
# What Reduce is doing here, basically
((((((1) + 2) + 3) + 4) + 5) + 6)
#> [1] 21
# Keep each intermediate sum
Reduce(`+`, 1:6, accumulate = TRUE)
#> [1] 1 3 6 10 15 21
(The purrr package separates these two behaviors into different functions: reduce()
and accumulate()
.)
We can use Reduce()
to implement the carry-over/scaling function. First, define a function that works on a pair of values, then use Reduce()
to perform a rolling version of it.
rolling_scale <- function(xs, scale_factor) {
scale_pair <- function(x1, x2) x2 + scale_factor * x1
Reduce(scale_pair, xs, accumulate = TRUE)
}
rolling_scale(c(4, 5, 0), .5)
#> [1] 4.0 7.0 3.5
Now, we can use dplyr and apply this rolling function to each indicator group.
library(dplyr)
raw <- data.frame(
ColumnA = c(1, 0, 0, 4, 5, 0, 4, 0, 2),
Indicator = rep(x = 1:3, each = 3),
Time = 1:3)
raw %>%
group_by(Indicator) %>%
mutate(ColumnB = rolling_scale(ColumnA, .5)) %>%
ungroup()
#> # A tibble: 9 × 4
#> ColumnA Indicator Time ColumnB
#> <dbl> <int> <int> <dbl>
#> 1 1 1 1 1.00
#> 2 0 1 2 0.50
#> 3 0 1 3 0.25
#> 4 4 2 1 4.00
#> 5 5 2 2 7.00
#> 6 0 2 3 3.50
#> 7 4 3 1 4.00
#> 8 0 3 2 2.00
#> 9 2 3 3 3.00
Upvotes: 1
Reputation: 728
Here is my code.
library(dplyr) # Optional, but makes code cleaner
raw = data.frame(ColumnA =
c(1, 0, 0, 4, 5, 0, 4, 0, 2),
Indicator =
rep(x = 1:3, each = 3),
Time = 1:3)
factor = 0.5
loop = function(vec) {
length = length(x = vec)
if (length == 1) {
return(vec)
}
if (length == 2) {
return(vec + c(0, vec[2] * factor))
}
for (idx in 2:length) {
vec[idx] = vec[idx] + vec[idx - 1] * factor
}
return(vec)
}
output = raw %>%
mutate(ColumnB =
tapply(X = ColumnA,
INDEX = Indicator,
FUN = loop) %>%
unlist())
output
Upvotes: 0