sort(c(2, 1, 3, 0), decreasing = TRUE)R> [1] 3 2 1 0
A maior dificuldade que um usuário iniciante possui ao começar a desenvolver rotinas com o R é a forma de trabalho. A nossa interação com computadores foi simplificada ao longo dos anos e atualmente estamos confortáveis com o formato de interação do tipo aponte e clique. Particularmente para o caso de análise de dados, o uso de planilhas eletrônicas do tipo Excel é provavelmente o primeiro contato de um estudante com plataformas de análise.
O formato aponte e clique, muito utilizado em planilhas, permite que o usuário aponte o mouse para um determinado local da tela, clique em um botão e realize uma determinada operação. Uma série de passos nesse sentido permite a execução de tarefas complexas no computador. Mas não se engane, essa forma de interação no formato aponte e clique é apenas uma camada por cima do que realmente acontece no computador. Por trás de todo clique existe um comando sendo executado, seja na abertura de um arquivo pdf, direcionamento do browser para uma página na internet ou qualquer outra operação cotidiana.
Enquanto esse formato de interação visual e motora tem seus benefícios ao facilitar e popularizar o uso de computadores, é pouco flexível e eficaz quando se trabalha com procedimentos computacionais. Alternativamente, ao conhecer os possíveis comandos disponíveis ao usuário, é possível criar um arquivo contendo instruções em sequência e, futuramente, simplesmente pedir que o computador execute esse arquivo com os nossos procedimentos. Uma rotina de computador é nada mais do que um texto que instrui, de forma clara e sequencial, o que o computador deve fazer. Investe-se certo tempo para a criação do programa, porém, no futuro, esse irá executar sempre da mesma maneira o procedimento gravado. No médio e longo prazo, existe um ganho significativo de tempo entre o uso de uma rotina do computador e uma interface do tipo aponte e clique.
Além disso, o risco de erro humano na execução do procedimento é quase nulo, pois os comandos e a sua sequência estão registrados no arquivo texto e irão ser executados sempre da mesma maneira. Da mesma forma, esse aglomerado de comandos pode ser compartilhado com outras pessoas, as quais podem replicar os resultados em seus computadores. Essa é uma das grandes razões que justificam a popularização de programação na realização de pesquisa em dados. Todos os procedimentos executados podem ser replicados pelo uso de um script.
O R é uma plataforma de programação e o primeiro choque para um novo usuário é o predomínio no uso de código sobre operações com o Mouse. O R e o RStudio possuem algumas funcionalidades através do mouse, porém a sua capacidade é otimizada quando os utilizamos via inserção de comandos específicos. Quando um grupo de comandos é realizado de uma maneira inteligente, temos um script do R que deve preferencialmente produzir algo importante para nós no final de sua execução.
O R também possibilita a exportação de arquivos, tal como figuras a serem inseridas em um relatório técnico ou informações em um arquivo texto. De fato, o próprio relatório técnico pode ser dinamicamente criado dentro do R através da tecnologia RMarkdown e Quarto. Por exemplo, este livro que estás lendo foi escrito utilizando a tecnologia Quarto. O conteúdo do livro é compilado com a execução dos códigos e as suas saídas são registradas em texto. Todas as figuras e os dados do livro podem ser atualizados com a execução de um simples comando.
Provavelmente, o produto final de trabalhar com R e RStudio será um script que produz elementos para um relatório de dados. Um bom exemplo de um código simples e polido pode ser encontrado neste link. Abra-o e você verá o conteúdo de um arquivo com extensão .R que fará o download dos preços das ações de duas empresas e criará um gráfico e uma tabela. Ao terminar de ler o livro, você irá entender o que está acontecendo no código e como ele realiza o trabalho. Melhor ainda, você poderá melhorá-lo com novas funcionalidades e novas saídas. Caso esteja curioso em ver o script rodar, faça o seguinte: 1) instale R e RStudio no computador, 2) copie o conteúdo de texto do link para um novo script (“File” -> “New File” -> “R Script”), 3) salve-o com um nome qualquer e, finalizando, 4) pressione control + shift + enter para executar o script inteiro.
No R, tudo é um objeto, e cada tipo de objeto tem suas propriedades. Por exemplo, o valor de um índice de inflação ao longo do tempo – em vários meses e anos – pode ser representado como um objeto do tipo vetor numérico. As datas em si, no formato YYYY-MM-DD (ano-mês-dia), podem ser representadas como texto (character) ou a própria classe Date. Por fim, podemos representar conjuntamente os dados de inflação e as datas armazenando-os em um objeto único do tipo dataframe, o qual nada mais é do que uma tabela com linhas e colunas. Todos esses objetos fazem parte do ecossistema do R e é através da manipulação destes que tiramos o máximo proveito do software.
Os principais tipos de objetos do R são:
Enquanto representamos informações do mundo real com as diferentes classes no R, um tipo especial de objeto é a função, a qual representa um procedimento preestabelecido que está disponível para o usuário. O R possui uma grande quantidade de funções, as quais possibilitam que o usuário realize uma vasta gama de procedimentos. Por exemplo, os comandos básicos do R, não incluindo demais pacotes, somam um total de 1268 funções. Com base neles e outros iremos importar dados, calcular médias, testar hipóteses, limpar dados, e muito mais.
Cada função possui um próprio nome. Por exemplo, a função sort() é um procedimento que ordena valores utilizados como input. Caso quiséssemos ordenear os valores no vetor numérico [2, 1, 3, 0], basta inserir no prompt o seguinte comando e apertar enter:
sort(c(2, 1, 3, 0), decreasing = TRUE)R> [1] 3 2 1 0
O comando c(2, 1, 3, 0) combina os valores em um vetor (maiores detalhes sobre comando c serão dados em seção futura). Observe que a função sort() é utilizada com parênteses de início e fim. Esses parênteses servem para destacar as entradas (inputs), isto é, as informações enviadas para a função produzir alguma coisa. Observe que cada entrada (ou opção) da função é separada por uma vírgula, tal como em MinhaFuncao(entrada01, entrada02, entrada03, ...). No caso do código anterior, note que usamos a opção decreasing = TRUE. Essa é uma instrução específica para a função sort() ordenar de forma decrescente os elementos do vetor de entrada. Veja a diferença:
sort(c(2, 1, 3, 0), decreasing = FALSE)R> [1] 0 1 2 3
O uso de funções está no coração do R e iremos dedicar grande parte do livro a elas. Por enquanto, essa breve introdução já serve o seu propósito. O principal é entender que uma função usa suas entradas para produzir algo de volta. Nos próximos capítulos iremos utilizar funções já existentes para as mais diferentes finalidades: baixar dados da internet, ler arquivos, realizar testes estatísticos e muito mais. No capítulo @ref(programacao) iremos tratar deste assunto com maior profundidade, incluindo a forma de escrevermos nossas próprias funções.
Um dos comandos mais básicos no R é a definição de objetos. Como foi mostrado nas seções anteriores, pode-se definir um objeto com o uso do comando <-, o qual, para o português, é traduzido para o verbo defina (assign em inglês). Considere o seguinte código:
# set x
my_x <- 123
# set x, y and z in one line
my_x <- 1 ; my_y <- 2; my_z <- 3Lê-se esse código como x é definido como 123. A direção da seta define onde o valor será armazenado. Por exemplo, utilizar 123 -> my_x também funcionaria, apesar de ser uma sintaxe pouco utilizada ou recomendada. Note que também é possível escrever diversos comandos na mesma linha com o uso da semi-vírgula (;).
O uso do símbolo <- para a definição de objetos é específico do R. Na época da concepção da linguagem S, de onde o R foi baseado, existiam teclados com uma tecla específica que definia diretamente o símbolo de seta. Teclados contemporâneos, porém, não possuem mais esta configuração. Uma alternativa é utilizar o atalho para o símbolo, o qual, no Windows, é definido por alt + -.
É possível também usar o símbolo = para definir objetos assim como o <-. Saliento que esta é prática comum em outras linguagens de programação. Porém, no ecosistema do R, a utilização do = com esse fim específico não é recomendada. O símbolo de igualdade tem o seu uso especial e resguardado na definição de argumentos de uma função tal como sort(x = 1:10, decreasing = TRUE).
A nomeação dos objetos criados no R é importante. Tirando alguns casos específicos, o usuário pode nomear os objetos como quiser. Essa liberdade, porém, pode ser um problema. É desejável sempre dar nomes curtos que façam sentido ao conteúdo do objeto e que sejam simples de entender. Isso facilita o entendimento do código por outros usuários e faz parte das normas sugeridas para a estruturação do código do Google.
O R executa o código procurando objetos e funções disponíveis no seu ambiente de trabalho (enviromnent). Se tentarmos acessar um objeto que não existe, o R irá retornar uma mensagem de erro:
print(z)R> Error in print(z): object 'z' not found
Isso ocorre pois o objeto z não existe na sessão atual do R. Se criarmos uma variável z como z <- 123 e repetirmos o comando print(z), não teremos a mesma mensagem de erro.
Um ponto importante aqui é a definição de objetos de classes diferentes com o uso de símbolos específicos. O uso de aspas duplas (" ") ou simples (' ') define objetos da classe texto enquanto números são definidos pelo próprio valor. Conforme será mostrado, cada objeto no R tem uma classe e cada classe tem um comportamento diferente. Portanto, objetos criados com o uso de aspas pertencem à classe character. Podemos confirmar isso via código:
# set vars
x <- 1
y <- '1'
# display classes
class(x)R> [1] "numeric"
class(y)R> [1] "character"
As saídas anteriores mostram que a variável x é do tipo numérico, enquanto a variável y é do tipo texto (character). Ambas fazem parte das classes básicas de objetos no R. Por enquanto, este é o mínimo que deves saber para avançar nos próximos capítulos. Iremos estudar este assunto mais profundamente no capítulo @ref(classes-basicas).
Nos exemplos anteriores criamos objetos simples tal como x <- 1 e x <- 'abc'. Enquanto isso é suficiente para demonstrar os comandos básicos do R, na prática tais comandos são bastante limitados, uma vez que um problema real de análise de dados certamente irá ter um maior volume de informações do mundo real.
Um dos procedimentos mais utilizados no R é a criação de vetores atômicos. Esses são objetos que guardam uma série de elementos. Todos os elementos de um vetor atômico devem possuir a mesma classe, o que justifica a sua propriedade atômica. Um exemplo seria representar no R uma série de preços diários de uma ação. Tal série possui vários valores numéricos que formam um vetor da classe numérica.
Vetores atômicos são criados no R através do uso do comando c()** **, o qual é oriundo do verbo em inglês combine. Por exemplo, caso eu quisesse combinar os valores 1, 2 e 3 em um vetor, eu poderia fazê-lo através do seguinte comando:
# set vector
x <- c(1, 2, 3)
# print it
print(x)R> [1] 1 2 3
Esse comando funciona da mesma maneira para qualquer número de elementos. Caso necessário, poderíamos criar um vetor com mais elementos simplesmente adicionando valores após o 3, tal como em x <- c(1, 2, 3, 4, 5).
O uso do comando c() não é exclusivo para vetores numéricos. Por exemplo, poderíamos criar um vetor de outra classe de dados, tal como character:
y <- c('text 1', 'text 2', 'text 3', 'text 4')
print(y)R> [1] "text 1" "text 2" "text 3" "text 4"
A única restrição no uso do comando c() é que todos os itens do vetor tenham a mesma classe. Se inserirmos dados de classes diferentes, o R irá tentar transformar os itens para a mesma classe seguindo uma lógica própria, onde a classe mais complexa sempre tem preferência. Caso ele não consiga transformar todos os elementos para uma classe só, uma mensagem de erro será retornada. Observe no próximo exemplo como os valores numéricos no primeiro e segundo elemento de x são transformados para a classe de caracteres.
# numeric class
x <- c(1, 2)
class(x)R> [1] "numeric"
# character class
x <- c(1, 2, '3')
class(x)R> [1] "character"
Outra utilização do comando c() é a combinação de vetores. De fato, isto é exatamente o que fizemos ao executar o código c(1, 2, 3). Neste caso, cada vetor possuía um elemento. Podemos realizar o mesmo com vetores maiores. Veja a seguir:
# set x and y
x <- c(1, 2, 3)
y <- c(4, 5)
# print concatenation between x and y
print(c(x, y))R> [1] 1 2 3 4 5
Portanto, o comando c() possui duas funções principais: criar e combinar vetores.
Após a execução de diversos comandos no editor ou prompt, é desejável saber quais são os objetos criados pelo código. É possível descobrir essa informação simplesmente olhando para o lado direito superior do RStudio, na aba da área de trabalho. Porém, existe um comando que sinaliza a mesma informação no prompt. Com o fim de saber quais são as variáveis atualmente disponíveis na memória do R, pode-se utilizar o comando ls() . Observe o exemplo a seguir:
# set vars
x <- 1
y <- 2
z <- 3
# show current objects
ls()R> [1] "x" "y" "z"
Os objetos x, y e z foram criados e estavam disponíveis no ambiente de trabalho atual, juntamente com outros objetos. Para descobrir os valores dos mesmos, basta digitar os nomes dos objetos e apertar enter no prompt:
xR> [1] 1
yR> [1] 2
zR> [1] 3
Digitar o nome do objeto na tela tem o mesmo resultado que utilizar a função print() . De fato, ao executar o nome de uma variável, internamente o R passa esse objeto para a função print() .
No R, conforme já mostrado, todos os objetos pertencem a alguma classe. Para descobrir a classe de um objeto, basta utilizar a função class() . Observe no exemplo a seguir que x é um objeto da classe numérica e y é um objeto da classe de texto (character).
# set vars
x <- 1
y <- 'a'
# check classes
class(x)R> [1] "numeric"
class(y)R> [1] "character"
Outra maneira de conhecer melhor um objeto é verificar a sua representação em texto. Todo objeto no R possui uma representação textual e a verificação desta é realizada através da função str() :
# print textual representation of a vector
x <- 1:10
print(str(x))R> int [1:10] 1 2 3 4 5 6 7 8 9 10
R> NULL
Essa função é particularmente útil quando se está tentando entender os detalhes de um objeto mais complexo, tal como uma tabela. A utilidade da representação textual é que nela aparece o tamanho do objeto e suas classes internas. Nesse caso, o objeto x é da classe integer e possui dez elementos.
Como já vimos, é possível mostrar o valor de uma variável na tela de duas formas, digitando o nome dela no prompt ou então utilizando a função print() . Explicando melhor, a função print() é voltada para a apresentação de objetos e pode ser customizada. Por exemplo, caso tivéssemos um objeto de classe chamada MyTable que representasse um objeto tabular, poderíamos criar uma função chamada print.MyTable que irá mostrar uma tabela na tela com um formato especial tal como número de linhas, nomes das colunas, etc. A função print() , portanto, pode ser customizada para cada classe de objeto.
Porém, existem outras funções específicas para apresentar texto (e não objetos) no prompt. A principal delas é message. Essa toma como input um texto, processa-o para símbolos específicos e o apresenta na tela. Essa função é muito mais poderosa e personalizável do que print() .
Por exemplo, caso quiséssemos mostrar na tela o texto 'O valor de x é igual a 2', poderíamos fazê-lo da seguinte forma:
# set var
x <- 2
# print with message()
message('The value of x is', x)R> The value of x is2
Função message também funciona para vetores:
# set vec
x <- 2:5
# print with message()
message('The values in x are: ', x)R> The values in x are: 2345
A customização da saída da tela é possível através de comandos específicos. Por exemplo, se quiséssemos quebrar a linha da tela, poderíamos fazê-lo através do uso do caractere reservado \n:
# set char
my_text <- 'First line,\nSecond Line,\nThird Line'
# print with new lines
message(my_text)R> First line,
R> Second Line,
R> Third Line
Observe que o uso do print() não resultaria no mesmo efeito, uma vez que esse comando apresenta o texto como ele é, sem processar para efeitos específicos:
print(my_text)R> [1] "First line,\nSecond Line,\nThird Line"
Outro exemplo no uso de comandos específicos para texto é adicionar um espaçamento tab no texto apresentado com o símbolo \t. Veja a seguir:
# set char with \t
my_text_1 <- 'A and B'
my_text_2 <- '\tA and B'
my_text_3 <- '\t\tA and B'
# print with message()
message(my_text_1)R> A and B
message(my_text_2)R> A and B
message(my_text_3)R> A and B
Vale destacar que, na grande maioria dos casos de pesquisa, será necessário apenas o uso de \n para formatar textos de saída. Outras maneiras de manipular a saída de texto no prompt com base em símbolos específicos são encontradas no manual oficial do R.
Parte do processo de apresentação de texto na tela é a customização do mesmo. Para isto, existem duas funções muito úteis: paste() e format() .
A função paste() cola uma série de caracteres juntos. É uma função muito útil, a qual será utilizada intensamente para o resto dos exemplos deste livro. Observe o código a seguir:
# set chars
my_text_1 <- 'I am a text'
my_text_2 <- 'very beautiful'
my_text_3 <- 'and informative.'
# using paste and message
message(paste(my_text_1, my_text_2, my_text_3))R> I am a text very beautiful and informative.
O resultado anterior não está muito longe do que fizemos no exemplo com a função print() . Note, porém, que a função paste() adiciona um espaço entre cada texto. Caso não quiséssemos esse espaço, poderíamos usar a função paste0() :
# using paste0
message(paste0(my_text_1, my_text_2, my_text_3))R> I am a textvery beautifuland informative.
Uma alternativa a função message é cat (concatenate and print). Não é incomum encontrarmos códigos onde mensagens para o usuário são transmitidas via cat e não message. Como regra, dê preferência a message pois esta é mais fácil de controlar. Por exemplo, caso o usuário quiser silenciar uma função, omitindo todas saídas da tela, bastaria usar o comando suppressMessages.
Outra possibilidade muito útil no uso do paste() é modificar o texto entre a junção dos itens a serem colados. Por exemplo, caso quiséssemos adicionar uma vírgula e espaço (,) entre cada item, poderíamos fazer isso através do uso do argumento sep, como a seguir:
# using custom separator
message(paste(my_text_1, my_text_2, my_text_3, sep = ', '))R> I am a text, very beautiful, and informative.
Caso tivéssemos um vetor atômico com os elementos da frase em um objeto apenas, poderíamos atingir o mesmo resultado utilizando paste() o argumento collapse:
# using paste with collapse argument
my_text <-c('Eu sou um texto', 'muito bonito', 'e charmoso.')
message(paste(my_text, collapse = ', '))R> Eu sou um texto, muito bonito, e charmoso.
Prosseguindo, o comando format() é utilizado para formatar números e datas. É especialmente útil quando formos montar tabelas e buscarmos apresentar os números de uma maneira visualmente atraente. Por definição, o R apresenta uma série de dígitos após a vírgula:
# message without formatting
message(1/3)R> 0.333333333333333
Caso quiséssemos apenas dois dígitos aparecendo na tela, utilizaríamos o seguinte código:
# message with format and two digits
message(format(1/3, digits=2))R> 0.33
Tal como, também é possível mudar o símbolo de decimal:
# message with format and two digits
message(format(1/3, decimal.mark = ','))R> 0,3333333
Tal flexibilidade é muito útil quando devemos reportar resultados respeitando algum formato local tal como o Brasileiro.
Uma alternativa recente e muito interessante para o comando paste() é stringr::str_c() e stringr::str_glue() . Enquanto a primeira é quase idêntica a paste0() , a segunda tem uma maneira pecular de juntar objetos. Veja um exemplo a seguir:
library(stringr)
# define some vars
my_name <- 'Pedro'
my_age <- 23
# using base::paste0
my_str_1 <- paste0('My name is ', my_name, ' and my age is ', my_age)
# using stringr::str_c
my_str_2 <- str_c('My name is ', my_name, ' and my age is ', my_age)
# using stringr::str_glue
my_str_3 <- str_glue('My name is {my_name} and my age is {my_age}')
identical(my_str_1, my_str_2)R> [1] TRUE
identical(my_str_1, my_str_3)R> [1] FALSE
identical(my_str_2, my_str_3)R> [1] FALSE
Como vemos, temos três alternativas para o mesmo resultado final. Note que str_glue usa de chaves para definir as variáveis dentro do próprio texto. Esse é um formato muito interessante e prático para concatenar textos em um único objeto.
Na prática de programação com o R, é muito importante saber o tamanho das variáveis que estão sendo utilizadas. Isso serve não somente para auxiliar o usuário na verificação de possíveis erros do código, mas também para saber o tamanho necessário em certos procedimentos de iteração tal como loops, os quais serão tratados em capítulo futuro.
No R, o tamanho do objeto pode ser verificado com o uso de quatro principais funções: length() , nrow() , ncol() e dim() .
A função length() é destinada a objetos com uma única dimensão, tal como vetores atômicos:
# set x
x <- c(2, 3, 3, 4, 2, 1)
# get length x
n <- length(x)
# display message
message(paste('The length of x is', n))R> The length of x is 6
Para objetos com mais de uma dimensão, por exemplo matrizes e dataframes, utilizam-se as funções nrow() , ncol() e dim() para descobrir o número de linhas (primeira dimensão) e o número de colunas (segunda dimensão). Veja a diferença a seguir.
# set matrix and print it
x <- matrix(1:20, nrow = 4, ncol = 5)
print(x)R> [,1] [,2] [,3] [,4] [,5]
R> [1,] 1 5 9 13 17
R> [2,] 2 6 10 14 18
R> [3,] 3 7 11 15 19
R> [4,] 4 8 12 16 20
# find number of rows, columns and elements
my_nrow <- nrow(x)
my_ncol <- ncol(x)
my_length <- length(x)
# print message
message(paste('\nThe number of lines in x is ', my_nrow))R>
R> The number of lines in x is 4
message(paste('\nThe number of columns in x is ', my_ncol))R>
R> The number of columns in x is 5
message(paste('\nThe number of elements in x is ', my_length))R>
R> The number of elements in x is 20
Já a função dim() mostra a dimensão do objeto, resultando em um vetor numérico como saída. Essa deve ser utilizada quando o objeto tiver mais de duas dimensões. Na prática, esses casos são raros. Um exemplo para a variável x é dado a seguir:
print(dim(x))R> [1] 4 5
Para o caso de objetos com mais de duas dimensões, podemos utilizar a função array para criá-los e dim() para descobrir o seu tamanho:
# set array with dimension
my_array <- array(1:9, dim = c(3,3,3))
# print it
print(my_array)R> , , 1
R>
R> [,1] [,2] [,3]
R> [1,] 1 4 7
R> [2,] 2 5 8
R> [3,] 3 6 9
R>
R> , , 2
R>
R> [,1] [,2] [,3]
R> [1,] 1 4 7
R> [2,] 2 5 8
R> [3,] 3 6 9
R>
R> , , 3
R>
R> [,1] [,2] [,3]
R> [1,] 1 4 7
R> [2,] 2 5 8
R> [3,] 3 6 9
# print its dimension
print(dim(my_array))R> [1] 3 3 3
Reforçando, cada objeto no R tem suas propriedades e funções específicas para manipulação.
Uma observação importante aqui é que as funções anteriores não servem para descobrir o número de letras em um texto. Esse é um erro bastante comum. Por exemplo, caso tivéssemos um objeto do tipo texto e usássemos a função length() , o resultado seria o seguinte:
# set char object
my_char <- 'abcde'
# find its length (and NOT number of characters)
print(length(my_char))R> [1] 1
Isso ocorre pois a função length() retorna o número de elementos. Nesse caso, my_char possui apenas um elemento. Para descobrir o número de caracteres no objeto, utilizamos a função nchar() , conforme a seguir:
# using nchar for number of characters
print(nchar(my_char))R> [1] 5