1

我经常发现自己需要根据特定条件对数据帧应用少量基于规则的转换,通常是具有特定值的固定数量的字段。转换可以修改任意数量的列,通常是一到三个。与数据帧中的总行数相比,这些转换中涉及的行数很小。目前我正在使用ddply但性能不足,因为ddply修改了所有行。

我正在寻找一种以优雅、通用的方式解决这个问题的方法,利用仅需要更改少量行的事实。下面是我正在处理的转换类型的简化示例。

df <- data.frame(Product=gl(4,10,labels=c("A","B", "C", "D")), 
                 Year=sort(rep(2002:2011,4)), 
                 Quarter=rep(c("Q1","Q2", "Q3", "Q4"), 10), 
                 Sales=1:40)           
> head(df)
  Product Year Quarter Sales
1       A 2002      Q1     1
2       A 2002      Q2     2
3       A 2002      Q3     3
4       A 2002      Q4     4
5       A 2003      Q1     5
6       A 2003      Q2     6
> 
transformations <- function(df) {
    if (df$Year == 2002 && df$Product == 'A') {
        df$Sales <- df$Sales + 3
    } else if (df$Year == 2009 && df$Product == 'C') {
        df$Sales <- df$Sales - 10
        df$Product <- 'E'
    }
    df
}

library(plyr)
df <- ddply(df, .(Product, Year), transformations)

> head(df)
  Product Year Quarter Sales
1       A 2002      Q1     4
2       A 2002      Q2     5
3       A 2002      Q3     6
4       A 2002      Q4     7
5       A 2003      Q1     5
6       A 2003      Q2     6

安装硬编码条件我正在使用条件和转换函数的配对列表,例如下面的代码,但这不是一个有意义的改进。

transformation_rules <- list(
  list(
    condition = function(df) df$Year == 2002 && df$Product == 'A',
    transformation = function(df) {
      df$Sales <- df$Sales + 3
      df
    }
  )
)

有什么更好的方法来解决这个问题?

4

1 回答 1

2

我认为你根本不需要使用plyr这个问题。我认为您可以简单地使用ifelse()和利用 R 是矢量化的事实并获得相同的结果。

由于您的函数直接修改了Sales列,因此在运行 plyr: 之前,我像这样复制了它df2 <- df。我还让我的示例创建了一个新列Sales2,而不是覆盖该Sales列。

然后像这样重写你的函数:

df2$Sales2 <- with(df2, ifelse(Year == 2002 & Product == "A", Sales + 3,
                        ifelse(Year == 2009 & Product == "C", Sales - 10, Sales)))

并测试输出是否相等:

> all.equal(df$Sales, df2$Sales2)
[1] TRUE

并且比较两者之间的系统时序表明,避免 ddply 的矢量化版本要快得多:

> system.time(df <- ddply(df, .(Product, Year), transformations))
   user  system elapsed 
  0.012   0.000   0.012 
> system.time(df2$Sales2 <- with(df2, ifelse(Year == 2002 & Product == "A", Sales + 3,
+                         ifelse(Year == 2009 & Product == "C", Sales - 10, Sales))))
   user  system elapsed 
      0       0       0 

所以,除非我遗漏了什么——你可以plyr在这里避免所有的东西,并获得一些不错的速度改进。如果ifelse()证明太慢,您可以编写一些布尔函数来更快,尽管我怀疑这是必要的。

于 2012-07-04T06:21:49.657 回答