Treinamento básico em funções

Treinamento em Programação no Ambiente R


GUIA DO MOCHILEIRO DO UNIVERSO R

Utilizaremos uma planilha com os dados gerados por alunos de um curso feito na ESALQ, salve no seu diretório acessando:

Aqui usaremos o argumento stringAsFactors que impede que o R transforme os vetores da tabela em fatores, os quais são mais difíceis de trabalhar. O argumento na.strings irá indicar como foram nomeados os dados perdidos.

dados <- read.csv("dados_alunos.csv", sep=";", na.strings="-", stringsAsFactors = F)
head(dados)
load(file="dados_alunos.RData")

Vamos explorar a estrutura dos dados coletados:

str(dados)
# também
dim(dados) #18 linhas e 16 colunas

Repare que nos nomes das colunas ainda estão as perguntas completas feitas no formulário, vamos alterar para nomes mais fáceis de trabalhar:

colnames(dados)
colnames(dados) <-  c("horario", "genero", "participacao", "cidade", "instituicao", 
                   "escolaridade", "graduacao", "area", "idade", "altura", "peso", 
                   "aniversario", "conhecimentoR", "linguagens", "usoR", "motivacaoR")
str(dados)
colnames(dados)

Paradoxo do aniversário

Nossa primeira análise com esses dados envolverá um problema denominado “Paradoxo do aniversário”, que afirma que em um grupo de 23 pessoas (ou mais), escolhidas aleatoriamente, há mais de 50% de chance de duas pessoas terem a mesma data de aniversário.

Primeiro, vamos verificar em quantos somos, contando o número de linhas, para isso use a função nrow.

nrow(dados)

Vamos então verificar se temos no nosso grupo pessoas que compartilham o mesmo dia de aniversário.

Podemos verificar isso facilmente com a função table, que indica a frequência de cada observação:

table(dados$aniversario)

Estruturas condicionais

if e else

Para nossa próxima atividade com os dados, vamos primeiro entender como funcionam as estruturas if e else.

Nas funções condicionais if e else, estabelecemos uma condição para if, se ela for verdade a atividade será realizada, caso contrário (else) outra tarefa será. Como no exemplo:

if(2 > 3){
  print("dois é maior que três")
} else {
  print("dois não é maior que três")
}
  • Teste o nível de conhecimento em R obtidos no formulário (13ª coluna) pela segunda pessoa que o respondeu (linha 2). Envie uma mensagem motivacional se ela não possuir qualquer conhecimento (nota 0), outra se possuir algum conhecimento (restante das notas). (dica: o sinal == se refere a “exatamente igual a”)
if(dados[2,13] == 0){
  print("Nunca é tarde para começar!")
} else {
  print("Já pegou o embalo, agora é só continuar!")
}

Podemos espeficiar mais do que uma condição repetindo a estrutura if else:

if(dados[2,13] == 0){
  print("Nunca é tarde para começar!")
} else if (dados[2,13] > 0 & dados[2,13] < 5){
  print("Já pegou o embalo, agora é só continuar!")
} else {
  print("Me avise se estivermos falando algo errado...hehe")
}

Também podemos fazer uso das funções any e all. Será que algum de nós tem mais de 60 anos de idade (idade está na 9ª coluna)? Se sim, diga para essa pessoa tomar cuidado e levar a sério o isolamento.

Lembrando como any e all funcionam:

x <- 1:10
x
all(x > 0)
any(x > 0)
all(x > 9)
any(x > 9)
all(x > 10)
any(x > 10)
if(any(dados[,9] > 60)){
  print("Sabemos que você está entre nós! Para sua segurança, é melhor que fique em casa, esperamos que esteja assistindo remotamente.")
} else{
  print("Não há maiores de 60 anos aqui, evitem visitar idosos nesse período, vamos mantê-los seguros.")
}

Ih! Achamos um bug, né? Vamos lidar com ele! O R deu uma dica de onde ele está:

