Is there a way to automate the multiplication of columns in R in relation to their names?

Viewed 94

I have a dataset like this.

> dataset

id a b c d
1 10 1 30 50
2 5 0 5 60
3 20 1 18 90
4 103 0 20 80
5 16 1 56 100

And so on up to 'n' number of columns in relation to the requirement.

My colleagues in the research area carry out certain analyzes where what they give me as an input is a data frame. In which the row names correspond to the variables of interest for a new dataset.

Something like this.

> rownames(Features)

a
b
d
a:d
b:d
b:c
a:c

Where the colon (:) represents the "product of". So in order to continue working I need to include those products in the original dataset.

I have manually created a function that reproduces the multiplications in the following way (where x represents my original dataset):

Products<- function(x){x<- x %>% mutate(Product1=x$a*x$d)
                       x<- x %>% mutate(Product2=x$b*x$d)
                       x<- x %>% mutate(Product3=x$b*x$c)
                       x<- x %>% mutate(Product4=x$a*x$c)
                       return(x)}

However, given that the number of products to create is variable every time, I want to find a way to automate the creation of these column multiplications with the names that they give me as input. I'm sure my approach isn't the best, so any help is very welcome.

4 Answers

This seems to be some modelling being done and the choice of : to stand for product is quite intriguing as that is often the case in the modeling world. In base R, you could do:

 model.matrix(reformulate(c(-1,rownames(Features))), dataset)

    a b   d  a:d b:d b:c  a:c
1  10 1  50  500  50  30  300
2   5 0  60  300   0   0   25
3  20 1  90 1800  90  18  360
4 103 0  80 8240   0   0 2060
5  16 1 100 1600 100  56  896

I added -1 in order to remove the intercept. Otherwise you could maintain it. Also note that this is a matrix, you can then change it to a dataframe.

I am not sure if the following code works for your, where eval + gsub are used to produce the product columns

with(
  dataset,
  list2DF(
    setNames(
      lapply(
        gsub(":", "*", rownames(Features)),
        function(x) eval(str2lang(x))
      ),
      rownames(Features)
    )
  )
)

which gives

    a b   d  a:d b:d b:c  a:c
1  10 1  50  500  50  30  300
2   5 0  60  300   0   0   25
3  20 1  90 1800  90  18  360
4 103 0  80 8240   0   0 2060
5  16 1 100 1600 100  56  896

Data

> dput(dataset)
structure(list(id = 1:5, a = c(10, 5, 20, 103, 16), b = c(1, 
0, 1, 0, 1), c = c(30, 5, 18, 20, 56), d = c(50, 60, 90, 80,
100)), class = "data.frame", row.names = c(NA, -5L))

> dput(Features)
structure(list(Features = 1:7), class = "data.frame", row.names = c("a",
"b", "d", "a:d", "b:d", "b:c", "a:c"))

We could use strsplit to split the names that have :, select the column in the 'dataset' based on that splitted named, Reduce with * to do elementwise multiplication, and assign those 'Product' columns to the original 'dataset'

nm1 <- grep(':', rownames(Features), value = TRUE)
lst1 <- lapply(strsplit(nm1, ":", fixed = TRUE), 
           function(x) Reduce(`*`, dataset[x]))
dataset[paste0("Product", seq_along(lst1))] <- lst1

-output

dataset
#  id   a b  c   d Product1 Product2 Product3 Product4
#1  1  10 1 30  50      500       50       30      300
#2  2   5 0  5  60      300        0        0       25
#3  3  20 1 18  90     1800       90       18      360
#4  4 103 0 20  80     8240        0        0     2060
#5  5  16 1 56 100     1600      100       56      896

data

dataset <- structure(list(id = 1:5, a = c(10, 5, 20, 103, 16), b = c(1, 
0, 1, 0, 1), c = c(30, 5, 18, 20, 56), d = c(50, 60, 90, 80, 
100)), class = "data.frame", row.names = c(NA, -5L))

Features <- structure(1:7, .Dim = c(7L, 1L), .Dimnames = list(c("a", "b", 
"d", "a:d", "b:d", "b:c", "a:c"), NULL))

This is similar to many solutions above. One thing about R is you can achieve the same results with so many different ways though the underline principle is still the same.

library(dplyr)

dataset <- tibble(id = c(1, 2, 3, 4, 5),
                  a = c(10, 5, 20, 103, 16),
                  b = c(1, 0, 1, 0, 1),
                  c = c(30, 5, 18, 20, 56),
                  d = c(50, 60, 90, 80, 100))

features = c("a", "b", "d", "a:d", "b:d", "b:c", "a:c")
final <- bind_cols(
  map(features, 
        function(x) {
          dataset %>%
            mutate(!!x := eval(parse(text=gsub(":", "*", x)))) %>%
            select(!!x)
          }
      ))

Final dataset.

# A tibble: 5 x 9
     id     a     b     c     d `a:d` `b:d` `b:c` `a:c`
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1     1    10     1    30    50   500    50    30   300
2     2     5     0     5    60   300     0     0    25
3     3    20     1    18    90  1800    90    18   360
4     4   103     0    20    80  8240     0     0  2060
5     5    16     1    56   100  1600   100    56   896
Related