Как сделать вложенный цикл for в R более эффективным для записи вывода в фрейм данных?

Я нуб 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 и кажется довольно неэлегантным. В частности, создание промежуточных векторов, а затем назначение их столбцам выходного фрейма данных кажется немного громоздким, но я не мог заставить его работать, пытаясь поместить значения прямо в столбцы моего выходного фрейма данных.

Любые мысли о том, как улучшить код, будут очень кстати.

Спасибо заранее...

Подобно ответу Бена, вы можете сделать table %>% mutate(row = row_number()) %>% pivot_longer(cols = -row), используя dplyr и tidyr.

Ronak Shah 22.12.2020 13:29

@RonakShah Возможно, вы захотите добавить library(tidyverse) /edit: устарело после ваших правок

Ben 22.12.2020 13:30

Это распространенная «ошибка» при изучении R, и часто она возникает из-за убеждения, что data.frames в R ведут себя аналогично двумерным массивам или таблицам в других языках. Это не так. Очень редко цикл for является наиболее эффективным способом изменения данных в кадре, и (почти) никогда не бывает хорошей итеративной итеративной последовательностью увеличения кадра.

r2evans 22.12.2020 13:35

Спасибо @RonakShah за самое простое решение! Спасибо also@r2evans за полезные комментарии. Очень ценю поддержку сообщества.

Braveheart1971 22.12.2020 23:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
1 507
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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

Что мы здесь делаем?

Прежде всего, мы добавляем «имена строк» ​​в качестве столбца к данным (потому что по какой-то причине вы хотите сохранить их в результирующем фрейме данных. Затем мы используем функцию pivot_longer() из пакета tidyr. Что вы хотите сделать с данными, так это изменить форму. В R есть много возможностей сделать это (reshape(), библиотека reshape2 или функции pivot_longer(), pivot_wider() из tidyr.

Мы хотим, чтобы наши «широкие» данные были в «длинной» форме (вы можете взглянуть на эту шпаргалку, хотя функции gather() и spread() заменены pivot_longer() и pivot_wider(), но в основном они работают в так же.

С помощью аргумента функции cols = -id мы указываем, что все переменные, кроме id, должны появиться в столбце значений нового фрейма данных.

Если вы хотите получить матрицу в качестве результата, просто запустите as.matrix() на только что созданном объекте.

Большое спасибо @Бен. Я принял ваш ответ, поскольку он интуитивно мне понятен, и вы предоставили подробное объяснение. Большое спасибо, что нашли время.

Braveheart1971 22.12.2020 22:56

Базовое решение 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 22.12.2020 13:35

@ r2evans Правда, он был основан только на примерных данных, предоставленных OP, но я понимаю, что он может быть недостаточно общим для фактических данных, к которым OP хочет его применить. reshape() Было бы лучшим выбором в этом случае.

hello_friend 22.12.2020 13:37

Спасибо @hello_friend Мне придется поработать с вашим решением, чтобы полностью понять, что оно делает, но приятно иметь и базовое решение R. Спасибо.

Braveheart1971 22.12.2020 23:03

@Braveheart11971, пожалуйста, проголосуйте за мой ответ, если он был вам полезен.

hello_friend 22.12.2020 23:55

Привет, @hello_friend, я проголосовал за твой ответ. Однако при внимательном рассмотрении я обнаружил, что он делает не совсем то, что мне нужно (поскольку index фактически просто воссоздает var, а не индексирует номера строк. Коды в моем ответе ниже, основанные на предложенном вами решении, работают. Было бы заинтересованы в ваших комментариях, чтобы уменьшить количество шагов, упростить, если это возможно.

Braveheart1971 29.12.2020 07:54

@Braveheart1971 Спасибо за голосование. Я не уверен, что полностью понимаю ваш вопрос. Насколько я понимаю, у вас есть проблема с тем, как я перестроил то, что упоминается в решении @Ben как вектор name. Пожалуйста, ознакомьтесь с двумя дополнительными решениями выше (которые я добавил в качестве редактирования с сегодняшней датой).

hello_friend 29.12.2020 09:44

@hello_friend спасибо за повторный визит. Ваше решение для преобразования теперь делает именно то, что мне нужно, с небольшим добавлением as.numeric() к созданию идентификатора, чтобы облегчить сортировку.

Braveheart1971 01.01.2021 03:17

@hello_friend Я добавил: df_3 <- df_2[with(df_2, order(name, -id)), ] чтобы получить результат в нужной мне окончательной форме. Единственная часть кода, с которой я боролся, — это функция ave() — я думаю, что имя меня сбивает с толку. По сути, код создает подмножества имени, сгруппированные по имени, и создает последовательность, увеличивающуюся для каждой подгруппы имени? Еще раз спасибо, я многому научился, работая над этим!

Braveheart1971 01.01.2021 03:31

@Braveheart1971 да, ave() назначает счетчик на основе значений идентификатора. Вы можете попробовать объединить свою функцию заказа и мою, чтобы немного сократить код. С Новым годом вас и ваших!

hello_friend 01.01.2021 11:21

Основываясь на предложенном выше ответе @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))  

Другие вопросы по теме