【问题标题】:Efficient method to filter and add based on certain conditions (3 conditions in this case)基于特定条件过滤和添加的有效方法(本例中为 3 个条件)
【发布时间】:2014-06-12 19:57:18
【问题描述】:

我有一个看起来像这样的数据框

     a    b    c   d
     1    1    1   0
     1    1    1   200
     1    1    1   300
     1    1    2   0
     1    1    2   600
     1    2    3   0
     1    2    3   100
     1    2    3   200
     1    3    1   0

我有一个看起来像这样的数据框

     a    b    c   d
     1    1    1   250
     1    1    2   600
     1    2    3   150
     1    3    1   0

我现在正在做 {

  n=nrow(subset(Wallmart, a==i &    b==j & c==k  ))
  sum=subset(Wallmart, a==i &    b==j & c==k  )
  #sum
  sum1=append(sum1,sum(sum$d)/(n-1))

}

我想添加“d”列并通过计算行数而不计算 0 来取平均值。例如第一行是 (200+300)/2 = 250。 目前我正在构建一个存储“d”列的列表,但理想情况下我希望它采用上述格式。例如第一行看起来像

     a    b    c   d
     1    1    1   250

这是一种非常低效的方式来完成这项工作。代码需要很长时间才能循环运行。 因此,感谢任何帮助,使其运行得更快。原始数据框有大约一百万行。

【问题讨论】:

  • ...你到底想达到什么目的?
  • 我没有看到循环。您的问题似乎缺少一些东西。无论如何,永远不要在循环中使用append
  • 对不起,我编辑了这个问题,现在应该很容易理解了。谢谢。
  • @user2575429,我在您的编辑后更新了我的答案。

标签: r data.table plyr dplyr subset-sum


【解决方案1】:

你可以试试aggregate:

aggregate(d ~ a + b + c, data = df, sum)
#   a b c   d
# 1 1 1 1 500
# 2 1 3 1   0
# 3 1 1 2 600
# 4 1 2 3 300

正如@Roland 所说,对于更大的数据集,您可以尝试使用data.tabledplyr,例如:

library(dplyr)
df %>%
  group_by(a, b, c) %>%
  summarise(
    sum_d = sum(d))

# Source: local data frame [4 x 4]
# Groups: a, b
# 
#   a b c sum_d
# 1 1 1 1   500
# 2 1 1 2   600
# 3 1 2 3   300
# 4 1 3 1     0

编辑更新问题。 如果你想计算分组平均值,不包括零行,你可以试试这个:

aggregate(d ~ a + b + c, data = df, function(x) mean(x[x > 0]))
#   a b c   d
# 1 1 1 1 250
# 2 1 3 1 NaN
# 3 1 1 2 600
# 4 1 2 3 150

df %>%
  filter(d != 0) %>%
  group_by(a, b, c) %>%
  summarise(
    mean_d = mean(d))

#   a b c mean_d
# 1 1 1 1    250
# 2 1 1 2    600
# 3 1 2 3    150

但是,因为您似乎希望将零视为缺失值而不是数字零,所以我认为在准备数据集时将它们转换为 NA 会更好,之前计算。

df$d[df$d == 0] <- NA
df %>%
  group_by(a, b, c) %>%
  summarise(
    mean_d = mean(d, na.rm = TRUE))

#   a b c mean_d
# 1 1 1 1    250
# 2 1 1 2    600
# 3 1 2 3    150
# 4 1 3 1    NaN

【讨论】:

  • +1 但对于一百万个观察数据,data.table 或 dplyr 可能更可取。
  • @Roland,感谢您的评论!我添加了一个dplyr 替代方案。
  • 谢谢@Henrik,特别是编辑后的回答。
【解决方案2】:

这是您上次编辑时的data.table 解决方案。

library(data.table)
DT <- setDT(df)[, if(any(d[d > 0])) mean(d[d > 0]) else 0, by = c("a","b","c")]
# a b c  V1
# 1: 1 1 1 250
# 2: 1 1 2 600
# 3: 1 2 3 150
# 4: 1 3 1   0

编辑#2:

@Arun 建议加快速度

setDT(df)[, mean(d[d > 0]), by = c("a","b","c")][is.nan(V1), V1 := 0]

编辑#3

@eddis 建议

setDT(df)[, sum(d) / pmax(1, sum(d > 0)), by = list(a, b, c)]

【讨论】:

  • 感谢大卫建议替代方法。 NaN 不是问题,我会解决它。
  • 这有点快:setDT(df)[, sum(d) / pmax(1, sum(d &gt; 0)), by = list(a, b, c)]
【解决方案3】:

这是另一种方式:

Step1:设置数据表:

df <- read.table(text="     a    b    c   d
     1    1    1   0
     1    1    1   200
     1    1    1   300
     1    1    2   0
     1    1    2   600
     1    2    3   0
     1    2    3   100
     1    2    3   200
     1    3    1   0",header=T)
library(data.table)
setDT(df)
setkey(df,a,b,c)

Step2:进行计算:

df[,sum(d)/ifelse((cnt=length(which(d>0)))>0,cnt,1),by=key(df)]

注意这里不推荐循环。 最好的策略是将解决方案矢量化,如上面的示例所示。

Step3:让我们测试时间:

> dt<-df
> for(i in 1:20) dt <- rbind(dt,dt)
> dim(dt)
[1] 9437184       4
> setkey(dt,a,b,c)
> dt[,sum(d)/ifelse((cnt=length(which(d>0)))>0,cnt,1),by=key(dt)]
   a b c  V1
1: 1 1 1 250
2: 1 1 2 600
3: 1 2 3 150
4: 1 3 1   0
> system.time(dt[,sum(d)/ifelse((cnt=length(which(d>0)))>0,cnt,1),by=key(dt)])
   user  system elapsed 
  0.495   0.090   0.609 

因此,将近 10M 条记录的计算大约需要 0.5 秒!

希望这会有所帮助!

【讨论】:

  • 两个 cmets - 设置密钥然后将其排除在您的时间之外是不公平的(不是什么大问题,因为设置密钥并不会改变太多的速度,但仍然如此),并在另一个 data.table 答案中查看我的评论,以更简单的方式完成您所做的事情
  • 谢谢@eddi。关于第一点:我试图说明执行速度,并且设置密钥不需要太多时间,所以我没有包括它。但是,我注意到这里很重要的一点,dt 上的 20 倍 rbinddf 上的 20 倍 rbind 相比运行得更快。有cmet吗??第二点非常好,非常感谢!
  • 不知道该评论什么,除了data.table rbind 更好:) 它在内部使用rbindlist,这真的很快。
猜你喜欢
  • 2021-08-26
  • 2021-10-04
  • 2014-02-17
  • 1970-01-01
  • 2022-07-21
  • 2014-02-20
  • 2019-09-10
  • 2021-12-14
  • 2016-07-31
相关资源
最近更新 更多