【问题标题】:conditional calculations in data frame数据框中的条件计算
【发布时间】:2015-06-17 16:31:31
【问题描述】:

我经常需要根据因子变量的条件从数据框中的现有变量中计算新变量。

编辑 在 2 分钟内得到 4 个答案,我意识到我的例子过于简单化了。请看下文。

简单示例:

df <- data.frame(value=c(1:5),class=letters[1:5])
df
value class
1     a
2     b
3     c
4     d
5     e

我可以使用这样的代码

df %>% 
    mutate(result=NA) %>%
    mutate(result=ifelse(class=="a",value*1,result)) %>%
    mutate(result=ifelse(class=="b",value*2,result)) %>%
    mutate(result=ifelse(class=="c",value*3,result)) %>%
    mutate(result=ifelse(class=="d",value*4,result)) %>%
    mutate(result=ifelse(class=="e",value*5,result))

对我的变量执行条件计算,导致

value class result
 1     a      1
 2     b      4
 3     c      9
 4     d     16
 5     e     25

实际上,类的数量更大,计算更复杂,但是,我更喜欢更干净的东西,比如这样

df %>%
mutate(results=switch(levels(class),
                    "a"=value*1,
                    "b"=value*2,
                    "c"=value*3,
                    "d"=value*4,
                    "e"=value*5))

这显然行不通

Error in switch(levels(1:5), a = 1:5 * 1, b = 1:5 * 2, c = 1:5 * 3, d =  1:5 *  : 
  EXPR must be a length 1 vector

有没有办法让我用 dplyr 管道(或其他)更好地做到这一点?

编辑 实际上,我的计算中包含更多的值变量,它们不是简单的连续向量,它们是数千行测量数据。

这是我的简单示例,带有第二个随机值变量(同样,它更多地存在于我的真实数据中)

df <- data.frame(value1=c(1:5),value2=c(2.3,3.6,7.2,5.6,0),class=letters[1:5])
value1 value2 class
  1    2.3     a
  2    3.6     b
  3    7.2     c
  4    5.6     d
  5    0.0     e

我的计算因每种情况而异。我知道我可以像这样简化一些

df %>% 
mutate(result=NA,
     result=ifelse(class=="a",value1*1,result),
     result=ifelse(class=="b",value1/value2*4,result),
     result=ifelse(class=="c",value2*3.57,result),
     result=ifelse(class=="d",value1+value2*2,result),
     result=ifelse(class=="e",value2/value1/5,result))

不过,类似于上述 switch 示例的可行解决方案会更加简洁。

【问题讨论】:

  • omg,你太快了......而且我对我的实际问题进行了简单的简化。在发布新解决方案之前,请给我一些时间进行编辑...
  • 不需要多个mutates,你可以在同一个mutate中引用“新鲜”变量,即:mutate(result=NA, result=ifelse(class=="a",value*1,result)), result= ... )
  • 谢谢,我在编辑中加入了这个。我同意一些改进,但它可以更清洁吗?
  • 嘿,在此编辑之后,没有任何解决方案可以工作......
  • this question差不多。

标签: r dplyr


【解决方案1】:

这里不用ifelse,可以用merge

df <- data.frame(value=c(1:5),class=letters[1:5])
cond <- data.frame(ratio=c(1:5),class=letters[1:5])
transform(merge(df,cond),result=value*ratio)

  class value ratio result
1     a     1     1      1
2     b     2     2      4
3     c     3     3      9
4     d     4     4     16
5     e     5     5     25

OP 编辑​​后

看起来 OP 希望为每个类应用不同的功能。 这是一个 data.table 解决方案。我认为它简单易读。 首先,我为每个因素创建函数:

## here each function takes a data.table as an single argument
fns <- list(
  function(x) x[,value1]*1,
  function(x) x[,value1]/x[,value2]*4,
  function(x) x[,value2]*3.57,
  function(x) x[,value1]+x[,value2]*2,
  function(x) x[,value2]/x[,value1]/5
)
## create a names list here 
## the names here are just the class factors
fns <- setNames(fns,letters[1:5])

按类应用函数很简单。我创建了函数名,并使用do.call 通过函数名调用函数

## using data.table here for grouping feature
## .SD is the rest of columns except the grouping variable
## the code can also be written in dplyr or in base-R
library(data.table)
setDT(df)[,value:= fns[[class]](.SD),by=class]

     value1 value2 class     value
 1:      1    2.3     a  1.000000
 2:      2    3.6     b  2.222222
 3:      3    7.2     c 25.704000
 4:      4    5.6     d 15.200000
 5:      5    0.0     e  0.000000
 6:      1    2.3     a  1.000000
 7:      2    3.6     b  2.222222
 8:      3    7.2     c 25.704000
 9:      4    5.6     d 15.200000
10:      5    0.0     e  0.000000

我用这个df:

df <- data.frame(value1=c(1:5),value2=c(2.3,3.6,7.2,5.6,0),
                 class=rep(letters[1:5],2))

