58

I have trouble working with formula as with text. What I'm trying to do is to concatenate the formula to the title of the graph. However, when I try to work with the formula as with text, I fail:

model <- lm(celkem ~ rok + mesic)
formula(model)
# celkem ~ rok + mesic

This is fine. Now I want to build string like "my text celkem ~ rok + mesic" - this is where the problem comes:

paste("my text", formula(model))
# [1] "my text ~"           "my text celkem"      "my text rok + mesic"

paste("my text", as.character(formula(model)))
# [1] "my text ~"           "my text celkem"      "my text rok + mesic"

paste("my text", toString(formula(model)))
# [1] "my text ~, celkem, rok + mesic"

Now I see there is a sprint function in package gtools, but I think this is such a basic thing that it deserves a solution within the default environment!!

Karolis Koncevičius
  • 7,687
  • 9
  • 48
  • 71
Tomas
  • 52,167
  • 46
  • 207
  • 345

9 Answers9

45

A short solution from the package formula.tools, as a function as.character.formula:

frm <- celkem ~ rok + mesic
Reduce(paste, deparse(frm))
# [1] "celkem ~ rok + mesic"

library(formula.tools)
as.character(frm)
# [1] "celkem ~ rok + mesic"

Reduce might be useful in case of long formulas:

frm <- formula(paste("y ~ ", paste0("x", 1:12, collapse = " + ")))

deparse(frm)
# [1] "y ~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + "
# [2] "    x12"                                                      
Reduce(paste, deparse(frm))
# [1] "y ~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 +      x12"

Which is because of width.cutoff = 60L in ?deparse.

