【问题标题】:How to make nested for loop in R writing output to dataframe more efficient?如何使R中的嵌套for循环将输出写入数据帧更有效?
【发布时间】:2020-12-22 12:10:05
【问题描述】:

我是 R 和 stackoverflow 菜鸟 - 如果问题不恰当或结构不合理,请原谅。

我正在尝试编写一些 R 代码来将 nrow x ncol 表/数据帧转换为数据帧,每行包括:行号、列号、列 j 中的值、行 i 的原始表/数据框。

我有许多表/数据框,我想用类似的方式处理它们,每个表/数据框都有不同的行数、列数...

因此,在本例中,我有一个 6 行 x 9 列的数据框,我想将其转换为 54 行的数据框:

#create example data
values <- rnorm(54, mean = 75, sd=3)
table_m <- matrix(values, ncol=9)
table <- as.data.frame(table_m)

我目前的代码如下:

##count rows and columns
nrows <- nrow(table)
ncols <- ncol(table)

#set up empty matrix for output
iterations <- nrows * ncols 
variables <-   3
output <- matrix(ncol=variables, nrow=iterations)

#set up first empty vector
my_vector_1 = c()

#run first nested for loop to create sequence of nrow * copies of column numbers
for (j in 1:ncol(table)) 
  for (i in 1:nrow(table))
  {
    my_vector_1[length(my_vector_1)+1] = colnames(table)[j]
  }

# add to first column of output
output[,1] <- my_vector_1

# set up second empty vector
my_vector_2 = c()

#run second nested for loop to create sequence of ncol * copies of row numbers
for (j in 1:ncol(table)) 
  for (i in 1:nrow(table))
  {
    my_vector_2[length(my_vector_2)+1] = rownames(table)[i]
}

# add to second column of output
output[,2] <- my_vector_2

#create third empty vector
my_vector_3 = c()

#run third nested for loop to pull values from original table/dataframe
for (j in 1:ncol(table)) 
  for (i in 1:nrow(table))
  {
    my_vector_3[length(my_vector_3)+1] = table[i,j]
  }

output[,3] <- my_vector_3

所以,这段代码可以正常工作,并且可以满足我的需求……但在我的菜鸟状态下,它是通过大量谷歌搜索拼凑而成的,看起来很不雅。特别是,创建中间向量,然后将它们分配给输出数据帧列似乎有点麻烦 - 但我无法让它工作试图将值直接放入输出数据帧的列中。

非常欢迎任何关于如何改进代码的想法。

提前非常感谢...

【问题讨论】:

  • 与 Ben 的回答类似,您可以使用 table %&gt;% mutate(row = row_number()) %&gt;% pivot_longer(cols = -row)dplyrtidyr
  • @RonakShah 您可能想在编辑后添加library(tidyverse) /edit: obsolete
  • 这是学习 R 时常见的“错误”,通常是因为认为 R 中的 data.frames 的行为类似于其他语言中的二维数组或表。它没有。 for 循环很少是更改帧中数据的最有效方法,并且(几乎)迭代地逐行增长帧永远不是一个好主意。
  • 感谢@RonakShah 提供最简单的解决方案!也感谢@r2evans 提供帮助的 cmets。非常感谢社区的支持。

标签: r loops for-loop coding-efficiency


【解决方案1】:

这是一个很好的方法,但它当然可以用更短的方式。 试试:

table$id <- 1:nrow(table) # Create a row no. column
tidyr::pivot_longer(table, cols = -id)
# A tibble: 54 x 3
      id name  value
   <int> <chr> <dbl>
 1     1 V1     70.3
 2     1 V2     72.8
 3     1 V3     76.1
 4     1 V4     73.1
 5     1 V5     71.9
 6     1 V6     73.8
 7     1 V7     76.4
 8     1 V8     74.1
 9     1 V9     75.5
10     2 V1     73.8
# ... with 44 more rows

我们在这里做什么?

