Voltando ao nosso cálculo da mediana, vamos ao invés de ir em cada coluna, construir um loop:
saida <- vector("double", ncol(df)) # 1. saída
for (i in seq_along(df)) { # 2. sequencia
saida[[i]] <- median(df[[i]]) # 3. corpo
}
saida
## [1] -0.34986024 -0.26790199 -0.01824207 -0.75018611
Todo for loop tem três componentes:
saida <- vector("double", ncol(df)). Antes de iniciar o loop, você sempre deve alocar espaço suficiente para a saída. Isso é muito importante para a eficiência: se você aumentar o loop for a cada iteração usando c() (por exemplo), o loop for será muito lento.Uma maneira geral de criar um vetor vazio de determinado comprimento é a função vector(). Ele tem dois argumentos: o tipo do vetor (“logical”, “integer”, “double”, “character”, etc) e o comprimento do vetor.
i em seq_along(df). Isso determina sobre que fazer o loop: cada execução do loop for atribuirá i a um valor diferente de seq_along(df). É útil pensar em i como um pronome, como “it”.Você pode não ter visto seq_along() antes. É uma versão segura do familiar 1:length(l), com uma diferença importante: se você tem um vetor de comprimento zero, seq_along() faz a coisa certa:
y <- vector("double", 0)
seq_along(y)
## integer(0)
1:length(y)
## [1] 1 0
saida[[i]] <- median(df[[i]]). Este é o código que faz o trabalho. É executado repetidamente, sempre com um valor diferente para i. A primeira iteração executará saída[[1]] <- median(df[[1]]), a segunda executará saída[[2]] <- median(df[[2]]) e assim por diante.forforExistem três variações importantes do loop for básico:
forSuponha que queiramos aplicar uma função sobre as colunas de uma função repetidas vezes como abaixo.
rescale01 <- function(x) {
rng <- range(x, na.rm = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
}
df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df$d <- rescale01(df$d)
Podemos resolver esse problema com um loop:
for (i in seq_along(df)) {
df[[i]] <- rescale01(df[[i]])
}
forExistem três maneiras básicas de fazer um loop sobre um vetor. Até agora, mostramos o mais geral: repetir os índices numéricos com for (i em seq_along(xs)) e extrair o valor com x[[i]]. Existem duas outras formas:
Loop sobre os elementos: for (x in xs). Isso é mais útil se você se preocupa apenas com efeitos colaterais, como plotar ou salvar um arquivo, porque é difícil salvar a saída com eficiência.
Loop sobre os nomes: for (nm in names(xs)). Isso fornece um nome, que você pode usar para acessar o valor com x[[nm]]. Isso é útil se você deseja usar o nome em um título de plot ou em um nome de arquivo. Se você estiver criando uma saída nomeada, nomeie o vetor de resultados da seguinte forma:
forresults <- vector("list", length(x))
names(results) <- names(x)
A iteração sobre os índices numéricos é a forma mais geral, pois, dada a posição, é possível extrair o nome e o valor:
for (i in seq_along(x)) {
name <- names(x)[[i]]
value <- x[[i]]
}
forÀs vezes você pode não saber o tamanho que a saída terá. Por exemplo, imagine que você deseja simular alguns vetores aleatórios de comprimentos aleatórios. Uma solução é salvar os resultados em uma lista e depois combiná-los em um vetor único depois que o loop terminar.
means <- c(0, 1, 2)
out <- vector("list", length(means))
for (i in seq_along(means)) {
n <- sample(100, 1)
out[[i]] <- rnorm(n, means[[i]])
}
str(out)
## List of 3 ## $ : num [1:8] -0.2971 0.4064 0.0545 1.299 0.1446 ... ## $ : num [1:14] 0.7 2.163 0.196 0.614 0.988 ... ## $ : num [1:52] 1.656 2.675 4.011 3.071 -0.769 ...
str(unlist(out))
## num [1:74] -0.2971 0.4064 0.0545 1.299 0.1446 ...
mapmapO padrão de criar um loop sobre um vetor, fazer algo em cada elemento e salvar os resultados é tão comum que o pacote purrr fornece uma família de funções para fazer isso. Há uma função para cada tipo de saída:
map() faz uma lista.map_lgl() cria um vetor lógico.map_int() cria um vetor inteiro.map_dbl() cria um vetor duplo.map_chr() cria um vetor de caractere.mapCada função pega um vetor como entrada, aplica uma função a cada peça e retorna um novo vetor que tem o mesmo comprimento (e tem os mesmos nomes) que a entrada. O tipo do vetor é determinado pelo sufixo da função de mapa.
Depois de dominar essas funções, você descobrirá que leva muito menos tempo para resolver problemas de iteração. Mas você nunca deve se sentir mal ao usar um loop for em vez de uma função de mapa. As funções do mapa são um passo à frente em uma torre de abstração e podem levar muito tempo para você entender como elas funcionam. O importante é que você resolva o problema em que está trabalhando, e não escreva o código mais conciso e elegante (embora isso seja definitivamente algo que você deseja buscar!).
mapdf %>% map_dbl(mean)
## a b c d ## 0.3269043 0.3619871 0.4902709 0.5355998
df %>% map_dbl(median)
## a b c d ## 0.1233160 0.3575584 0.5199698 0.5195861
df %>% map_dbl(sd)
## a b c d ## 0.3807706 0.3218500 0.3229475 0.2825503
mapExistem alguns atalhos que você pode usar com .f para economizar um pouco de digitação. Imagine que você deseja ajustar um modelo linear para cada grupo em um conjunto de dados. O exemplo a seguir divide o conjunto de dados mtcars em três partes (uma para cada valor do cilindro) e ajusta o mesmo modelo linear a cada peça:
models <- mtcars %>% split(.$cyl) %>% map(function(df) lm(mpg ~ wt, data = df))
mapA sintaxe para criar uma função anônima no R é bastante detalhada, assim, o pacote purrr fornece um atalho conveniente: uma fórmula unilateral.
models <- mtcars %>% split(.$cyl) %>% map(~lm(mpg ~ wt, data = .))
Aqui, usamos . como pronome: refere-se ao elemento da lista atual (da mesma maneira que i se referia ao índice atual no loop for).
mapPodemos usar a função map também para extrair os elementos de models, como abaixo.
models %>% map(summary) %>% map_dbl(~.$r.squared)
## 4 6 8 ## 0.5086326 0.4645102 0.4229655
models %>%
map(summary) %>%
map_dbl("r.squared")
## 4 6 8 ## 0.5086326 0.4645102 0.4229655