MyQ
MyQ

Reputation: 469

Apply a formula to the data in R

Given a formula like log(y)~x^2+x*z +z how can I apply the formula to make a new data.frame from an existing data?

For example, let the raw data be (set.seed is really not necessary here):

     df = abs(data.frame(y = rnorm(10),x=rnorm(10),z=rnorm(10)))
> df
            y         x            z
1  0.80805320 2.0371328 7.645385e-01
2  0.45052293 0.9700742 5.456879e-05
3  1.36125062 0.5002969 5.911681e-03
4  1.43985610 0.7149159 2.062861e+00
5  0.31509256 0.4588976 9.477389e-01
6  0.27022582 1.1975979 4.723059e-01
7  0.62058948 0.9114911 2.877264e-01
8  0.07162775 0.4323277 3.725793e-01
9  0.06317060 0.4251968 3.981087e-01
10 1.32624421 0.7272188 1.143495e+00

I am looking for a magic function that applies log(y)~x^2+x*z +z to the data and produces:

   df2 = data.frame(df,'log(y)'=log(df$y),'x^2'=df$x^2,'x*z'=df$x*df$z,z=df$z,check.names = FALSE)
> df2
            y         x            z     log(y)       x^2          x*z            z
1  0.80805320 2.0371328 7.645385e-01 -0.2131274 4.1499100 1.557466e+00 7.645385e-01
2  0.45052293 0.9700742 5.456879e-05 -0.7973463 0.9410439 5.293578e-05 5.456879e-05
3  1.36125062 0.5002969 5.911681e-03  0.3084039 0.2502970 2.957596e-03 5.911681e-03
4  1.43985610 0.7149159 2.062861e+00  0.3645432 0.5111048 1.474772e+00 2.062861e+00
5  0.31509256 0.4588976 9.477389e-01 -1.1548888 0.2105870 4.349151e-01 9.477389e-01
6  0.27022582 1.1975979 4.723059e-01 -1.3084973 1.4342407 5.656326e-01 4.723059e-01
7  0.62058948 0.9114911 2.877264e-01 -0.4770855 0.8308160 2.622601e-01 2.877264e-01
8  0.07162775 0.4323277 3.725793e-01 -2.6362728 0.1869073 1.610764e-01 3.725793e-01
9  0.06317060 0.4251968 3.981087e-01 -2.7619163 0.1807923 1.692745e-01 3.981087e-01
10 1.32624421 0.7272188 1.143495e+00  0.2823510 0.5288472 8.315712e-01 1.143495e+00

Upvotes: 1

Views: 145

Answers (4)

jay.sf
jay.sf

Reputation: 72623

Similar to @g-grothendieck's solution but with strsplit.

s <- strsplit(paste(as.character(fml)[-1], collapse="+"), "\\+")[[1]]
sapply(s, function(s) with(dat, eval(parse(text=s))))
#           log(y)       x^2      x * z          z
#  [1,]  0.3155101 1.70268481 0.40012340 0.3066386
#  [2,] -0.5714639 5.22874715 4.07322072 1.7813084
#  [3,] -1.0129988 1.92893405 0.23876926 0.1719174
#  [4,] -0.4575019 0.07772318 0.33863766 1.2146747
#  [5,] -0.9056765 0.01777458 0.25266972 1.8951935
#  [6,] -2.2431422 0.40443291 0.27375702 0.4304691
#  [7,]  0.4131171 0.08079972 0.07312957 0.2572694
#  [8,] -2.3574739 7.05675540 4.68376414 1.7631631
#  [9,]  0.7023169 5.95587883 1.12285238 0.4600974
# [10,] -2.7691690 1.74269925 0.84486578 0.6399949

Data

dat <- structure(list(y = c(1.37095844714667, 0.564698171396089, 0.363128411337339, 
0.63286260496104, 0.404268323140999, 0.106124516091484, 1.51152199743894, 
0.0946590384130976, 2.01842371387704, 0.062714099052421), x = c(1.30486965422349, 
2.28664539270111, 1.38886070111234, 0.278788766817371, 0.133321336393658, 
0.635950398070074, 0.284252921416072, 2.65645542090478, 2.44046692857552, 
1.32011334573019), z = c(0.306638594078475, 1.78130843398, 0.171917355759621, 
1.2146746991726, 1.89519346126497, 0.4304691316062, 0.25726938276893, 
1.76316308519478, 0.460097354831271, 0.639994875960119)), row.names = c(NA, 
-10L), class = "data.frame")

