如何在R中创建一个数据框,当其他列之一已更改时自动更新列?

时间:2018-07-01 23:05:06

标签: r tibble

这是我的输入

    > a<-data.frame(p=c(1:3),q=c(3:5))
    > a
      p q
    1 1 3
    2 2 4
    3 3 5
    > a<-a%>%mutate(r=p*q)
    > a
      p q  r
    1 1 3  3
    2 2 4  8
    3 3 5 15

修改一个单元格

   > a$p[1]<-2
   > a
     p q  r
   1 2 3  3
   2 2 4  8
   3 3 5 15     

a $ r [1]尚未更新为2 * 3 = 6

我希望输出为:

    > a
      p q  r
    1 1 3  6
    2 2 4  8
    3 3 5 15

那就是我希望r的值在p或q之一改变时自动更新。

2 个答案:

答案 0 :(得分:3)

您正在尝试复制Excel用户体验,对吗?编辑一个单元格将更新所有相关单元格。

在Excel的幕后程序以及R中最终的解决方案是重新计算所有可能的逻辑下游值。某些东西将触发它。在Excel中,触发器正在更改单元格或正在执行重新计算命令。在R扩展名Shiny中,可以进行各种鼠标移动和单击操作。

是的,您可以编写一个方法来查看a$pa$q的任何成员是否已更改,但是还必须触发该脚本。 R并不提供始终运行的功能,例如PC中等待键盘输入或鼠标移动的功能。在大多数情况下,R不需要花太多时间就可以进行所有可能的计算,而不仅仅是基于a[1,3]的新值来计算a[a,1]

您可以创建一个执行所有常规计算的函数。您可以通过在控制台中输入recalc()来启动它。不需要任何参数。为它分配一个键盘快捷键,并在对数据对象进行任何更改后运行它。

    recalc <- function() {
      a$r <<- a$p * a$q
      ...
      ...
      TRUE
      }

超级分配运算符<<-允许您在全局环境中操作对象,就像在控制台中键入命令一样。这不是通常应该构建函数的方式。

答案 1 :(得分:2)

这可能不是您要查找的内容,但这是一种更新单个值并已计算的列进行相应更新的简单方法。我将使用data.table包,因为它允许您按引用,按行更新列,因此您不必每次重新计算整个列。

首先,我向a添加一个属性,该属性指定应更新的计算列。然后,我定义一个用于更新各个值的函数,该函数可识别该属性并按定义重新计算列。

a <- data.frame(p = c(1:3),q = c(3:5))
a <- a %>% mutate(r = p*q, s = p/q - 4)

library(data.table)
setDT(a)
attr(a, 'refresh.cols') <- c(r = 'p*q', s = 'p/q - 4')

df.update <- function(df, row, ...){
  l <- list(...)
  Map(function(x, nm) df[row, eval(parse(text = paste(nm, ':=', x)))]
      , l, names(l))
  df[row, names(attr(a, "refresh.cols")) := 
       lapply(attr(a, "refresh.cols"), function(x) eval(parse(text = x)))]
}
a
#    p q  r         s
# 1: 1 3  3 -3.666667
# 2: 2 4  8 -3.500000
# 3: 3 5 15 -3.400000

a %>% df.update(row = 1, p = 2)
a
#    p q  r         s
# 1: 2 3  6 -3.333333
# 2: 2 4  8 -3.500000
# 3: 3 5 15 -3.400000


a %>% df.update(row = 3, p = 6, q = 7)
a
#    p q  r         s
# 1: 2 3  6 -3.333333
# 2: 2 4  8 -3.500000
# 3: 6 7 42 -3.142857

a %>% df.update(row = 2, p = 9, q = 'p/3 + 1')
a
#    p q  r         s
# 1: 2 3  6 -3.333333
# 2: 9 4 36 -1.750000
# 3: 6 7 42 -3.142857