首先,我们将“行名”作为列添加到数据中(因为出于某种原因,您希望将它们保留在结果数据框中。 然后,我们使用 tidyr 包中的 pivot_longer() 函数。您想要对数据执行的操作是重塑。在 R 中有很多这样的可能性,(reshape()reshape2 库,或来自tidyr 的函数pivot_longer()pivot_wider()

我们希望我们的“宽”数据采用“长”形式(您可能想看看this Cheat Sheet,即使函数gather()spread() 已被pivot_longer() 和@987654333 取代@,但它们的功能基本相同。

使用函数参数cols = -id,我们指定除id 之外的所有变量都应出现在新数据框的值列中。

如果您想要一个矩阵作为结果,只需在新创建的对象上运行 as.matrix()

【讨论】:

  • 非常感谢@Ben。我已经接受了您的回答,因为这是我直观理解的答案,并且您提供了详尽的解释。非常感谢您抽出宝贵时间。
【解决方案2】:

根据上面@hello_friend 的建议答案,我能够在base R 中提出这个解决方案:

##Set up example data
values <- rnorm(54, mean = 75, sd=3)
table_m <- matrix(values, ncol=9)
df <- as.data.frame(table_m)

##Create intermediate vectors
total_length <- nrow(df)*ncol(df)
statment_count <- rep(seq_along(1:nrow(df)),each =ncol(df), length.out=total_length)
site_count <- rep(seq_along(1:ncol(df)),length.out=total_length)
value = c(t(df))

##join vectors into data frame
output <- data.frame(site = site_count, 
                     statement = statment_count,
                     value = value  
                     )

##sort output                    
output <- output[with(output, order(site, -statement)), ]

这肯定比我最初使用的一系列 for 循环更简单、更直观。希望这会帮助正在寻找类似问题的基本 R 解决方案的其他人。

此外,为了完整起见,为@Ben 和@Ronak Shah 提出的 tidyverse 解决方案添加了“完整”解决方案

##Set up example data
values <- rnorm(54, mean = 75, sd=3)
table_m <- matrix(values, ncol=9)
table <- as.data.frame(table_m)

output_2 <- table %>% 
            mutate(statement = row_number()) %>%
            pivot_longer(cols = -statement)%>%
            rename(site = name)%>%
            relocate(site) %>%
            mutate(site = as.numeric(gsub("V", "", site))) %>%
            arrange(site, desc(statement))  

【讨论】:

    【解决方案3】:

    基础 R 解决方案:

    data.frame(c(t(df)))
    

    如果我们想知道原始data.frame中的值属于哪个V向量:

    data.frame(var = paste0("V", seq_along(df)), val = c(t(df)))
    

    并且还包括行索引:

    transform(data.frame(var = paste0("V", seq_along(df)), val = c(t(df)), stringsAsFactors = F),
              idx = ave(var, var, FUN = seq.int))
    

    更强大的解决方案(根据@r2evans 推理):

    transform(data.frame(var = names(df), val = do.call("c", df), 
      stringsAsFactors = FALSE, row.names = NULL), idx = ave(var, var, FUN = seq.int))
    

    使用stack() 的另一个更强大的解决方案:

    transform(data.frame(stack(df), stringsAsFactors = FALSE, row.names = NULL),
              idx = ave(as.character(ind), ind, FUN = seq.int))
    

    29/12/2020 编辑: 强大的解决方案镜像@Ben,但在 Base R 中:

    transform(data.frame(name = as.character(rep(names(df), nrow(df))), value = c(t(df)),
      stringsAsFactors = FALSE), id = ave(name, name, FUN = seq.int))
    

    最直接的 Base R 解决方案(模仿 Ben 的回答):

    # Flatten the data.frame: 
    stacked_df <- setNames(within(stack(df), {
      # Coerce index to character type (to enable counting):
      ind <- as.character(ind)
      # Issue a count to each ind element: 
      id <- ave(ind, ind, FUN = seq.int)
      }
      # Rename the data.frame's vector match Ben's accepted solution:
    ), c("value", "name", "id"))
    
    # Order the data.frame as in Ben's answer: 
    ordered_df <- with(stacked_df, stacked_df[order(id), c("id", "name", "value")])
    

    数据:

    values <- rnorm(54, mean = 75, sd=3)
    table_m <- matrix(values, ncol=9)
    df <- as.data.frame(table_m)
    

    【讨论】:

    • 这仅适用于框架中的所有数据属于同一类(例如,numericcharacter),并假定没有其他列(即枢轴/重塑会保留)。
    • @r2evans 没错,它仅基于 OP 提供的示例数据,但我知道它对于 OP 想要应用的实际数据可能不够通用。 reshape() 在这种情况下会是更好的选择。
    • 谢谢@hello_friend 我必须仔细研究你的解决方案才能完全理解它在做什么,但也很高兴有一个基本的 R 解决方案。非常感谢。
    • @hello_friend 感谢您再次访问。您的转换解决方案现在完全符合我的需要,只是在创建 id 时稍微添加了 as.numeric() - 以便更轻松地进行排序。
    • @hello_friend 我添加了:df_3 name 的子集,按 name 分组,并创建一个序列,为 name 的每个子组递增?再次感谢,我从这次工作中学到了很多东西!
    猜你喜欢
    • 1970-01-01
    • 2020-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-05
    • 2021-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多