How to add columns to a dataframe based on indexes in R? (See example)

Viewed 68

I'm working with a self made infix function which simply calculates the percentage growth between observations in columns.

options(digits=3)

`%grow%` <- function(x,y) {
    (y-x) / x * 100
}

test <- data.frame(a=c(101,202,301), b=c(123,214,199), h=c(134, 217, 205))

Then I use lapply to my toy database in order to add two new columns.

test[,4:5] <- lapply(1:(ncol(test)-1), function(i) test[,i] %grow% test[,(i+1)])
test

#Output
    a   b   h     V4   V5
1 101 123 134  21.78 8.94
2 202 214 217   5.94 1.40
3 301 199 205 -33.89 3.02

This is easy considering I just have three columns and I just can write test[,4:5]. Now talking in general terms: How to do this if we have n columns using column indexes? What I mean is I want to create n-1 columns to a given database starting from the last one. Something like:

test[,(last_current_column+1):(last_column_created_using_function)]

Considering what I've read in some other posts, using my example, test[,(last_current_column+1): could be written as:

test[,(ncol(test)+1):]

but second part is still missing and I have no idea how to write it.

I hope I made myself clear. I fully appreciate any comment or advise.

Happy 2019 :)

2 Answers

Another way would be:

#options(digits=3)

`%grow%` <- function(x,y) {
  (y-x) / x * 100
}

test <- data.frame(a=c(101,202,301), 
                   b=c(123,214,199),
                   h=c(134, 217, 205),
                   d=c(156,234,235))
#     a   b   h   d
# 1 101 123 134 156
# 2 202 214 217 234
# 3 301 199 205 235


seqcols <- seq_along(test) # saved just to improve readability
test[,seqcols[-length(seqcols)] + max(seqcols)] <- lapply(seqcols[-length(seqcols)], 
                     function(i) test[,i] %grow% test[,(i+1)])
test
#     a   b   h   d     V5   V6    V7
# 1 101 123 134 156  21.78 8.94 16.42
# 2 202 214 217 234   5.94 1.40  7.83
# 3 301 199 205 235 -33.89 3.02 14.63

Similar to the second solution from @Ronak Shah, just with the use of map2_df from purrr:

cbind(test,
      new=purrr::map2_df(test[seqcols[-length(seqcols)]], test[seqcols[-1]], `%grow%`),
      deparse.level=1)
#     a   b   h   d  new.a new.b new.h
# 1 101 123 134 156  21.78  8.94 16.42
# 2 202 214 217 234   5.94  1.40  7.83
# 3 301 199 205 235 -33.89  3.02 14.63

You would always ncol(test) - 1 new columns. Now using this logic there are multiple ways to do this.

One way would be to construct a character vector with some prefix value.

test[paste0("new_col", seq_len(ncol(test) - 1))] <- lapply(1:(ncol(test)-1),
                    function(i) test[,i] %grow% test[,(i+1)])


test
#    a   b   h   new_col1 new_col2
#1 101 123 134  21.782178 8.943089
#2 202 214 217   5.940594 1.401869
#3 301 199 205 -33.887043 3.015075

Another option using mapply and transform by creating subsets of dataframe

transform(test,
   new_col = mapply(`%grow%`, test[1:(ncol(test)- 1)], test[2:ncol(test)]))


#    a   b   h  new_col.a new_col.b
#1 101 123 134  21.782178  8.943089
#2 202 214 217   5.940594  1.401869
#3 301 199 205 -33.887043  3.015075
Related