如何使R中的嵌套for循环更高效地将输出写入数据帧



我是一个R和stackoverflow noob,所以如果这个问题不合适,或者结构不好,请原谅。

我正试图编写一些R代码,将一个nrow x ncol表/数据帧转换为一个数据帧,每行包括:原始表/数据框架的RowNumber、Column Number、Value from Columnj、rowI

我有很多表/数据帧,我想对它们进行类似的处理,每个表都有不同数量的行、列。。。

在这个例子中,我有一个6行乘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

所以,这个代码是有效的,并且做了我需要的。。。但在我的状态下,它是从谷歌上大量搜索拼凑而成的,看起来很不雅。特别是,创建中间向量,然后将它们分配给输出数据帧列似乎有点麻烦,但我无法将值直接放入输出数据帧的列中。

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

非常感谢。。。

这是一种很好的方法,但肯定可以用更短的方式。尝试:

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()中有很多这样做的可能性

我们希望拥有我们的";宽";数据在";"长";表单(你可能想看看这张备忘单,尽管函数gather()spread()pivot_longer()pivot_wider()取代了,但它们的作用基本相同

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

如果您希望得到一个矩阵作为结果,只需对新创建的对象运行as.matrix()即可。

基于@hello_friend上面建议的答案,我能够在基本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))  

基本R解决方案:

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

如果我们想知道原始数据中的值属于哪个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))

2020年12月29日编辑:强大的解决方案镜像@Ben’s,但在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))

最直接的BaseR解决方案(反映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)

最新更新