【讨论】:

  • 谢谢,但请参阅我的编辑。这不仅仅是一个简单的比率,每个条件的计算都是不同的。
  • 感谢您的更新。对于熟悉 data.table 语法的人来说,这无疑是一个可读的解决方案,但是我不是……此外,以这种方式定义函数在此示例中效果很好。然而,在我真正的问题中,要执行的计算使用了大约 20 个不同的变量,而每个类只需要它们的一个子集。据我在您的解决方案中了解,即使大多数类只需要其中的 4-5 个,我也必须为我所有的 ~30 个类创建函数,其中包含所有 20 个变量。无论如何,这又是我的错,因为我的例子仍然太简单了。我很抱歉。
  • @user3460194 你可以看到我的编辑。现在函数将 data.table 作为输入并在内部选择变量。你解释你的问题越多,你最好你得到一个解决方案。希望这次没事。
  • 好的,谢谢,这很有趣。尽管我现在可能会坚持我的 ifelse 解决方案,但我肯定在这里学到了一些东西!下次我会努力解释得更好......
【解决方案2】:

正如我在 cmets 中提到的,this question 或多或少与这个相同(您应该阅读那里的答案以了解下面发生的情况):

library(data.table)
dt = as.data.table(df) # or setDT to convert in place
dt[, class := as.character(class)] # simpler

# create a data.table with *functions* to match each class
fns = data.table(cls = letters[1:5], fn = list(quote(value1*1), quote(value1/value2*4), quote(value2*3.57), quote(value1+value2*2), quote(value2/value1/5)), key = 'cls')

# I have to jump through hoops here, due to a bug or two, see below
setkey(dt, class)
newvals = dt[, eval(fns[class]$fn[[1]], .SD), by = class]$V1
dt[, result := newvals][]
#   value1 value2 class    result
#1:      1    2.3     a  1.000000
#2:      2    3.6     b  2.222222
#3:      3    7.2     c 25.704000
#4:      4    5.6     d 15.200000
#5:      5    0.0     e  0.000000

由于data.table 中的一些错误,以下简单版本尚无法使用:

dt[, result := eval(fns[class]$fn[[1]], .SD), by = class]

# or even better
dt[fns, result := eval(fn[[1]], .SD), by = .EACHI]

已提交错误报告。


我在下面 Frank 的 cmets 中添加了这个建议,因为我认为它很酷,这样它更有可能保存在 SO 中。一种更易读的创建函数表的方式如下:

quotem <- function(...) as.list(sys.call())[-1]

fnslist <- quotem(a = value1*1,
                  b = value1/value2*4,
                  c = value2*3.57,
                  d = value1+value2*2,
                  e = value2/value1/5)

fns = data.table(cls=names(fnslist),fn=fnslist,key="cls")

【讨论】:

  • 这是一种更可读/可写的构造fnsquotem &lt;- function(...) as.list(sys.call())[-1]; fnslist &lt;- quotem(a = value1*1, b = value1/value2*4, c = value2*3.57, d = value1+value2*2, e = value2/value1/5); fns = data.table(cls=names(fnslist),fn=fnslist,key="cls")
  • @eddi 好的,你是对的。我对 data.table 完全不熟悉,并且引用问题中的 dplyr 解决方案似乎基于汇总函数,它是一个聚合函数。无论如何,感谢您的帮助,但我想我将不得不继续使用我的 dplyr ifelse 解决方案
  • @user3460194 dplyr 解决方案确实不能做data.table 所做的事情(我实际上不知道像这样的功能匹配解决方案是否可以使用dplyr )。 ifelse 相当缓慢且效率低下,但如果它符合您的需求,那就可以了。
【解决方案3】:

使用dplyr 和@agstudy 示例的类似想法:

library(dplyr)

df %>% 
  left_join(cond) %>% 
  mutate(result = value * ratio)

这给出了:

#  value class ratio result
#1     1     a     1      1
#2     2     b     2      4
#3     3     c     3      9
#4     4     d     4     16
#5     5     e     5     25

【讨论】:

    【解决方案4】:

    这是@agstudy 答案的dplyr/lazyeval 实现:

    # required packages
    require(lazyeval)
    require(dplyr)
    # data (from @agstudy)
    df <- data.frame(value1=c(1:5),value2=c(2.3,3.6,7.2,5.6,0),
                     class=rep(letters[1:5],2))
    # functions (lazy instead of functions)
    fns <- list(
      a = lazy(x*1), 
      b = lazy(x/y*4), 
      c = lazy(y*3.57),
      d = lazy(x+y*2),
      e = lazy(y/x/5)
    )
    # mutate call
    df %>% 
      group_by(class) %>%
      mutate(value = lazy_eval(fns[class][[1]], 
                               list(x = value1, y = value2)))
    

    【讨论】:

    • 谢谢,有趣!我是否需要命名变量 x,y,z... 并将它们作为列表传递给lazy_eval 函数?或者有没有办法直接在fns列表中使用变量的真实名称?
    猜你喜欢
    • 1970-01-01
    • 2020-01-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 2017-06-08
    • 2021-12-31
    • 2023-02-20
    • 2020-05-16
    相关资源
    最近更新 更多