Есть ли быстрый способ изменить согласованные компоненты вложенного списка в R?

У меня есть вектор значений, который я хочу использовать для изменения согласованного компонента вложенного списка. В настоящее время я использую для этого цикл for, который оказывается довольно медленным, и мне интересно, есть ли более быстрый способ сделать это.

В качестве воспроизводимого примера:

#Create Data and List
set.seed(100)
Students <- c("Amy", "Ben", "Caz")
Subject <- c("Maths", "English", "Science")

ExamResults <- lapply(Students, function (r) {
  Scores <- lapply(Subject, function(x) round(runif(4, 0, 100)))
  names(Scores) <- Subject
  Scores
})
names(ExamResults) <- Students

Что создает список, который выглядит следующим образом:

$Amy
$Amy$Maths
[1] 31 26 55  6

$Amy$English
[1] 47 48 81 37

$Amy$Science
[1] 55 17 62 88

$Ben
$Ben$Maths
[1] 28 40 76 67

$Ben$English
[1] 20 36 36 69

$Ben$Science
[1] 54 71 54 75

$Caz
$Caz$Maths
[1] 42 17 77 88

$Caz$English
[1] 55 28 49 93

$Caz$Science
[1] 35 95 70 89

Теперь предположим, что вторая работа по математике для всех учащихся отмечена:

#New Data
MathsRemark <- c(24, 50, 45) #assume in correct (alphabetical) order

И мы хотим изменить их существующую вторую оценку по математике для этих новых значений. Мое текущее решение для этого:

#Current Slow Solution
for (i in 1:length(Students)) {
  ExamResults[[i]][["Maths"]][[2]] <- MathsRemark[i]
}

На больших наборах данных это оказывается очень медленным. Кто-нибудь знает более быстрый способ сделать это? Меня учили избегать использования циклов for там, где возможна векторизация, но я не могу понять, как векторизовать эту проблему. Любые идеи будут высоко оценены.


person Will T-E    schedule 14.01.2019    source источник
comment
Этот вопрос может помочь stackoverflow.com/questions/31950336/   -  person niko    schedule 14.01.2019


Ответы (3)


Один из вариантов Map().

Map(function(x, y) { x$Maths[2] <- y; x }, ExamResults, MathsRemark)

Второй вариант — исключить из списка, заменить и повторно добавить в список.

u <- unlist(ExamResults)
relist(replace(u, endsWith(names(u), "Maths2"), MathsRemark), ExamResults)
person Rich Scriven    schedule 14.01.2019
comment
Спасибо, Рич, к сожалению, ни один из них не работает быстрее. Решение Map() кажется быстрее только с небольшими списками. - person Will T-E; 15.01.2019

Я хочу отметить, что в следующем выпуске purrr будет assign_in, что позволит очень элегантно выразить ваше решение как:

library( purrr )
map2( ExamResults, MathsRemark, assign_in, where=list("Maths",2) )

Однако это не так быстро, как решение @Rich Scriven:

microbenchmark::microbenchmark(
    f0 = for (i in 1:length(Students))
         {ExamResults[[i]][["Maths"]][[2]] <- MathsRemark[i]},
    f1 = map2( ExamResults, MathsRemark, assign_in, where=list("Maths",2) ),
    f2 = Map(function(x, y) { x$Maths[2] <- y; x }, ExamResults, MathsRemark)
)
# Unit: microseconds
# expr      min        lq       mean    median       uq      max neval
#   f0 2148.158 2258.7700 2580.87901 2333.9455 2741.233 4550.022   100
#   f1  180.081  206.7990  247.42790  235.8160  254.058 1310.912   100
#   f2    9.410   14.7515   33.05673   18.8505   19.943 1558.125   100
person Artem Sokolov    schedule 14.01.2019
comment
Спасибо за это, Артем, есть идеи, когда будет доступен следующий выпуск purrr? К сожалению, функция Map() работает значительно медленнее в больших списках. В качестве примера я получаю: f0 293.370 330.5755 2225.629 567.9235 3819.797 10960.75 100 f2 3511.032 3709.6050 6312.285 3860.7090 4333.693 130174.74 100 - person Will T-E; 15.01.2019
comment
Привет @WillT-E. Я не уверен, каковы сроки выхода релиза на CRAN, но вы можете установить его прямо сейчас прямо с GitHub: github.com/tidyverse/purrr - person Artem Sokolov; 15.01.2019

Возможно, вам лучше превратить исходный список в фреймворк данных, а затем изменить его. В этом может помочь map из пакета purrr.

library(tidyverse)

extract_subject_df<- function(specific_subject){
# Outputs a dataframe for a specific subject
    ExamResults %>% 
        map(specific_subject) %>%
        data.frame() %>%
        mutate(specific_subject=specific_subject)
}

new_df <- map_df(Subject, extract_subject_df)

new_df[new_df$specific_subject == "Maths"][2, 1:3] <- c(24, 50, 45)
person Charlie    schedule 14.01.2019