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.for
for
Existem três variações importantes do loop for
básico:
for
Suponha 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]]) }
for
Existem 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:
for
results <- 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 ...
map
map
O 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.map
Cada 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!).
map
df %>% 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
map
Existem 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))
map
A 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
).
map
Podemos 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