Error in if (any(dados[, 9] > 60)) { : 
  missing value where TRUE/FALSE needed
dados[,9] # Verificamos que existem dados perdidos
dados[,9] > 60 # Quando comparamos com 60, eles continuam como dados perdidos
any(dados[,9] > 60) # Aqui está o bug! Por padrão o any, quando encontra NA retorna NA
any(dados[,9] > 60, na.rm = TRUE) # Mudamos esse padrão utilizando o argumento para remover os NA
# Nós poderíamos fazer também da seguinte maneira:
idade <- dados[,9]
is.na(idade)
idade
idade_semNA <- idade[-which(is.na(idade))]
idade_semNA

Pronto! Podemos voltar ao código.

if(any(dados[,9] > 60, na.rm = TRUE)){
  print("Sabemos que você está entre nós! Para sua segurança, é melhor que fique em casa, esperamos que esteja assistindo remotamente.")
} else{
  print("Não há maiores de 60 anos aqui. Evitem visitar idosos nesse período, vamos mantê-los seguros.")
}
# ou
if(any(idade_semNA > 60)){
  print("Sabemos que você está entre nós! Para sua segurança, é melhor que fique em casa, esperamos que esteja assistindo remotamente.")
} else{
  print("Não há maiores de 60 anos aqui. Evitem visitar idosos nesse período, vamos mantê-los seguros.")
}
  • Lembra do paradoxo do aniversário? Verifique se existe uma data repetida no vetor e responda “Existem pessoas aqui que fazem aniversário no mesmo dia” caso exista e “Não temos pessoas com aniversário no mesmo dia aqui” caso contrário.

Switch

Uma outra estrutura que também pode ser usada com o mesmo propósito é o switch. Esta estrutura é mais utilizada quando trabalhado com caracteres. Por isso vamos aplicá-la para explorar a área (8ª coluna) com que a segunda pessoa se identifica.

switch(dados[2,8],
  Exatas = print("Será que aprendeu alguma linhagem de programação na graduação?"),
  Interdiciplinar = print("Em que foi a gradução?"),
  print("Ta aqui colocando o pezinho na exatas")
)

A estrutura switch costuma ser mais rápida que o if e else. Quando lidamos com grande quantidade de dados isso pode ser uma grande vantagem.

Mas repare que só é possível utilizar essas estruturas para um elemento individual do vetor ou em todo ele, se quisermos percorrer os elementos individualmente precisamos recorrer a outro recurso.

Estruturas de repetição

For

Esse recurso pode ser a função for, uma função muito utilizada e poderosa. Ela constitui uma estrutura de loop, pois irá aplicar a mesma atividade repetidamente até atingir uma determinada condição. Veja exemplos:

for(i in 1:10){
  print(i)
}

test <- vector()
for(i in 1:10){
  test[i] <- i+4 
}
test

Nos casos acima, i funciona como um index que irá variar de 1 até 10 a operação determinada entre chaves.

Com essa estrutura, podemos repetir a operação realizada com as estruturas if e else para todo o vetor:

for(i in 1:nrow(dados)){
  if(dados[i,13] == 0){
    print("Nunca é tarde para começar!")
  } else if (dados[i,13] > 0 && dados[i,13] < 5){
    print("Já pegou o embalo, agora é só continuar!")
  } else {
      print("Nos avise se estivermos falando algo errado...hehe")
    }
}

Dica: Identação

Repare a diferença:

# Sem identação
for(i in 1:nrow(dados)){
if(dados[i,13] == 0){
print("Nunca é tarde para começar!")
} else if (dados[i,13] > 0 && dados[i,13] < 5){
print("Já pegou o embalo, agora é só continuar!")
} else {
print("Nos avise se estivermos falando algo errado...hehe")
}
}
# Com identação correta
for(i in 1:nrow(dados)){
  if(dados[i,13] == 0){
    print("Nunca é tarde para começar!")
  } else if (dados[i,13] > 0 && dados[i,13] < 5){
    print("Já pegou o embalo, agora é só continuar!")
  } else {
    print("Nos avise se estivermos falando algo errado...hehe")
  }
}

O editor de código do RStudio tem uma facilitação para identar códigos em R, selecione a área que deseja identar e aperte Ctrl-i.

Agora vamos trabalhar com a coluna 4, que possui a informação da cidade de origem dos participantes. Repare que alguns não colocaram o estado, como o exemplo sugeria. Vamos utilizar um loop para descobrir quais estão faltando. Vamos utilizar a função grepl para identificar as strings que contém o caracter “-”, aqueles que tiverem consideraremos correto, os que não tiverem, vamos pedir para adicionar mais informações.

# Exemplo do uso da função grepl
grepl("-", dados[1,4]) # A primeira linha contem o caracter "-"
for(i in 1:nrow(dados)){
  if(grepl("-", dados[i,4])){
    cat("Esse seguiu o exemplo direitinho. Parabéns!\n")
  } else {
    cat("Precisamos adicionar mais informações na linha", i, "\n")
  }
}

Para que seja possível imprimir conteúdo de objetos durante o loop, usamos a função cat, ela não separa cada resposta em uma linha, precisamos colocar o \n indicando a quebra de linha.

Agora vamos nos mesmos corrigir essas informações. Podemos armazenar em uma variável a posição das linhas incorretas, então corrigiremos manualmente somente essas:

corrigir <- vector()
for(i in 1:nrow(dados)){
  if(grepl("-", dados[i,4])){
    cat("Esse/a seguiu o exemplo direitinho. Parabéns!\n")
  } else {
    cat("Precisamos adicionar mais informações na linha", i, "\n")
    corrigir <- c(corrigir, i)
  }
}
corrigir

Como você faria para corrigir esses elementos errados? Tente! Dica: Primeiro busque quais são as cidades desses elementos (dados[corrigir,4]), depois, faça um novo vetor com os nomes corretos e substitua na tabela

Uma possibilidade de resposta:

dados[corrigir,4]
novo <- c("Piracicaba - SP")
novo
dados[corrigir,4] <- novo
# Verificando se corrigiu
dados[,4]
dados[3,4] <- c("São Paulo-SP")

A coluna 9 da tabela se refere à idade dos participantes, imprima na tela (usando print ou cat) a década em que cada um nasceu, como a seguir: “Nasceu na década de 80”. Dica: Faça um novo vetor com a subtração das idades pelo ano atual e depois faça um loop com uma condicional para imprimir as mensagens na tela

Uma possibilidade de resposta:

decada <- 2021 - idade_semNA
decada
for(i in 1:length(decada)){
  if(decada[i] < 1960){
    print("Nasceu antes da década de 60")
  } else if(decada[i] > 1960 && decada[i] < 1970){
    print("Nasceu na década de 60")
  } else if(decada[i] >= 1970 && decada[i] < 1980){
    print("Nasceu na década de 70")
  } else if(decada[i] >= 1980 && decada[i] < 1990){
    print("Nasceu na década de 80")
  } else if(decada[i] >= 1990 && decada[i] < 2000){
    print("Nasceu na década de 90")
  } else {
    print("Xófên")
  }
}

While

Nesse tipo de estrutura de repetição a tarefa será realizada até que seja atingida determinada condição.

x <- 1
x
while(x < 5){
  x <- x + 1
  print(x)
}

É muito importante que nessa estrutura a condição seja atingida, caso contrário o loop irá funcionar infinitamente e você terá que interrompê-lo por meios externos, como, se este utilizando RStudio, clicar no simbolo em vermelho no canto direito superior da janela do console, ou apertar ESC no console.

Não é muito difícil disso acontecer, basta um pequeno erro como:

x <- 1
while(x < 5){
  x + 1
  print(x)
}

Aqui podemos utilizar os comandos break e next para atender a outras condições, como:

x <- 1
while(x < 5){
  x <- x + 1
  if(x==4) break
  print(x)
}
x <- 1
while(x < 5){
  x <- x + 1
  if(x==4) next
  print(x)
}

Repeat

Esta estrutura também exige uma condição de parada, mas esta condição é necessariamente colocada dentro do bloco de código com o uso do break. Ela então repete o bloco de código até a condição o interrompa.

x <- 1
repeat{
  x <- x+1
  print(x)
  if(x==4) break
}

Loops dentro de loops

É possível também utilizarmos estruturas de repetição dentro de estruturas de repetição. Por exemplo, se quisermos trabalhar tanto nas colunas como nas linhas de uma matrix.

# Criando uma matrix vazia
ex_mat <- matrix(nrow=10, ncol=10)
ex_mat
# cada número dentro da matrix será o produto no índice da coluna pelo índice da linha
for(i in 1:dim(ex_mat)[1]) {
  for(j in 1:dim(ex_mat)[2]) {
    ex_mat[i,j] = i*j
  }
}
ex_mat

Outro exemplo de uso:

var1 <- c("fertilizante1", "fertilizante2")
var2 <- c("ESS", "URO", "GRA")
w <- 0
for(i in var1){
  for(j in var2){
    nome_arquivo <- paste0(i,"_planta_",j,".txt")
    arquivo <- data.frame("bloco" = "fake_data", "tratamento" ="fake_data")
    write.table(arquivo, file = nome_arquivo)
    w <- w + 1
  }
}
getwd()

Algumas dicas:

  • Cuidado ao rodar o mesmo comando mais de uma vez, algumas variáveis podem não ser mais como eram antes. Para que o comando funcione da mesma forma é necessário que os objetos de entrada estejam da forma como você espera.
  • Lembrem-se que = é para definir objetos e == é o sinal de igualdade.
  • Nas estruturas condicionais e de repetição, lembrem-se que é necessário manter a sintaxe esperada: If(){} e for(i in 1:10){}. No for, podemos trocar a letra que será o índice, mas é sempre necessário fornecer uma sequência de inteiros ou caracteres.
  • Usar identação ajuda a visualizar o começo e fim de cada estrutura de código e facilita o abrir e fechar de chaves. Identação são aqueles espaços que usamos antes da linha, como:
# Criando uma matrix vazia
ex_mat <- matrix(nrow=10, ncol=10)
# cada número dentro da matrix será o produto no índice da coluna pelo índice da linha
for(i in 1:dim(ex_mat)[1]) {   # Primeiro nível, não tem espaço
  for(j in 1:dim(ex_mat)[2]) { # Segundo nível tem um espaço (tab)
    ex_mat[i,j] = i*j          # Terceiro nível tem dois espaços
  }                            # Fechei o segundo nível
}                              # Fechei o primeiro nível

Elaboração de funções

Normalmente é uma boa prática criar um bloco de código se vai realizar aquela ação poucas vezes. Se for realizar várias vezes a ação e de uma vez só, vale a pena fazer um loop. Mas, se for realizar diversas vezes e o objeto de entrada for modificado, vale a pena fazer uma função. E, na hierarquia, quando tiver acumulado muitas funções para realizar uma tarefa mais complexa, vale a pena construir um pacote.

A função também é considerada um objeto no R, portanto você a atribui à uma variável. Procure nomear a função com um nome curto e intuitivo em relação à tarefa que ela executa. Nem sempre é uma tarefa fácil.

Para criar uma função obedeça a seguinte estrutura:

nome_da_funcao <- function(argumento1, argumento2,...){
  corpo da função
}

Como exemplo, vamos criar a função quadra. Estabelecemos os argumentos da função, nesse caso x. Entre as chaves fica todo o corpo da função. Se você quer que a função retorne algum valor, é necessário utilizar o return.

quadra <- function(x){
  z <- x*x
  return(z)
}
quadra(3)
quadra(4)
qualquer_nome <- 4
quadra(qualquer_nome)

O ambiente de variáveis utilizado pela função é diferente do ambiente global em que são armazenadas todos as variáveis já criadas. Variáveis criadas no corpo da função não pertencerão ao ambiente global, mas as contidas no ambiente global podem ser acessadas pela função. Observe:

rm(z) # certificando que não há uma variável/objeto z criada/o
# Na função quadra, criamos uma variável z internamente
quadra(qualquer_nome)
z # mais ele não é criado no ambiente global, só no ambiente interno da função, e z desaparece assim que a função termina sua tarefa

Agora vamos criar uma outra funçao que usa y, mas não possui y como argumento. Atenção: não façam isso na rotina de vocês, sempre estabeleçam tudo o que for ser usado no corpo da função nos argumentos dela, estamos fazendo aqui apenas para fins didádicos.

rm(y)
quadra_mais_y <- function(x){
  z <- x*x + y
  return(z)
}
quadra_mais_y(4) # não temos ainda y
y <- 2
quadra_mais_y(4) # quando temos, a função acessa o y do ambiente global

##podemos criar uma variável função em x e y
rm(y)
quadra_mais_y <- function(x, y){
  z <- x*x + y
  return(z)
}
quadra_mais_y(2,3) # não temos ainda y

Agora, vamos criar uma função para calcular o IMC (índice de massa corporal) dos participantes do curso. Lembrando que o IMC é calculado pela divisão do peso pelo quadrado da altura.

## Calcula o índice de massa corporal (IMC) dos participantes
dados$peso
dados$altura
calc_IMC <- function(peso, altura){
  IMC <- peso/altura^2
  return(IMC)
}
calc_IMC(peso=dados$peso, altura=dados$altura)

Considere a tabela abaixo e faça agora a função dizer para o usuário quantos valores do vetor de IMCs estão em cada uma das categorias, além de retornar o vetor, como já estava fazendo antes.

calc_IMC <- function(peso, altura){
  IMC <- peso/altura^2
  
  idx <- sum(IMC < 18.5) # No R FALSE = 0 e TRUE=1, a soma aqui é a mesma coisa que contar o número de TRUEs
  cat(idx, "indivíduos estão abaixo do peso")
  idx <- sum(IMC >= 18.5 & IMC < 25)
  cat(idx, "indivíduos estão com peso normal")
  idx <- sum(IMC >= 25 & IMC < 30)
  cat(idx, "indivíduos estão com sobrepeso")
  idx <- sum(IMC >= 30 & IMC < 35)
  cat(idx, "indivíduos estão com obesidade grau 1")
  idx <- sum(IMC >= 35 & IMC < 40)
  cat(idx, "indivíduos estão com obesidade grau 2")
  idx <- sum(IMC >= 40)
  cat(idx, "indivíduos estão com obesidade grau 3")
  
  return(IMC)
}

Se é uma função para uso próprio, você saberá como deve ser o objeto de entrada, mas se ela for utilizada por outras pessoas, será necessário, além de uma prévia explicação de suas ações, verificar se o objeto de entrada esta de acordo com o esperado pela função. No nosso exemplo, não faria sentido o usário entrar com números negativos. Faça com que a função retorne um erro caso isso aconteça.

# para testar durante a construção da função 
peso <- dados$peso
altura <- dados$altura
calc_IMC <- function(peso, altura){
  
  if(any(peso < 0 | altura < 0)){
    stop("Os valores dos argumentos peso e altura não podem ser negativos")
  }
  
  IMC <- peso/altura^2
  
  idx <- sum(IMC < 18.5) # No R FALSE = 0 e TRUE=1, a soma aqui é a mesma coisa que contar o número de TRUEs
  cat(idx, "indivíduos estão abaixo do peso \n")
  idx <- sum(IMC >= 18.5 & IMC < 25)
  cat(idx, "indivíduos estão com peso normal \n")
  idx <- sum(IMC >= 25 & IMC < 30)
  cat(idx, "indivíduos estão com sobrepeso \n")
  idx <- sum(IMC >= 30 & IMC < 35)
  cat(idx, "indivíduos estão com obesidade grau 1 \n")
  idx <- sum(IMC >= 35 & IMC < 40)
  cat(idx, "indivíduos estão com obesidade grau 2 \n")
  idx <- sum(IMC >= 40)
  cat(idx, "indivíduos estão com obesidade grau 3")
  
  return(IMC)
}
peso <- c(-1,56,79)
altura <- c(1.6,1.73,1.58)
calc_IMC(peso, altura)


peso <- c(20,56,79)
knitr::kable(calc_IMC(peso, altura))

Para saber mais sobre desenvolvimento de funções acesse aqui e, um pouco mais avançado, aqui.

Diferenças entre cat, print e paste

exemplo <- "Este é um exemplo"
# Diferença 1 - salva em novos objetos?
obj <- cat(exemplo)
obj
obj <- print(exemplo)
obj
obj <- paste(exemplo)
obj
# Diferença 2 - Concatena?
cat(exemplo, "oi")

print(exemplo, "oi") #não concatena

paste(exemplo, "oi")
paste0(exemplo, "oi")
# Diferença 4 - Aparece quando dentro de funções ou loops?
for(i in 1:5){
  cat(exemplo)
}
for(i in 1:5){
  paste(exemplo)
}
for(i in 1:5){
  print(exemplo)
}
# Diferença 3 - Pode mudar conforme classes S3?
cat(exemplo)
print(exemplo)
class(exemplo) <- "inventei_uma_classe" # Aqui estou fazendo um objeto de classe S3
print.inventei_uma_classe <- function(x) print("Agora vai printar isso aqui, pq eu quero")
cat(exemplo)
print(exemplo)
paste(exemplo)

Rodando outros scripts .R

As vezes, parte do seu código demanda que você chame algo que foi rodado em outro script. Muitas pessoas também tem o costume de salvar as funções próprias em um script separado. Vamos fazer isso?

  • Abra um novo script .R, copie suas funções para ele e o salve como funcoes.R

Agora, você pode acessa-las usando:

source("funcoes.R")