【问题标题】:Column looping through a user function and storing output in a newly created column (R)列循环通过用户函数并将输出存储在新创建的列 (R)
【发布时间】:2016-02-20 20:58:06
【问题描述】:

我有一些由类似振荡的模式组成的数据,我想对峰值进行一些测量。我有几块代码,其中大多数都可以完全按照我的意愿工作。我遇到的主要问题是我不知道如何将它们集成起来以在功能上协同工作。

基本上,我想使用我在数据帧上编写的 freq 函数,以便它将遍历每一列(a、b 和 c)并给我函数的结果。然后我想将每一列的输出存储在一个新的数据框中,列名与源名称匹配。

我已经阅读了很多关于循环遍历列和在数据框中创建新列的答案,这就是我达到这一点的方式。一些单独的部分需要稍微调整,但我无法在任何地方找到一个很好的解释,说明我如何将它们组合在一起。我试过无济于事;我只是看不到正确的顺序。

(对于可重现的数据)

library(zoo)
count = 1:20
a = c(-0.802776, -0.748272, 0.187434, 1.23577, 1.00677, 0.874122, 0.232802, -0.279368, -1.57815, -1.76652, -0.958916, -0.316385, 0.831575, 1.19312, 1.45508, 0.848923, 0.257728, -0.318474, -1.14129, -1.42576)
b = c(-2.23512, -1.36572, -0.0357366, 0.925563, 1.53282, 0.171045, -0.438714, -1.38769, -0.696898, 1.37184, 2.01038, 2.6302, 2.53296, 1.8788, 0.100366, -1.34726, -1.4309, -1.37271, -0.750669, 0.100656)
c = c(0.749062, 0.0690315, -0.750494, -1.04069, -0.654432, 0.0186072, 0.710011, 0.920915, 1.13075, 0.227108, -0.195086, -0.68333, -0.607532, -0.485424, 0.495913, 0.655385, 0.468796, 0.274053, -0.906834 , 0.321526)
test = data.frame(count, a, b, c)
d = 20:40

这是我编写的代码块,用于检查我指定的任何数据并识别局部峰值,然后从识别的峰值计算一系列事物。它工作得非常好,并且它的功能没有问题(但是,欢迎提出使它更好的建议),只需将它与其余部分放在一起。 我想遍历数据框的列(在下一节中使用 for 循环来完成)并获取每列的 freq 函数的结果

freq = function(x, y, data, w=1, span = 0.05, ...) {
       require(zoo)
       n = length(y)
       y.smooth = loess(y ~ x, span = span)$fitted
       y.max = rollapply(zoo(y.smooth), 2*w+1, max, align = "center")
       delta = y.max - y.smooth[-c(1:w, n+1-1:w)]
       i.max = which(delta <= 0) + w #identifies peaks
       list(x = x[i.max], i = i.max, y.hat = y.smooth)
       dist = diff(i.max) #calculates distance between peaks
       instfreq = (25/dist) #calculates the rate of each peak occurence
       print(instfreq) #output I ultimately want
}

#example
freq(count, a, span = 0.5)

这就是我在指定数据框中循环遍历列的方式。另外,我不确定我做了什么,但这最终会打印我的输出两次......(我想避免)。

for(i in test){
    output <- freq(test$count, y = i, span = 0.5)
    print(output)
}

这可能是让我最头疼的部分。这应该将新列添加到现有数据框中。到目前为止它有效,但我还没有弄清楚如何将它集成到上面的东西中。另外,我真的很希望它将输出存储在一个新的数据帧中,而不是源数据帧中。

作为参考,这里 df = data,to.add = 要添加到 df 的数据,new.name = 新 col 的名称

我想要的另一件事是 new.name 来自源 (to.add)。例如,如果我尝试将 d (从上面)添加到测试的末尾,我希望列名(new.name)读取 d 而不必指定它。当我遍历多个列并希望保留计算输出的源名称时,这将很有帮助。