fml <- log(y) ~ x^2 + x*z + z

Upvotes: 1

Rafael Toledo
Rafael Toledo

Reputation: 1054

A functional paradigm solution:

library(tidyverse)

df <- abs(data.frame(y = rnorm(10),x = rnorm(10), z = rnorm(10)))
formula <- "log(y) ~ x ^ 2 + x * z + z"

add_expr_column <- function(df) {
    function(e) {
        df %>% 
            mutate(!!e := eval(parse(text = e))) %>% 
            select(e)
    }
}

formula %>% 
    str_split("~|\\+") %>%
    unlist() %>% 
    map(add_expr_column(df)) %>% 
    bind_cols(df, .)

#            y          x           z      log(y)          x^2        x*z           z1
#1  2.23099710 0.74623401 1.353305848  0.80244861 0.5568652025 1.009882854 1.353305848
#2  1.71320154 0.47955684 1.612452480  0.53836387 0.2299747664 0.773262622 1.612452480
#3  1.15136870 1.24478445 0.988070302  0.14095141 1.5494883309 1.229934549 0.988070302
#4  2.63530469 1.85889324 1.180528845  0.96899881 3.4554840915 2.194477094 1.180528845

Upvotes: 1

IceCreamToucan
IceCreamToucan

Reputation: 28675

I'm not convinced this is a good idea. It seems easy enough to type out x^2, x*z, ..., and any attempt at evaluating these formulas seems likely to leave some edge cases un-accounted-for. But, here's one way you might do it.

library(tidyverse)

my_form <- log(y)~x^2+x*z +z

my_form %>% 
  as.character %>% 
  keep(~ . != '~') %>% 
  str_split('\\+') %>% 
  unlist %>% 
  setNames(., .) %>% 
  map_dfc(~ eval(parse(text = .x), df))

# # A tibble: 10 x 4
#    `log(y)`   `x^2 ` ` x * z `   ` z`
#       <dbl>    <dbl>     <dbl>  <dbl>
#  1  0.670   0.109       0.211  0.640 
#  2 -2.32    3.48        1.05   0.561 
#  3 -1.90    1.66        0.0778 0.0604
#  4  0.341   2.46        2.26   1.44  
#  5 -2.09    0.536       0.254  0.347 
#  6  0.133   4.39        1.24   0.590 
#  7  0.291   0.00591     0.0501 0.652 
#  8 -0.00351 0.000674    0.0199 0.766 
#  9 -0.485   0.109       0.0103 0.0314
# 10 -0.495   1.36        0.893  0.765 

Upvotes: 2

G. Grothendieck
G. Grothendieck

Reputation: 269461

Using the inputs in the Note at the end we split out the terms giving nms in the first line and then evaluate each one giving matrix m which we convert to data.frame.

nms <- trimws(scan(text = sub("~", "+", format(fo)), what = "", sep = "+", quiet = TRUE))
m <- sapply(nms, function(x) eval(parse(text = x), df))
cbind(df, m)

giving this data frame:

            y         x         z     log(y)        x^2      x * z         z
1  0.56047565 1.2240818 1.0678237 -0.5789695 1.49837625 1.30710356 1.0678237
2  0.23017749 0.3598138 0.2179749 -1.4689046 0.12946599 0.07843039 0.2179749
3  1.55870831 0.4007715 1.0260044  0.4438575 0.16061776 0.41119329 1.0260044
4  0.07050839 0.1106827 0.7288912 -2.6520235 0.01225066 0.08067566 0.7288912
5  0.12928774 0.5558411 0.6250393 -2.0457149 0.30895937 0.34742254 0.6250393
6  1.71506499 1.7869131 1.6866933  0.5394510 3.19305856 3.01397443 1.6866933
7  0.46091621 0.4978505 0.8377870 -0.7745390 0.24785510 0.41709268 0.8377870
8  1.26506123 1.9666172 0.1533731  0.2351205 3.86758304 0.30162620 0.1533731
9  0.68685285 0.7013559 1.1381369 -0.3756352 0.49190010 0.79823906 1.1381369
10 0.44566197 0.4727914 1.2538149 -0.8081945 0.22353172 0.59279292 1.2538149

Note

The inputs in reproducible form are:

fo <- log(y) ~ x^2 + x * z + z  # input formula

set.seed(123)
df <- abs(data.frame(y = rnorm(10), x = rnorm(10), z = rnorm(10)))

Upvotes: 3

Related Questions