Reputation: 469
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
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
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
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
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
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