如何将R公式转换为文本?

67

我在处理公式时遇到了困难,因为我试图将公式与图表标题连接起来。然而,当我像处理文本一样处理公式时,却失败了:

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

这很好。现在我想构建像"my text celkem ~ rok + mesic"这样的字符串——这就是问题所在:

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"

现在我看到gtools包中有一个sprint函数,但我认为这是如此基本的事情,它应该在默认环境中解决!!


3
“sprint”不再是“gtools”的一部分。 有人知道这篇文章的解决方案吗? - zx8754
有人知道为什么formula(model)的默认行为是进行列表体操吗? - jessexknight
10个回答

51

来自包formula.tools的简短解决方案,作为一个函数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在长公式的情况下可能非常有用:

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"

这是因为在?deparsewidth.cutoff = 60L


2
唉,我对反解析(deparse)非常热衷,但现在看来它相当欺骗人心!一定有一些函数可以将公式转换为字符串,而不需要包装、截断等! - Tomas
不理想,我需要可视化的使用,而仅仅解析还不足以完成任务。一定有某种东西能够从公式中生成简单的字符串! - Tomas
顺便说一句,Reduce(paste, deparse(frm))是一个不错的技巧,可以替换paste(deparse(frm), collapse=' '),谢谢。 - Tomas
@TMSReduce(paste, deparse(frm, width.cutoff = 500))似乎可以解决问题。 - Brian D
6
R 4.0.0(于2020年4月24日发布)推出了deparse1,不再使用Reduce(paste, deparse(.))paste(deparse(.), collapse=' '),因为它们会将结果拆分为多个字符串。详情请参见我的回答 - jan-glx
显示剩余3条评论

36

尝试使用format

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

1
谢谢,不过这个问题与deparse一样,无视宽度和对齐选项,会导致长公式换行的问题。 - Tomas
5
我尝试了你的示例,并且它有效,但是我现在已经检查了源代码,它确实调用了 deparse。似乎 deparse(fo, cutoff.width = 200) 是有效的,但使用 format 时无法传递该参数。 - G. Grothendieck

18

最简单的解决方案,包括所有内容:

f <- formula(model)
paste(deparse(f, width.cutoff = 500), collapse="")

3
R 4.0.0(于2020年4月24日发布)引入了deparse1函数,它不会将结果分成多个字符串(请参见我的回答)。 - jan-glx

16

R 4.0.0(发布于2020年4月24日)引入了deparse1函数,它不再将结果拆分成多个字符串:

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"

然而,它仍具有一个width.cutoff参数(默认值为最大值:500),在该参数之后引入换行符,但使用collapse(默认值为" ")将行分隔而不是\n,留下额外的空白空间(即使使用collapse = "")(如果需要,请使用gsub来去除它们,参见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"

如果要在 R < 4.0.0 中使用它,建议使用backports或复制其实现:

#  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)


最佳答案。简单易行,无需额外的库。 - vasili111

12

或者作为Julius版本的替代方案(注意:您的代码不是自包含的)

celkem = 1
rok = 1
mesic = 1
model <- lm(celkem ~ rok + mesic)
paste("my model ", deparse(formula(model)))

1
但要注意,它存在朱利斯提到的问题 - deparse 会截断长公式。 - Tomas
有趣...从未想过。 - Dieter Menne

10

最简单的方法是这样的:

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

完成了!


这绝对是最好的通用答案。它基于R语言,不像deparseformat()那样存在width.cutoff问题。 - Jonas Lindeløv

2

这里有一个解决方案,它使用print.formula,看起来很巧妙,但它可以在一行中完成工作,避免了使用deparse并且不需要使用额外的包。我只是捕获了打印公式的输出,使用capture.output

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

对于较长的公式:

 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"

谢谢,但是capture.output(print())可以简化为Julius提出的deparse(),并且达到完全相同的结果:paste(deparse(frm), collapse= ' ')... - Tomas

2
另一种基于deparse的解决方案是rlang::expr_text()(以及rlang::quo_text()):

使用rlang::expr_text()(和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"

它们确实有一个宽度参数来避免换行,但这也仅限于500个字符。至少它是一个单一的函数,很可能已经被加载了...

最初的回答


1
然后添加 gsub 去除空格。
gsub("  ", "", paste(format(frm), collapse = ""))

那么?这个关键字不是前面要跟着一个if吗? - Himanshu Mishra
我对@G. Grothendieck的补充 - Ross D
1
这更像是对另一篇文章的评论,而不是答案本身。一旦你有足够的声望,你可以评论任何帖子。你总是可以评论自己的帖子。 - BenBarnes

1
今天我在优化一些函数。以下是几种迄今未被提及的方法。
f <- Y ~ 1 + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u
bench::mark(
  expression = as.character(as.expression(f)),
  deparse = deparse(f, width.cutoff = 500L),
  deparse1 = deparse1(f),
  tools = formula.tools:::as.character.formula(f),
  stringi = stringi::stri_c(f),
  I = as.character(I(f)),
  as = as(f, "character"),
  txt = gettext(f),
  txtf = gettextf(f),
  sub = sub("", "", f),
  chr = as.character(f),
  str = substring(f, 1L),
  paste = paste0(f),
)[c(1, 3, 5, 7)]

#> # A tibble: 13 x 3
#>    expression   median mem_alloc
#>    <bch:expr> <bch:tm> <bch:byt>
#>  1 expression   15.4us        0B
#>  2 deparse        31us        0B
#>  3 deparse1       34us        0B
#>  4 tools        58.7us    1.74MB
#>  5 stringi        67us    3.09KB
#>  6 I            64.1us        0B
#>  7 as          100.5us  521.61KB
#>  8 txt          83.4us        0B
#>  9 txtf         85.8us    3.12KB
#> 10 sub          64.6us        0B
#> 11 chr            60us        0B
#> 12 str          62.8us        0B
#> 13 paste        63.5us        0B

速度和基准测试并不是问题。如果您有一种在任何其他方面都比这里的其他解决方案更好的方法,请描述一下。 - Tomas
速度的六分之一在功能上更好。 - DaveG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接