如何使 R 中的嵌套 for 循环更有效地将输出写入数据帧?
How to make nested for loop in R writing output to dataframe more efficient?
我是 R 和 Whosebug 菜鸟 - 如果问题不合适或结构不合理,请原谅。
我正在尝试编写一些 R 代码以将 nrow x ncol table/dataframe 转换为数据帧,每行包括:行号、列号、来自列 j[=30 的值=], 行 i 原来 table/dataframe.
我有许多 tables/dataframes 想做类似的事情,每个都有不同的行数、列数...
因此,在此示例中,我有一个 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
我们在这里做什么?
首先,我们将“rownames”作为列添加到数据中(因为出于某种原因,您希望将它们保留在生成的数据框中。
然后,我们使用 tidyr
包中的 pivot_longer()
函数。你想对数据做的是重塑。在 R 中有很多这样做的可能性,(reshape()
,reshape2
库,或来自 tidyr
.[=26= 的函数 pivot_longer()
,pivot_wider()
]
我们希望以“长”形式获取“宽”数据(您可能想看看 this Cheat Sheet,即使函数 gather()
和 spread()
是被 pivot_longer()
和 pivot_wider()
取代,但它们的功能基本相同。
使用函数参数 cols = -id
,我们指定除 id
之外的所有变量都应出现在新数据框的值列中。
如果您想要一个矩阵作为结果,只需在新创建的对象上 运行 as.matrix()
。
基础 R 解决方案:
data.frame(c(t(df)))
如果我们想知道原来这个值属于哪个V向量data.frame:
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)
根据上面@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 解决方案添加了 'full' 解决方案
##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 和 Whosebug 菜鸟 - 如果问题不合适或结构不合理,请原谅。
我正在尝试编写一些 R 代码以将 nrow x ncol table/dataframe 转换为数据帧,每行包括:行号、列号、来自列 j[=30 的值=], 行 i 原来 table/dataframe.
我有许多 tables/dataframes 想做类似的事情,每个都有不同的行数、列数...
因此,在此示例中,我有一个 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
我们在这里做什么?
首先,我们将“rownames”作为列添加到数据中(因为出于某种原因,您希望将它们保留在生成的数据框中。
然后,我们使用 tidyr
包中的 pivot_longer()
函数。你想对数据做的是重塑。在 R 中有很多这样做的可能性,(reshape()
,reshape2
库,或来自 tidyr
.[=26= 的函数 pivot_longer()
,pivot_wider()
]
我们希望以“长”形式获取“宽”数据(您可能想看看 this Cheat Sheet,即使函数 gather()
和 spread()
是被 pivot_longer()
和 pivot_wider()
取代,但它们的功能基本相同。
使用函数参数 cols = -id
,我们指定除 id
之外的所有变量都应出现在新数据框的值列中。
如果您想要一个矩阵作为结果,只需在新创建的对象上 运行 as.matrix()
。
基础 R 解决方案:
data.frame(c(t(df)))
如果我们想知道原来这个值属于哪个V向量data.frame:
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)
根据上面@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 解决方案添加了 'full' 解决方案
##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))