我经常发现自己需要根据某些条件对数据帧应用少量基于规则的转换,通常是具有特定值的固定数量的字段。转换可以修改任意数量的列,通常是一到三列。与数据帧中的总行数相比,这些转换中涉及的行数较少。目前我正在使用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
}
)
)
有什么更好的方法来解决这个问题?
答案 0 :(得分: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()
证明太慢,你可以编写一些布尔函数来更快,但我怀疑这是必要的。