1

I would like to define different parts of a formula in R and then concatenate these parts - not necessarily only by adding the terms up. I could imagine a syntax like this:

# define one-handed formulas
part_1 <- ~ x:w
part_2 <- ~ y + z

# concatenate, e.g. with glue syntax
f <- y ~ a + {part_1}:{part_2}

To me, this appears to be a natural application, so I am wondering if there is this sort of functionality in base R. Some packages do offer solutions but come with other strings attached.

My efforts to do this in base R did not succeed:

# does not work: update inserts "part_2" literally
update(part_1, ~ . + part_2)
#> ~part_2 + x:w

# reformulate uses + to concatenate
reformulate(c(attr(terms(part_1), "term.labels"),
              attr(terms(part_2), "term.labels")))
#> ~x:w + y + z

Merge methods for formulas have been written (e.g., here and here) but do not allow concatenating with anything other than addition.

The only solution that I could find so far involves the macro notation from fixest:

library(fixest)

setFixest_fml(..part_1 = ~ x:w)
setFixest_fml(..part_2 = ~ y + z)

f <- y ~ a + (..part_1):(..part_2)
xpd(f)
#> y ~ a + (x:w):(y + z)
attr(terms(xpd(f)), "term.labels")
#> [1] "a"     "y:x:w" "x:w:z"

Created on 2021-11-18 by the reprex package (v2.0.1)

dufei
  • 2,166
  • 1
  • 7
  • 18

2 Answers2

1

Here's an option using the rlang package:

library(rlang)

part_1 <- ~ x:w
part_2 <- ~ y + z
f <- eval(expr(y ~ a + (!!(f_rhs(part_1))):(!!(f_rhs(part_2)))))
f
#> y ~ a + x:w:(y + z)
attr(terms(f), "term.labels")
#> [1] "a"     "y:x:w" "x:w:z"
jblood94
  • 10,340
  • 1
  • 10
  • 15
  • Thanks! I wasn't aware that rlang has a special way to deal with formulas. In fact, if we turn `part_1` and `part_2` into expressions (e.g. `part_1 <- expr(x:w)`), it seems we can even do without `f_rhs` (which might improve legibility): `eval(expr(y ~ a + (!!part_1):(!!part_2)))` – dufei Nov 19 '21 at 14:24
  • If `part_1` and `part_2` aren't needed as formulas, then that's probably the way to go. – jblood94 Nov 19 '21 at 19:54
1

Alternatively, yet still using fixest (version 0.10.1), you can use the dot square bracket operator:

library(fixest)
part1 = "x:w"
part2 = "y + z"

# .[x] inserts x in the formula
xpd(y ~ a + (.[part1]):(.[part2]))
#> y ~ a + (x:w):(y + z)

# note that in case of vectors, concatenation is sum based:
part2_bis = c("y", "z")
xpd(y ~ a + (.[part1]):(.[part2_bis]))
#> y ~ a + (x:w):(y + z)
Laurent Bergé
  • 1,292
  • 6
  • 8