2 votes

Exécuter de nombreuses simulations en évitant la boucle for

Je tente de simuler des valeurs ajustées pour des modèles changeants de différents sous-groupes de mes données, qui sont à nouveau basés sur un échantillonnage aléatoire d'un autre sous-ensemble de mon cadre de données original (l'exemple minimal que j'ai créé pour cette question ignore l'échantillonnage aléatoire, etc., ce qui donne des valeurs ajustées identiques pour toutes les simulations, mais cela n'a pas d'importance). J'ai écrit un code dplyr pour stocker le modèle pour chaque groupe, produire de nouvelles valeurs x pour prédire les valeurs ajustées, les prédire etc. etc. Cela donne une colonne de valeurs ajustées, exactement comme je le veux. Cependant, j'aimerais parcourir tout le processus 1000 fois. Je peux bien sûr le faire avec une boucle for (comme c'est fait ci-dessous dans l'exemple), mais y a-t-il une possibilité de faire cela dans une ligne de traitement dplyr ? Et peut-être accélérer un peu tout le processus (mon jeu de données original est plutôt grand et la boucle for prend une éternité) ?

# making up data
dat <- data.frame("species" =  seq(1:20), "col_A" = runif(20, min=1000, max=2500), "col_B" = runif(20, min = 0, max = 1500), 
                  "maximum" = rep(2500, 20), "minimum" = rep(1000, 20), groups = rep(LETTERS[1:5], each = 4))

# functions to use with purrr
linear_mod <- function(dat) {
  lm(col_A ~ col_B, data = dat)
}

# define parametres and an empty data frame to use in the for loop
runs <- 10
fitted_sim <- data.frame(matrix(data=NA,nrow=20,ncol=runs+1,byrow=FALSE)) #empty dataframe to contain fitted values for each alt
names(fitted_sim) <- as.factor(seq(1:runs+1))