add.col = function(df, to.add, new.name){
  if (nrow(df) < length(to.add)){ 
    df =  # pads rows if needed
  rbind(df, matrix(NA, length(to.add)-nrow(df), ncol(df),
  dimnames = list(NULL, names(df))))
  }
  length(to.add) = nrow(df) # pads with NA's
  df[, new.name] = to.add; # names new col whatever was placed in new.name arg
  return(head(df)) #shortened output so I can verify it worked 
               #when I was testing it for myself, this would 
               #need to be changed so that it adds the column 
               #to a dataframe and stores the results, which 
               #I believe would require I use print() and a store
               #like Results = print(df)
}
#example
addcol(test, d, "d") #would like the code to grab the name d just from the to.add                   
 #argument, without having to specify "d" as the new.name

任何帮助、建议或改进(使其不那么笨重、更高效等)将不胜感激。 只要我能弄清楚如何将所有输出一起存储在一个地方,我就可以使用 for 循环(如果重复项得到修复)。我的实际数据的格式与上面的可重现集类似,只是有更多的行和列(并且已经在 .csv 数据帧中,而不是从单个向量创建)。

这几天我一直在努力解决这个问题,并且已经到了这么远,但就是无法完全理解。

此外,您还可以随意编辑标题,以帮助它找到合适的人!

