Я нуб R и stackoverflow, поэтому, пожалуйста, простите, если вопрос не подходит или не очень хорошо структурирован.
Я пытаюсь написать некоторый код R для преобразования таблицы/фрейма данных nrow x ncol в фрейм данных, где каждая строка содержит: RowNumber, номер столбца, значение из столбца j, строку i исходной таблицы/фрейма данных.
У меня есть несколько таблиц/фреймов данных, с которыми я хочу поступить аналогичным образом, каждая из которых имеет разное количество строк, столбцов...
Итак, в этом примере у меня есть фрейм данных 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
Итак, этот код работает и делает то, что мне нужно... но в моем состоянии нуба он был собран из большого количества Google и кажется довольно неэлегантным. В частности, создание промежуточных векторов, а затем назначение их столбцам выходного фрейма данных кажется немного громоздким, но я не мог заставить его работать, пытаясь поместить значения прямо в столбцы моего выходного фрейма данных.
Любые мысли о том, как улучшить код, будут очень кстати.
Спасибо заранее...
@RonakShah Возможно, вы захотите добавить library(tidyverse)
/edit: устарело после ваших правок
Это распространенная «ошибка» при изучении R, и часто она возникает из-за убеждения, что data.frame
s в R ведут себя аналогично двумерным массивам или таблицам в других языках. Это не так. Очень редко цикл for
является наиболее эффективным способом изменения данных в кадре, и (почти) никогда не бывает хорошей итеративной итеративной последовательностью увеличения кадра.
Спасибо @RonakShah за самое простое решение! Спасибо also@r2evans за полезные комментарии. Очень ценю поддержку сообщества.
это хороший способ сделать это, но это, безусловно, возможно и более коротким способом. Пытаться:
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
Что мы здесь делаем?
Прежде всего, мы добавляем «имена строк» в качестве столбца к данным (потому что по какой-то причине вы хотите сохранить их в результирующем фрейме данных.
Затем мы используем функцию pivot_longer()
из пакета tidyr
. Что вы хотите сделать с данными, так это изменить форму. В R есть много возможностей сделать это (reshape()
, библиотека reshape2
или функции pivot_longer()
, pivot_wider()
из tidyr
.
Мы хотим, чтобы наши «широкие» данные были в «длинной» форме (вы можете взглянуть на эту шпаргалку, хотя функции 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'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))
Самое простое решение Base R (отражающее ответ Бена):
# 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)
Это работает только до тех пор, пока все данные во фрейме относятся к одному классу (например, numeric
или character
) и предполагают, что нет дополнительных столбцов (которые сохранялись бы при повороте/изменении формы).
@ r2evans Правда, он был основан только на примерных данных, предоставленных OP, но я понимаю, что он может быть недостаточно общим для фактических данных, к которым OP хочет его применить. reshape()
Было бы лучшим выбором в этом случае.
Спасибо @hello_friend Мне придется поработать с вашим решением, чтобы полностью понять, что оно делает, но приятно иметь и базовое решение R. Спасибо.
@Braveheart11971, пожалуйста, проголосуйте за мой ответ, если он был вам полезен.
Привет, @hello_friend, я проголосовал за твой ответ. Однако при внимательном рассмотрении я обнаружил, что он делает не совсем то, что мне нужно (поскольку index фактически просто воссоздает var, а не индексирует номера строк. Коды в моем ответе ниже, основанные на предложенном вами решении, работают. Было бы заинтересованы в ваших комментариях, чтобы уменьшить количество шагов, упростить, если это возможно.
@Braveheart1971 Спасибо за голосование. Я не уверен, что полностью понимаю ваш вопрос. Насколько я понимаю, у вас есть проблема с тем, как я перестроил то, что упоминается в решении @Ben как вектор name
. Пожалуйста, ознакомьтесь с двумя дополнительными решениями выше (которые я добавил в качестве редактирования с сегодняшней датой).
@hello_friend спасибо за повторный визит. Ваше решение для преобразования теперь делает именно то, что мне нужно, с небольшим добавлением as.numeric() к созданию идентификатора, чтобы облегчить сортировку.
@hello_friend Я добавил: df_3 <- df_2[with(df_2, order(name, -id)), ] чтобы получить результат в нужной мне окончательной форме. Единственная часть кода, с которой я боролся, — это функция ave() — я думаю, что имя меня сбивает с толку. По сути, код создает подмножества имени, сгруппированные по имени, и создает последовательность, увеличивающуюся для каждой подгруппы имени? Еще раз спасибо, я многому научился, работая над этим!
@Braveheart1971 да, ave() назначает счетчик на основе значений идентификатора. Вы можете попробовать объединить свою функцию заказа и мою, чтобы немного сократить код. С Новым годом вас и ваших!
Основываясь на предложенном выше ответе @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 для аналогичной проблемы.
Кроме того, для полноты добавлено «полное» решение для решения tidyverse, предложенное @Ben и @Ronak Shah.
##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))
Подобно ответу Бена, вы можете сделать
table %>% mutate(row = row_number()) %>% pivot_longer(cols = -row)
, используяdplyr
иtidyr
.