Julius Vainora
  • 44,018
  • 9
  • 79
  • 96
  • Aaah, `deparse`! Thanks a million! However, why you use `Reduce`? It is enough to do just `deparse(frm)`. – Tomas Feb 03 '13 at 10:28
  • @Tomas, just added an explanation about `Reduce`. – Julius Vainora Feb 03 '13 at 10:40
  • 2
    Erghh, I was so enthusiastic about deparse, but now it seems it is quite deceitful! There must be some function that converts formula to string *without wrappings, cutoffs* etc!! – Tomas Feb 03 '13 at 10:48
  • @Tomas, I don't think there is one which would be significantly shorter than your solution. I think `deparse` alone is sufficient for visual usage and `Reduce` + `deparse` is not that bad in long formulas for non visual usage. – Julius Vainora Feb 03 '13 at 11:27
  • it is not ideal, I need visual usage and deparse alone will not do the job. There must be something that that generates simple string from the formula! – Tomas Feb 03 '13 at 15:30
  • BTW, `Reduce(paste, deparse(frm))` is a nice trick to replace `paste(deparse(frm), collapse=' ')`, thanks. – Tomas Feb 03 '13 at 15:32
  • @TMS`Reduce(paste, deparse(frm, width.cutoff = 500))` seems to do the trick. – Brian D Feb 04 '16 at 16:43
  • 4
    No more `Reduce(paste, deparse(.))` or `paste(deparse(.), collapse=' ')`: R 4.0.0 (released 2020-04-24) introduced deparse1 which never splits the result into multiple strings (see [my answer](https://stackoverflow.com/a/62930525/1870254)) – jan-glx Jul 16 '20 at 08:31
32

Try format :

paste("my text", format(frm))
## [1] "my text celkem ~ rok + mesic"
G. Grothendieck
  • 211,268
  • 15
  • 177
  • 297
  • 1
    thanks, however this has the same problem with wrapping long formulas as `deparse` has, disregarding the width and justify options... – Tomas Feb 03 '13 at 15:20
  • 5
    It worked when I tried your example but, yes, I have checked the source now and it does indeed call `deparse`. It seems that `deparse(fo, cutoff.width = 200)` works but there is no way to pass that argument to deparse when using `format`. – G. Grothendieck Feb 03 '13 at 17:22
12

or as an alternative to Julius's version (note: your code was not self-contained)

celkem = 1
rok = 1
mesic = 1
model <- lm(celkem ~ rok + mesic)
paste("my model ", deparse(formula(model)))
Dieter Menne
  • 9,643
  • 39
  • 65
11

Simplest solution covering everything:

f <- formula(model)
paste(deparse(f, width.cutoff = 500), collapse="")
Tomas
  • 52,167
  • 46
  • 207
  • 345
  • R 4.0.0 (released 2020-04-24) introduced deparse1 which never splits the result into multiple strings (see [my answer](https://stackoverflow.com/a/62930525/1870254)) – jan-glx Jul 16 '20 at 08:27
7

The easiest way is this:

f = formula(model)
paste(f[2],f[3],sep='~')

done!

TPArrow
  • 1,183
  • 13
  • 22
  • This is by far the best general-purpose answer. It is base R and does not have the `width.cutoff` issue like `deparse` and `format()`. – Jonas Lindeløv Dec 28 '20 at 20:45
6

R 4.0.0 (released 2020-04-24) introduced deparse1 which never splits the result into multiple strings:

f <- y ~ a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + 
     p + q + r + s + t + u + v + w + x + y + z
deparse(f)
# [1] "y ~ a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + " "    p + q + r + s + t + u + v + w + x + y + z"                   
deparse1(f)
# [1] "y ~ a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z"



However, it still has a width.cutoff argument (default (an maximum): 500) after which linebreaks are introduced but with lines separated by collapse (default: " ") not \n, leaving extra white whitespace (even with collapse = "") (use gsub to remove them if needed, see Ross D's answer):

> f <- rlang::parse_expr( paste0("y~", paste0(rep(letters, 20), collapse="+")))
> deparse1(f, collapse = "")
[1] "y ~ a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u +     v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p +     q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k +     l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + a + b + c + d + e + f +     g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z"

To use it in R < 4.0.0 use backports (recommended) or copy it's implementation:

#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-2019 The R Core Team
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  https://www.R-project.org/Licenses/

deparse1 <- function (expr, collapse = " ", width.cutoff = 500L, ...) 
    paste(deparse(expr, width.cutoff, ...), collapse = collapse)

jan-glx
  • 4,580
  • 29
  • 50
2

Here a solution which use print.formula, it seems trick but it do the job in oneline and avoid the use of deparse and no need to use extra package. I just capture the output of the print formula, using capture.output

paste("my text",capture.output(print(formula(celkem ~ rok + mesic))))
[1] "my text celkem ~ rok + mesic"

In case of long formula:

 ff <- formula(paste("y ~ ", paste0("x", 1:12, collapse = " + ")))
 paste("my text",paste(capture.output(print(ff)), collapse= ' '))

 "my text y ~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 +      x12"
agstudy
  • 113,354
  • 16
  • 180
  • 244
  • Thanks, but `capture.output(print())` can be just simplified to `deparse()` as Julius proposed, with exactly the same result: `paste(deparse(frm), collapse= ' ')`... – Tomas Feb 03 '13 at 15:26
2

Another deparse-based solution is rlang::expr_text() (and rlang::quo_text()):

f <- Y ~ 1 + a + b + c + d + e + f + g + h + i +j + k + l + m + n + o + p + q + r + s + t + u
rlang::quo_text(f)
#> [1] "Y ~ 1 + a + b + c + d + e + f + g + h + i + j + k + l + m + n + \n    o + p + q + r + s + t + u"

They do have a width argument to avoid line breaks, but that is limited to 500 characters too. At least it's a single function that is most likely loaded already...

jan-glx
  • 4,580
  • 29
  • 50
1

Then add gsub to remove white spaces

gsub("  ", "", paste(format(frm), collapse = ""))
Nazik
  • 8,393
  • 26
  • 72
  • 115
Ross D
  • 309
  • 2
  • 4