【问题讨论】:

    标签: r function loops dataframe user-defined-functions


    【解决方案1】:

    好的,首先,您的函数打印输出两次的原因是因为基本上发生的情况是:

    • instfreq 被计算并返回
    • instfreq 被打印出来
    • instfreq 被分配给输出
    • 再次打印输出

    此外,我想您不希望您的函数尝试为 count 参数(返回 numeric(0))计算它,因此最好只为其他列运行它。 最后,这种简单的 for 循环可以很容易地被 r 中的 apply 函数替换。这将您问题的第一部分带到:

    freq = function(x, y, data, w=1, span = 0.05, ...) {
      require(zoo)
      n = length(y)
      y.smooth = loess(y ~ x, span = span)$fitted
      y.max = rollapply(zoo(y.smooth), 2*w+1, max, align = "center")
      delta = y.max - y.smooth[-c(1:w, n+1-1:w)]
      i.max = which(delta <= 0) + w #identifies peaks
      list(x = x[i.max], i = i.max, y.hat = y.smooth)
      dist = diff(i.max) #calculates distance between peaks
      instfreq = (25/dist) #calculates the rate of each peak occurence
      return(instfreq) #output I ultimately want
    }
    output <- apply(test[,2:length(test[1,])],2, function(v) freq(test$count, y=v, span=0.5))
    output
    #       a        b        c 
    #2.500000 3.571429 2.777778
    

    您问题的第二部分想要返回变量的名称以将其用作新列的名称。为此,我们可以使用 deparse(substitute(variable)) 使您的函数变为:

    add.col = function(df, to.add){
      new.name <- deparse(substitute(to.add))
      if (nrow(df) < length(to.add)){ 
        df =  # pads rows if needed
          rbind(df, matrix(NA, length(to.add)-nrow(df), ncol(df),
                           dimnames = list(NULL, names(df))))
      }
      length(to.add) = nrow(df) # pads with NA's
      df[, new.name] = to.add; # names new col whatever was placed in new.name arg
      return(df) 
    }
    #example
    dnametest = 20:40
    add.col(test, dnametest)
    #   count         a          b          c dnametest
    #1      1 -0.802776 -2.2351200  0.7490620        20
    #2      2 -0.748272 -1.3657200  0.0690315        21
    #etc.
    

    此函数将不会覆盖您的原始数据框,因此您只需将其分配给新的数据框:

    newframe <- add.col(test, dnametest)
    

    编辑添加循环 x 数量的数组的可能性:

    您在尝试循环时遇到的第一个问题是您正在处理不同长度的数组。这使得使用数据框变得困难,因此您必须使用列表。在这种情况下,编写一个接收任意数量的数组并自动为您循环它们的新函数会更容易。因为在这个函数中更容易捕捉和添加名称,我重新调整了你的函数 add.col 以再次使用 new.name:

    add.col = function(df, to.add, new.name){
      if (nrow(df) < length(to.add)){ 
        df =  # pads rows if needed
          rbind(df, matrix(NA, length(to.add)-nrow(df), ncol(df),
                           dimnames = list(NULL, names(df))))
      }
      length(to.add) = nrow(df) # pads with NA's
      df[, new.name] = to.add; 
      return((df)) 
    }
    

    然后我可以像这样编写第二个函数 add.multicol:

    #this function takes in an unspecfied number of arguments
    add.multicol <- function(df, ...){
      #convert this number of arguments to a list
      to.add.cols <- list(...)
      #add the variable names to this list
      names(to.add.cols) <- as.list(substitute(list(...)))[-1]
      #find number of columns to add
      number.cols.to.add <- length(to.add.cols)
      #loop add.col
      newframe <- df
      for(i in 1:number.cols.to.add){
        to.add.col <- array(unlist(to.add.cols[i]))
        to.add.col.name <- names(to.add.cols[i])
        newframe <- add.col(newframe,to.add.col,to.add.col.name)
      }
      return(newframe)
    }
    

    这将允许你做任何你想做的事。示例:

    dnametest <- 20:40
    test1 <- 1:15
    test2 <- 25:56
    argumentsake <- seq(0,1,length=21)
    #run function
    newframe <- add.multicol(test,dnametest,test1,test2,argumentsake)
    newframe
    #   count         a          b          c dnametest test1 test2 argumentsake
    #1      1 -0.802776 -2.2351200  0.7490620        20     1    25         0.00
    #2      2 -0.748272 -1.3657200  0.0690315        21     2    26         0.05
    #3      3  0.187434 -0.0357366 -0.7504940        22     3    27         0.10
    #4      4  1.235770  0.9255630 -1.0406900        23     4    28         0.15
    #5      5  1.006770  1.5328200 -0.6544320        24     5    29         0.20
    #6      6  0.874122  0.1710450  0.0186072        25     6    30         0.25
    #7      7  0.232802 -0.4387140  0.7100110        26     7    31         0.30
    #8      8 -0.279368 -1.3876900  0.9209150        27     8    32         0.35
    #9      9 -1.578150 -0.6968980  1.1307500        28     9    33         0.40
    #10    10 -1.766520  1.3718400  0.2271080        29    10    34         0.45
    #11    11 -0.958916  2.0103800 -0.1950860        30    11    35         0.50
    #12    12 -0.316385  2.6302000 -0.6833300        31    12    36         0.55
    #13    13  0.831575  2.5329600 -0.6075320        32    13    37         0.60
    #14    14  1.193120  1.8788000 -0.4854240        33    14    38         0.65
    #15    15  1.455080  0.1003660  0.4959130        34    15    39         0.70
    #16    16  0.848923 -1.3472600  0.6553850        35    NA    40         0.75
    #17    17  0.257728 -1.4309000  0.4687960        36    NA    41         0.80
    #18    18 -0.318474 -1.3727100  0.2740530        37    NA    42         0.85
    #19    19 -1.141290 -0.7506690 -0.9068340        38    NA    43         0.90
    #20    20 -1.425760  0.1006560  0.3215260        39    NA    44         0.95
    #21    NA        NA         NA         NA        40    NA    45         1.00
    #22    NA        NA         NA         NA        NA    NA    46           NA
    #23    NA        NA         NA         NA        NA    NA    47           NA
    #24    NA        NA         NA         NA        NA    NA    48           NA
    #25    NA        NA         NA         NA        NA    NA    49           NA
    #26    NA        NA         NA         NA        NA    NA    50           NA
    #27    NA        NA         NA         NA        NA    NA    51           NA
    #28    NA        NA         NA         NA        NA    NA    52           NA
    #29    NA        NA         NA         NA        NA    NA    53           NA
    #30    NA        NA         NA         NA        NA    NA    54           NA
    #31    NA        NA         NA         NA        NA    NA    55           NA
    #32    NA        NA         NA         NA        NA    NA    56           NA
    

    编辑 2:扩展循环以接收任何形式的数据帧

    现在它变得非常混乱,您还需要重命名输出元素,使它们与已经存在的任何列名称不匹配。

    add.multicol <- function(df, ...){
      #convert this number of arguments to a list
      to.add.cols <- list(...)
      #find number of columns to add
      number.args <- length(to.add.cols)
      #number of elements per list entry
      hierarch.cols.to.add <- array(0,length(number.args))
      for(i in 1:number.args){
        #if this list element has only one name, treat it as an array, else treat it as a data frame
        if(is.null(names(to.add.cols[[i]]))){
          #get variable names from input of normal arrays
          names(to.add.cols[[i]]) <- as.list(substitute(list(...)))[i+1]
          hierarch.cols.to.add[i] <- 1
        } else {
          #find the number of columns in the data frame
          number <- length(names(to.add.cols[[i]]))
          hierarch.cols.to.add[i] <- number
        }
      }
      #loop add.col
      newframe <- df
      for(i in 1:number.args){
        #if array
        if(hierarch.cols.to.add[i]==1){
          to.add.col <- array(unlist(to.add.cols[[i]]))
          to.add.col.name <- names(to.add.cols[[i]][1])
          newframe <- add.col(newframe,to.add.col,to.add.col.name)
        } else { #if data.frame
          #foreach column in the data frame
          for(j in 1:hierarch.cols.to.add[i]){
            #if only one element per column
            if(is.null(dim(to.add.cols[[i]]))){
              to.add.col <- to.add.cols[[i]][j]
            } else { #if multiple elements per column
              to.add.col <- to.add.cols[[i]][,j]
            }
            to.add.col.name <- names(to.add.cols[[i]])[j]
            newframe <- add.col(newframe,to.add.col,to.add.col.name)
          }
        }
      }
      return(newframe)
    }
    testdf <- data.frame(cbind(test1,test2))
    dnametest <- 20:40
    output <- apply(test[,2:length(test[1,])],2, function(v) freq(test$count, y=v, span=0.5))
    #edit output names because we can't have a dataframe with the same name for multiple columns
    names(output) <- c("output_a","output_b","output_c")
    newframe <- test
    #function now takes dataframes of single elements, normal data frames and single arrays
    newframe <- add.multicol(newframe,output,dnametest,testdf)
    #   count         a          b          c output_a output_b output_c dnametest test1 test2
    #1      1 -0.802776 -2.2351200  0.7490620      2.5 3.571429 2.777778        20     0    25
    #2      2 -0.748272 -1.3657200  0.0690315       NA       NA       NA        21     1    26
    #3      3  0.187434 -0.0357366 -0.7504940       NA       NA       NA        22     2    27
    #4      4  1.235770  0.9255630 -1.0406900       NA       NA       NA        23     3    28
    #...
    

    这可能不是最有效的方法,但它可以完成工作

    【讨论】:

    • 几乎完全可以工作,只是它不喜欢将不同长度的列添加到新的数据帧中,所以我的 rbind 参数在添加中一定存在一些问题。 col 函数。如果我只是尝试一次添加一列,但当我尝试将它与循环一起使用时会出错:new.frame = add.col(new.frame, output) Error in [&lt;-.data.frame(@987654329 @, , new.name, value = list(trace = c(2.5, : 替换元素 1 有 12 行,需要 20
    • 如果通过一次添加一个来工作,错误可能在于您循环的方式,而不是函数本身。你问的事情不是一件小事,所以我编辑了我的答案,包括循环的可能性。
    • "首先,函数打印输出两次的原因是因为没有显式返回语句的函数返回它计算的最后一个值。" print,它打印到控制台(并希望返回它,所以分配工作)。 return 仅在您想在函数结束之前退出函数时才需要(例如在 if 语句中)
    • 因此,这非常适合将多个指定的列添加到数据框中,但我的最终目标是能够使用newframe = add.multicol(newframe, output),并且现在,当我这样做时,它会将输出从b 和 c 放在一个单独的列中,而不是三个单独的列,每个列标记为“a”、“b”、“c”。有没有办法将它与输出循环更好地集成?很抱歉提出如此具体的问题。
    • 我不确定我是否完全理解您的目标。在这种情况下,输出参数是什么? freq 函数的输出?
    猜你喜欢
    • 2014-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-11
    • 2021-11-11
    • 2021-10-30
    • 1970-01-01
    相关资源
    最近更新 更多