Rudimentos de programação

Aula 07 · Introdução à Linguagem R

Diogo B. Provete

PPG em Ecologia e Conservação · UFMS

Por que escrever uma função

O custo escondido do copiar-e-colar

Na aula 04 escrevemos isto, três vezes, mudando só a coluna:

mean(anuros$ctmax, na.rm = TRUE); sd(anuros$ctmax, na.rm = TRUE)
[1] 36.96158
[1] 2.599318
mean(anuros$ctmin, na.rm = TRUE); sd(anuros$ctmin, na.rm = TRUE)
[1] 4.899107
[1] 2.534161

Na terceira linha você troca a coluna do mean e esquece a do sd. Nenhum erro, nenhum aviso. Um número errado no meio da tabela.

Anatomia de uma função

resumo <- function(x, digitos = 2) {        # nome <- function(argumentos)
  c(n   = sum(!is.na(x)),                   # corpo
    na  = sum(is.na(x)),
    med = round(mean(x, na.rm = TRUE), digitos),
    dp  = round(sd(x,   na.rm = TRUE), digitos))
}                                           # a última expressão é o retorno

resumo(anuros$ctmax)
     n     na    med     dp 
190.00  35.00  36.96   2.60 

Regra prática: copiou e colou três vezes, escreva uma função. Duas vezes, comece a desconfiar.

Agora vocês

10 minutos

  1. Rode resumo() em ctmin, ewl e massa
  2. Escreva erro_padrao(x) que devolva sd(x)/sqrt(n), ignorando NA
  3. Acrescente o erro-padrão ao vetor que a resumo() devolve

No item 3: você vai precisar mexer em um lugar só. Era esse o ponto.

Escopo: o que entra é cópia

x <- 10
troca <- function(x) { x <- 999; x }
troca(x)
[1] 999
x
[1] 10

É por isso que a função precisa devolver o resultado. Mexer no objeto de fora, de dentro da função, é possível — e é fábrica de bug.

Controle de fluxo

if enxerga um valor de cada vez

classifica <- function(m) {
  if (is.na(m))      NA_character_
  else if (m < 500)  "baixa"
  else if (m < 1200) "média"
  else               "alta"
}
classifica(35); classifica(1600)
[1] "baixa"
[1] "alta"

Na coluna inteira isso não funciona. A versão vetorizada:

anuros <- anuros %>%
  mutate(faixa = case_when(altitude <  500 ~ "baixa",
                           altitude < 1200 ~ "média",
                           TRUE            ~ "alta"))
table(anuros$faixa)

 alta baixa média 
   78    39   108 

for: e a armadilha de crescer o objeto

colunas <- c("ctmin", "ctmax", "ewl", "massa")

res <- numeric(length(colunas))        # reserve o espaço ANTES
names(res) <- colunas
for (i in seq_along(colunas)) res[i] <- mean(anuros[[colunas[i]]], na.rm = TRUE)
round(res, 2)
ctmin ctmax   ewl massa 
 4.90 36.96  1.80 72.87 

seq_along(x), nunca 1:length(x): com x vazio, 1:0 devolve c(1, 0) e o laço roda duas vezes com índice inválido.

A alternativa funcional

purrr::map

O mesmo do slide anterior, sem laço e sem pré-alocar:

map_dbl(anuros[colunas], mean, na.rm = TRUE) %>% round(2)
ctmin ctmax   ewl massa 
 4.90 36.96  1.80 72.87 
map_dbl(anuros[colunas], ~ sum(is.na(.x)))
ctmin ctmax   ewl massa 
    1    35    11     0 

map() devolve lista; map_dbl() devolve vetor de double — e dá erro se o resultado não for. O erro aparece na hora, não três passos adiante.

O caso real: um modelo por espécie

modelos <- anuros %>%
  filter(!is.na(tol_aquec)) %>%
  split(.$especie) %>%
  map(~ lm(tol_aquec ~ altitude, data = .x))

map_dfr(modelos, tidy, .id = "especie") %>%
  filter(term == "altitude") %>%
  select(especie, estimate, p.value)
# A tibble: 5 × 3
  especie               estimate  p.value
  <chr>                    <dbl>    <dbl>
1 Boana faber           0.00362  7.75e-11
2 Dendropsophus minutus 0.00337  3.57e- 7
3 Leptodactylus latrans 0.00331  1.43e-11
4 Physalaemus cuvieri   0.000754 4.03e- 1
5 Rhinella icterica     0.00259  8.47e- 7

Cinco modelos, nenhum lm() repetido.

Agora vocês

15 minutos

  1. Refaçam o slide anterior trocando especie por serra. Quantas linhas do seu código mudaram? Se foi mais de uma, generalize
  2. Acrescentem o R² de cada modelo, com glance()
  3. Olhem count(especie, serra, altitude) para D. minutus e L. latrans: o ponto mais alto do gradiente vem de uma serra diferente do resto. O que isso faz com a inclinação que vocês acabaram de estimar?

O item 3 não é sobre purrr. É a aula 06 voltando: automatizar o ajuste não automatiza o julgamento.

Para ler depois

Tirando vantagem de LLMs para programação científica

Harnessing large language models for coding, teaching and inclusion to empower research in ecology and evolution

Cooper, N.; Clark, A.T.; Lecomte, N.; Qiao, H.; Ellison, A.M. (2024). Methods in Ecology and Evolution. doi:10.1111/2041-210X.14325

Fluxo de trabalho com Gemini e ChatGPT

2 exercícios

  1. Peguem um exemplo de manuseio de dados do livro Análises Ecológicas no R escrito em R base e solicitem à IA:

    “Traduz este código para a sintaxe do tidyverse utilizando pipes (%>%) e as funções do dplyr.”

  2. Utilizando o ggplot2, alterem um gráfico já feito para que o eixo X tenha as etiquetas a 45 graus, use o tema minimal e mude a paleta de cores para uma que seja amigável para daltônicos (colorblind-friendly).