代码之家  ›  专栏  ›  技术社区  ›  89_Simple

R: 对数据帧使用“应用族”而不是“for循环”

  •  1
  • 89_Simple  · 技术社区  · 6 年前

    首先,一些示例数据:

    location <- c("A","B","C","D","E")
    mat <- as.data.frame(matrix(runif(1825),nrow=5,ncol=365))
    t1<- c(258,265,306,355)
    t2<- c(258,270,302,352)
    t3<- c(258,275,310,353)
    t4<- c(258,280,303,355)
    t5<- c(258,285,312,356)
    ts<-rbind(t1,t2,t3,t4,t5)
    dat <-as.data.frame(cbind(location,mat,ts))
    names(dat)[367:370] <- c("pl","vg","re","me")
    

    location 是站点的名称。 V1 V365 是每日降雨量(含 V1 像 一年的第一天)。我想做的是:

    对于每行( 地方 ),我想根据上一个 四列 pl , vg , re , me (规定了一年中的几天)

    例如,对于位置 A ,最后四列为:

    pl公司 = 258 vg公司 = 265 重新 = 306 = 355

    因此,对于位置 A. ,我想生成三个降雨量值,它们是来自以下方面的降雨量总和:

    V258 V264

    V265 V305

    V306 V355

    在所有五个地点都这样做。

    我所做的是:

     for(j in unique(dat$location)){
    
        loc <- dat[dat$location == j,]
    
        pl.val <- loc$pl + 1 # have to add + 1 since the rainfall starts from the second column
       vg.val <- loc$vg + 1
       re.val <- loc$re + 1
       me.val <- loc$me + 1
    
       rain1 <- sum(loc[,pl.val:vg.val]) 
       rain2 <- sum(loc[,(vg.val+ 1):re.val]) 
       rain3 <- sum(loc[,(re.val + 1):me.val]) 
    }     
    

    我想避免使用 for 循环并使用 apply 功能。然而,我是 不熟悉如何使用apply函数对所有行进行计算 (位置)一次性完成。有人能告诉我怎么做吗?

    谢谢

    编辑

    如果我有其中一个位置的降雨量值为NA,而其他日期为NAs,那么我如何修改下面接受为答案的代码。以下是示例数据

    location <- c("A","B","C")
    mat <- as.data.frame(matrix(runif(365*3),nrow=3,ncol=365))
    t1<- c(258,265,306,355)
    t2<- c(258,NA,NA,NA)
    t3<- c(258,275,310,353)
    ts<-rbind(t1,t2,t3)
    dat <-as.data.frame(cbind(location,mat,ts))
    names(dat)[367:370] <- c("pl","vg","re","me")
    dat[2,-c( 367:370)] <- NA
    
    2 回复  |  直到 6 年前
        1
  •  1
  •   cuttlefish44    6 年前

    我想你想要速度。

    我认为数据的形式不适合计算,因为只有col1是字符,col367:370是不同的类型,而且非常广泛。也许逐行计算不是个好主意。基本上,R适合逐列计算。

    如果我是你,我会准备以下表格中的数据;

    library(tidyverse)
    
    dat1 <- dat[, -c(1, 367:370)] %>% 
      t() %>% 
      as.tibble() %>% 
      set_names(location)
    
    dat2 <- dat[, 367:370] %>% 
      t() %>% 
      as.tibble() %>% 
      set_names(location)
    

    我建议 map2() 计算每对列。 .x 每列 dat1 .y 每列 dat2 (它们被视为向量)。以下代码的速度是您的50倍。

    map2(dat1, dat2, ~ {
      pl.val <- .y[1]
      vg.val <- .y[2]
      re.val <- .y[3]
      me.val <- .y[4]
    
      rain1 <- sum(.x[pl.val:vg.val]) 
      rain2 <- sum(.x[(vg.val+ 1):re.val]) 
      rain3 <- sum(.x[(re.val + 1):me.val]) 
      c(rain1 = rain1, rain2 = rain2, rain3 = rain3)
      }
    )
    


    [添加NL(应用,映射)]

    注:对于 apply() 治疗 data.frame 由于转换为矩阵而具有字符和数字。所以如果你使用 应用() ,需要删除位置列。

    apply(dat[,-1], MARGIN = 1, function(x){
      pl.val <- x[367 - 1]
      vg.val <- x[368 - 1]
      re.val <- x[369 - 1]
      me.val <- x[370 - 1]
    
      rain1 <- sum(x[pl.val:vg.val]) 
      rain2 <- sum(x[(vg.val+ 1):re.val]) 
      rain3 <- sum(x[(re.val + 1):me.val]) 
      c(rain1 = rain1, rain2 = rain2, rain3 = rain3)
    })
    

    mapply() 基本上与 map2() . 在这个问题上, mapply() 提供最佳性能。

    mapply(function(.x, .y){
      pl.val <- .y[1]
      vg.val <- .y[2]
      re.val <- .y[3]
      me.val <- .y[4]
    
      rain1 <- sum(.x[pl.val:vg.val]) 
      rain2 <- sum(.x[(vg.val+ 1):re.val]) 
      rain3 <- sum(.x[(re.val + 1):me.val]) 
      c(rain1 = rain1, rain2 = rain2, rain3 = rain3)
      }, dat1, dat2)
    

    [基准]

    Unit: microseconds
                 expr       min        lq       mean     median        uq       max neval cld
     forloop_method() 14154.075 15074.555 17110.4060 16588.1200 18416.387 25869.836   100   c
        map2_method()   205.586   234.263   325.8762   313.9395   333.633  2072.911   100 a  
       apply_method()  1617.443  1684.812  1913.9187  1783.2480  1933.216  4189.687   100  b 
      mapply_method()   154.972   185.079   213.9370   210.2300   225.978   468.690   100 a  
    


    [附加2(错误处理)]

    当没有NA时,下面的代码几乎和上面的代码一样快。(注意:如果在一行中,可以省略 {} 属于 if(...) { A } else { B } 例如 if(...) A else B .)

    results <- map2(dat1, dat2, ~ {
      pl.val <- .y[1]
      vg.val <- .y[2]
      re.val <- .y[3]
      me.val <- .y[4]
    
      rain1 <- if(is.na(pl.val) | is.na(vg.val)) NA else sum(.x[pl.val:vg.val], na.rm = T)
      rain2 <- if(is.na(vg.val) | is.na(re.val)) NA else sum(.x[(vg.val+ 1):re.val], na.rm = T)
      rain3 <- if(is.na(re.val) | is.na(me.val)) NA else sum(.x[(re.val + 1):me.val], na.rm = T)
      c(rain1 = rain1, rain2 = rain2, rain3 = rain3)
      }
    )
    
    # If you want data.frame instead of list
    invoke("rbind", results)
    
        2
  •  1
  •   Carl Boneri    6 年前

    我不知道你是怎么想回来的雨天的?是否将它们绑定为3个新列?

    基本上,这是代码。。。我将浏览: 对于 dat 数据框架中,选择表示天数的列,然后构建这些数字对应值的序列,但要逐步减少下一个值,以便每次都能得到正确的列。因为我们现在在每个地方都有行动 slice 将值转换为数字,并对 apply 步使用 ?sprintf 附加 V 我们从序列创建中获得的每个列编号,并作为列表返回。然后我简单地用相应位置的ID命名列表向量。。。如果要将其附加到数据中。框架也很简单。

    lapply(1:nrow(dat), function(i){
        d_idx <- dat[i,] %>% dplyr::select(dplyr::matches("pl|vg|re|me"))
        a_idx <- data.frame(
            s = as.numeric(d_idx[,1:3]), 
            e = c(as.numeric(d_idx[,2:3]) - 1, as.numeric(d_idx[[4]]))
        )
        as.list(apply(a_idx, 1, function(j){
            rowSums(dat[i, sprintf('V%s', seq(min(j),max(j)))])
        })) %>% setNames(sprintf('rain%s', 1:length(.)))
    }) %>% setNames(dat$location)
    
    
    $A
    $A$rain1
    [1] 2.391448
    
    $A$rain2
    [1] 21.58306
    
    $A$rain3
    [1] 27.805
    
    
    $B
    $B$rain1
    [1] 5.339885
    
    $B$rain2
    [1] 16.57476
    
    $B$rain3
    [1] 26.37708
    
    
    $C
    $C$rain1
    [1] 7.929777
    
    $C$rain2
    [1] 17.81324
    
    $C$rain3
    [1] 20.12217
    
    
    $D
    $D$rain1
    [1] 9.715258
    
    $D$rain2
    [1] 11.2547
    
    $D$rain3
    [1] 25.93332
    
    
    $E
    $E$rain1
    [1] 12.81343
    
    $E$rain2
    [1] 15.41595
    
    $E$rain3
    [1] 21.79217