# for-loop around my dplyr-code
for (j in 1:runs){
  simul <- dat %>%
    group_by(groups) %>%
    nest(data = c(col_A, col_B, species)) %>%
    mutate(model = map(data, linear_mod), # add model for every group
           sim_data = list(seq(minimum, maximum, by = 10))) %>% # define new x-values for later predictions
    unnest(sim_data) %>%
    nest(sim_data = sim_data) %>%
    mutate(fitted = map2(model, sim_data, ~predict(.x, col_B = sim_data, type = "response")), # predict values
    unnest(fitted) # unnest predicted values to save in data frame

  # save newly fitted values in fitted_sim data frame
  fitted_sim[,1] <- simul$groups
  fitted_sim[,1+j] <- simul$fitted
}

Merci pour chaque indice!

EDIT: Il s'agit d'un code d'exemple étendu comprenant l'échantillonnage aléatoire décrit ci-dessus mais omis dans mon premier exemple:

# for-loop around my dplyr-code
for (j in 1:runs) {
  simul <- dat %>%
    group_by(groups) %>%
    rowwise() %>%
    mutate(vector_column = case_when(abs(col_A) == (maximum-minimum) ~ list(col_B),
                                     sign(col_A) == 1 ~ list(dat$col_B[dat$col_B <= maximum - col_A]), # using list function to store vectors in a data.frame
                                     sign(col_A) != 1 ~ list(dat$col_B[dat$col_B >= minimum + col_A])),
           helper = !is_empty(vector_column), # in case some of the vectors are empty so it is not possible to use sample
           col_B_new = ifelse(helper, sample(vector_column, 1), NA),
           helper = NULL,
           sim_data = list(seq(minimum, maximum, by = 10))) %>% # define new x-values for later predictions
    ungroup() %>% # get rid of rowwise()
    group_by(groups) %>%
    unnest(sim_data) %>%
    nest(sim_data = sim_data) %>%
    nest(data = c(col_A, col_B, col_B_new, species)) %>%
    mutate(model = map(data, linear_mod),
           fitted = map2(model, sim_data, ~predict(.x, col_B_new = sim_data, type = "response"))) %>% # predict values
    unnest(fitted) # unnest predicted values to save in data frame

           # save newly fitted values in fitted_sim data frame
           fitted_sim[,1] <- simul$groups
           fitted_sim[,1+j] <- simul$fitted
}

1voto

user12728748 Points 6262

Ne suis pas sûr exactement de ce dont vous aurez besoin de votre simul data.frame à l'avenir, mais les éléments de la boucle individuelle semblent être indépendants les uns des autres, vous pourriez donc utiliser parallel:mclappy pour les exécuter en parallèle. Dans mon exemple, j'utilise simplement lapply, mais c'est déjà beaucoup plus rapide - si cela vous aide de quelque manière que ce soit...

library(tidyverse)
library(data.table)
library(microbenchmark)

# créer des données
dat <- data.frame("species" =  seq(1:20), "col_A" = runif(20, min=1000, max=2500), "col_B" = runif(20, min = 0, max = 1500), 
                  "maximum" = rep(2500, 20), "minimum" = rep(1000, 20), groups = rep(LETTERS[1:5], each = 4))

# fonctions à utiliser avec purrr
linear_mod <- function(dat) {
    lm(col_A ~ col_B, data = dat)
}

# définir des paramètres et un data frame vide à utiliser dans la boucle for
runs <- 10
fitted_sim <- data.frame(matrix(data=NA,nrow=20,ncol=runs+1,byrow=FALSE)) #dataframe vide pour contenir les valeurs ajustées pour chaque alt
names(fitted_sim) <- as.factor(seq(1:runs+1))

# J'ai simplement enveloppé votre code dans une fonction pour le benchmarking
run.simul <- function(){
    for (j in 1:runs){
        simul <- dat %>%
            group_by(groups) %>%
            nest(data = c(col_A, col_B, species)) %>%
            mutate(model = map(data, linear_mod), # ajouter un modèle pour chaque groupe
                   sim_data = list(seq(minimum, maximum, by = 10))) %>% # définir de nouvelles valeurs x pour les prédictions ultérieures
            unnest(sim_data) %>%
            nest(sim_data = sim_data) %>%
            mutate(fitted = map2(model, sim_data, ~predict(.x, col_B = sim_data, type = "response"))) %>% # prédire les valeurs
            unnest(fitted) # déroulez les valeurs prédites pour les enregistrer dans le data frame
        # enregistrer les nouvelles valeurs ajustées dans le data frame fitted_sim
        fitted_sim[,1] <- simul$groups
        fitted_sim[,1+j] <- simul$fitted
    }
    fitted_sim
}

# ma version (désolé d'utiliser `data.table`...)
Dat <- data.table(dat, key="groups")
getFits <- function(x){
    x[, fitted := predict(lm(col_A ~ col_B), .(list(seq(minimum[1], maximum[1], by = 10)))), by = groups]
    x[, .(species, groups, fitted)]
}

# obtenir les résultats dans un data.frame; utilisez mclapply à la place de lapply si vous le souhaitez
dcast(rbindlist(lapply(seq_len(runs), function(z) getFits(data.table(dat, key="groups"))), idcol = "run"), ... ~ run, value.var = "fitted")
#>     species groups        1        2        3        4        5        6
#>  1:       1      A 1767.621 1767.621 1767.621 1767.621 1767.621 1767.621
#>  2:       2      A 1376.679 1376.679 1376.679 1376.679 1376.679 1376.679
#>  3:       3      A 1523.100 1523.100 1523.100 1523.100 1523.100 1523.100
#>  4:       4      A 1794.593 1794.593 1794.593 1794.593 1794.593 1794.593
#>  5:       5      B 1272.408 1272.408 1272.408 1272.408 1272.408 1272.408
#>  6:       6      B 1967.709 1967.709 1967.709 1967.709 1967.709 1967.709
#>  7:       7      B 1792.934 1792.934 1792.934 1792.934 1792.934 1792.934
#>  8:       8      B 2318.699 2318.699 2318.699 2318.699 2318.699 2318.699
#>  9:       9      C 2017.187 2017.187 2017.187 2017.187 2017.187 2017.187
#> 10:      10      C 1899.827 1899.827 1899.827 1899.827 1899.827 1899.827
#> 11:      11      C 1734.953 1734.953 1734.953 1734.953 1734.953 1734.953
#> 12:      12      C 1834.046 1834.046 1834.046 1834.046 1834.046 1834.046
#> 13:      13      D 1797.615 1797.615 1797.615 1797.615 1797.615 1797.615
#> 14:      14      D 1915.832 1915.832 1915.832 1915.832 1915.832 1915.832
#> 15:      15      D 1841.489 1841.489 1841.489 1841.489 1841.489 1841.489
#> 16:      16      D 1798.442 1798.442 1798.442 1798.442 1798.442 1798.442
#> 17:      17      E 1641.359 1641.359 1641.359 1641.359 1641.359 1641.359
#> 18:      18      E 1631.253 1631.253 1631.253 1631.253 1631.253 1631.253
#> 19:      19      E 1634.197 1634.197 1634.197 1634.197 1634.197 1634.197
#> 20:      20      E 1634.991 1634.991 1634.991 1634.991 1634.991 1634.991
#>            7        8        9       10
#>  1: 1767.621 1767.621 1767.621 1767.621
#>  2: 1376.679 1376.679 1376.679 1376.679
#>  3: 1523.100 1523.100 1523.100 1523.100
#>  4: 1794.593 1794.593 1794.593 1794.593
#>  5: 1272.408 1272.408 1272.408 1272.408
#>  6: 1967.709 1967.709 1967.709 1967.709
#>  7: 1792.934 1792.934 1792.934 1792.934
#>  8: 2318.699 2318.699 2318.699 2318.699
#>  9: 2017.187 2017.187 2017.187 2017.187
#> 10: 1899.827 1899.827 1899.827 1899.827
#> 11: 1734.953 1734.953 1734.953 1734.953
#> 12: 1834.046 1834.046 1834.046 1834.046
#> 13: 1797.615 1797.615 1797.615 1797.615
#> 14: 1915.832 1915.832 1915.832 1915.832
#> 15: 1841.489 1841.489 1841.489 1841.489
#> 16: 1798.442 1798.442 1798.442 1798.442
#> 17: 1641.359 1641.359 1641.359 1641.359
#> 18: 1631.253 1631.253 1631.253 1631.253
#> 19: 1634.197 1634.197 1634.197 1634.197
#> 20: 1634.991 1634.991 1634.991 1634.991

run.simul()
#>    1        2        3        4        5        6        7        8        9
#> 1  A 1767.621 1767.621 1767.621 1767.621 1767.621 1767.621 1767.621 1767.621
#> 2  A 1376.679 1376.679 1376.679 1376.679 1376.679 1376.679 1376.679 1376.679
#> 3  A 1523.100 1523.100 1523.100 1523.100 1523.100 1523.100 1523.100 1523.100
#> 4  A 1794.593 1794.593 1794.593 1794.593 1794.593 1794.593 1794.593 1794.593
#> 5  B 1272.408 1272.408 1272.408 1272.408 1272.408 1272.408 1272.408 1272.408
#> 6  B 1967.709 1967.709 1967.709 1967.709 1967.709 1967.709 1967.709 1967.709
#> 7  B 1792.934 1792.934 1792.934 1792.934 1792.934 1792.934 1792.934 1792.934
#> 8  B 2318.699 2318.699 2318.699 2318.699 2318.699 2318.699 2318.699 2318.699
#> 9  C 2017.187 2017.187 2017.187 2017.187 2017.187 2017.187 2017.187 2017.187
#> 10 C 1899.827 1899.827 1899.827 1899.827 1899.827 1899.827 1899.827 1899.827
#> 11 C 1734.953 1734.953 1734.953 1734.953 1734.953 1734.953 1734.953 1734.953
#> 12 C 1834.046 1834.046 1834.046 1834.046 1834.046 1834.046 1834.046 1834.046
#> 13 D 1797.615 1797.615 1797.615 1797.615 1797.615 1797.615 1797.615 1797.615
#> 14 D 1915.832 1915.832 1915.832 1915.832 1915.832 1915.832 1915.832 1915.832
#> 15 D 1841.489 1841.489 1841.489 1841.489 1841.489 1841.489 1841.489 1841.489
#> 16 D 1798.442 1798.442 1798.442 1798.442 1798.442 1798.442 1798.442 1798.442
#> 17 E 1641.359 1641.359 1641.359 1641.359 1641.359 1641.359 1641.359 1641.359
#> 18 E 1631.253 1631.253 1631.253 1631.253 1631.253 1631.253 1631.253 1631.253
#> 19 E 1634.197 1634.197 1634.197 1634.197 1634.197 1634.197 1634.197 1634.197
#> 20 E 1634.991 1634.991 1634.991 1634.991 1634.991 1634.991 1634.991 1634.991
#>          10       NA
#> 1  1767.621 1767.621
#> 2  1376.679 1376.679
#> 3  1523.100 1523.100
#> 4  1794.593 1794.593
#> 5  1272.408 1272.408
#> 6  1967.709 1967.709
#> 7  1792.934 1792.934
#> 8  2318.699 2318.699
#> 9  2017.187 2017.187
#> 10 1899.827 1899.827
#> 11 1734.953 1734.953
#> 12 1834.046 1834.046
#> 13 1797.615 1797.615
#> 14 1915.832 1915.832
#> 15 1841.489 1841.489
#> 16 1798.442 1798.442
#> 17 1641.359 1641.359
#> 18 1631.253 1631.253
#> 19 1634.197 1634.197
#> 20 1634.991 1634.991

# benchmarking
microbenchmark(
    a=dcast(rbindlist(lapply(seq_len(runs), function(z) getFits(data.table(dat, key="groups"))), idcol = "run"), ... ~ run, value.var = "fitted"),
    b=run.simul(), times= 10L
    )
#> Unit: milliseconds
#>  expr       min        lq      mean    median       uq      max neval cld
#>     a  46.46851  48.65027  51.09618  49.88775  54.7177  56.2888    10  a 
#>     b 389.76893 410.57588 418.16993 415.74493 424.2042 448.4604    10   b

Créé le 2020-08-13 par le package reprex (v0.3.0)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X