No R, tudo é um objeto e cada classe de objeto tem propriedades diferentes. As classes básicas são os elementos mais primários na representação de dados no R. Por exemplo, um objeto do tipo tabela pode ser incrementado com novas colunas ou linhas. Um objeto do tipo vetor numérico pode interagir com outros valores numéricos através de operações de multiplicação, divisão e soma. Para objetos contendo texto, porém, tal propriedade não é válida, uma vez que não faz sentido somar um valor numérico a um texto ou dividir um texto por outro. Entretanto, a classe de texto tem outras propriedades, como a que permite procurar uma determinada sequência textual dentro de um texto maior, a manipulação de partes do texto e a substituição de caracteres específicos, dentre tantas outras possibilidades. Um dos aspectos mais importantes no trabalho com o R é o aprendizado das funcionalidades e propriedades de objetos básicos.
Neste capítulo iremos estudar mais a fundo as classes básicas de objetos do R, incluindo a sua criação até a manipulação do seu conteúdo. Este capítulo é de suma importância pois mostrará quais operações são possíveis com cada classe de objeto e como podes manipular as informações de forma eficiente. Os tipos de objetos tratados aqui serão:
Numéricos (numeric)
Texto (character)
Fatores (factor)
Valores lógicos (logical)
Datas e tempo (Date e ddtm)
Dados Omissos (NA)
6.1 Objetos Numéricos
Uma das classes mais utilizadas no R. Os valores numéricos são representações de uma quantidade. Por exemplo: o preço de uma ação em determinada data, o volume negociado de um contrato financeiro em determinado dia, a inflação anual de um país, entre várias outras possibilidades.
6.1.1 Criando e Manipulando Vetores Numéricos
A criação e manipulação de valores numéricos é fácil e direta. Os símbolos de operações matemáticas seguem o esperado, tal como soma (+), diminuição (-), divisão (/) e multiplicação (*). Todas as operações matemáticas são efetuadas com a orientação de elemento para elemento e possuem notação vetorial. Isso significa, por exemplo, que podemos manipular vetores inteiros em uma única linha de comando. Veja a seguir, onde se cria dois vetores e realiza-se diversas operações entre eles.
Um diferencial do R em relação a outras linguagens é que, nele, são aceitas operações entre vetores diferentes. Por exemplo, podemos somar um vetor numérico de quatro elementos com outro de apenas dois. Nesse caso, aplica-se a chamada regra de reciclagem (recycling rule). Ela define que, se dois vetores de tamanho diferente estão interagindo, o vetor menor é repetido tantas vezes quantas forem necessárias para obter-se o mesmo número de elementos do vetor maior. Veja o exemplo a seguir:
# set x with 4 elements and y with 2x <-1:4y <-2:1# print sumprint(x + y)
R> [1] 3 3 5 5
O resultado de x + y é equivalente a 1:4 + c(2, 1, 2, 1). Caso interagirmos vetores em que o tamanho do maior não é múltiplo do menor, o R realiza o mesmo procedimento de reciclagem, porém emite uma mensagem de warning:
# set x = 4 elements and y with 3x <-c(1, 2, 3, 4)y <-c(1, 2, 3)# print sum (recycling rule)print(x +y)
R> Warning in x + y: longer object length is not a multiple of
R> shorter object length
R> [1] 2 4 6 5
Os três primeiros elementos de x foram somados aos três primeiros elementos de y. O quarto elemento de x foi somado ao primeiro elemento de y. Uma vez que não havia um quarto elemento em y, o ciclo reinicia, resgatando o primeiro elemento de y e resultando em uma soma igual a 5.
Os elementos de um vetor numérico também podem ser nomeados quando na criação do vetor:
Para nomear os elementos após a criação, podemos utilizar a função names() . Veja a seguir:
# create unnamed vectorx <-c(10, 14, 9, 2)# set names of elementsnames(x) <-c('item1', 'item2', 'item3', 'item4')# print itprint(x)
R> item1 item2 item3 item4
R> 10 14 9 2
Vetores numéricos vazios também podem ser criados. Em algumas situações de desenvolvimento de código faz sentido pré-alocar o vetor antes de preenchê-lo com valores. Nesse caso, utilize a função numeric() :
Observe que, nesse caso, os valores de my_x são definidos como zero.
6.1.1.1 Criando Sequências de Valores
Existem duas maneiras de criar uma sequência de valores no R. A primeira, que já foi utilizada nos exemplos anteriores, é o uso do operador :, tal como em my_seq <- 1:10 e my_seq <- -5:5. Esse método é bastante prático, pois a notação é clara e direta.
Porém, o uso do operador : limita as possibilidades. A diferença entre os valores adjacentes é sempre 1 para sequências ascendentes e -1 para sequências descendentes. Função seq() é uma versão mais poderosa do operador :, possibilitando sequências customizadas com argumento by.
# set sequence from -10 to 10, by 2 my_seq <-seq(from =-10, to =10, by =2)# print itprint(my_seq)
R> [1] -10 -8 -6 -4 -2 0 2 4 6 8 10
Outro atributo interessante da função seq() é a possibilidade de criar vetores com um valor inicial, um valor final e o número de elementos desejado. Isso é realizado com o uso da opção length.out. Observe o código a seguir, onde cria-se um vetor de 0 até 10 com 20 elementos:
# set sequence with fixed sizemy_seq <-seq(from =0, to =10, length.out =20)# print itprint(my_seq)
No caso anterior, o tamanho final do vetor foi definido e a própria função se encarregou de descobrir qual a variação necessária entre cada valor de my_seq.
6.1.1.2 Criando Vetores com Elementos Repetidos
Outra função interessante é a que cria vetores com o uso de repetição. Por exemplo: imagine que estamos interessado em um vetor preenchido com o valor 1 dez vezes. Para isso, basta utilizar a função rep() :
# repeat vector three timesmy_x <-rep(x =1, times =10)# print itprint(my_x)
R> [1] 1 1 1 1 1 1 1 1 1 1
A função também funciona com vetores. Considere uma situação onde temos um vetor com os valores c(1,2) e gostaríamos de criar um vetor maior com os elementos c(1, 2, 1, 2, 1, 2) - isto é, repetindo o vetor menor três vezes. Veja o resultado a seguir:
# repeat vector three timesmy_x <-rep(x =c(1, 2), times =3)# print itprint(my_x)
R> [1] 1 2 1 2 1 2
6.1.1.3 Criando Vetores com Números Aleatórios
Em muitas situações será necessário a criação de números aleatórios. Esse procedimento numérico é bastante utilizado para simular modelos matemáticos em Finanças. Por exemplo, o método de simulação de preços de ativos de Monte Carlo parte da simulação de números aleatórios (McLeish 2011). No R, existem diversas funções que criam números aleatórios para diferentes distribuições estatísticas. As mais utilizadas, porém, são as funções rnorm() e runif() .
A função rnorm() gera números aleatórios da distribuição Normal, com opções para a média (tendência) e o desvio padrão (variabilidade). Veja o seu uso a seguir:
# generate 10000 random numbers from a Normal distributionmy_rnd_vec <-rnorm(n =10000,mean =0,sd =1)# print first 20 elementsprint(my_rnd_vec[1:20])
O código anterior gera uma grande quantidade de números aleatórios de uma distribuição Normal com média zero e desvio padrão igual a um.
Função runif() também gera valores aleatórios, porém da distribuição uniforme e dentro de um intervalor. Ela é geralmente utilizada para simular probabilidades, valores entre 0 e 1. A função runif() tem três parâmetros de entrada: o número de valores aleatórios desejado, o valor mínimo e o valor máximo. Veja exemplo a seguir:
# create a random vector with minimum and maximummy_rnd_vec <-runif(n =5,min =-5,max =5)# print itprint(my_rnd_vec)
Observe que ambas as funções anteriores são limitadas à suas respectivas distribuições. Uma maneira alternativa e flexível de gerar valores aleatórios é utilizar a função sample() . Essa tem como entrada um vetor qualquer e retorna uma versão embaralhada de seus elementos. A sua flexibilidade reside no fato de que o vetor de entrada pode ser qualquer coisa. Por exemplo, caso quiséssemos criar um vetor aleatório com os números c(0, 5, 15, 20, 25) apenas, poderíamos fazê-lo da seguinte forma:
A função sample() também permite a seleção aleatória de um certo número de termos. Por exemplo, caso quiséssemos selecionar aleatoriamente apenas um elemento de my_vec, escreveríamos o código da seguinte maneira:
# sample one element of my_vecmy_rnd_vec <-sample(my_vec, size =1)# print itprint(my_rnd_vec)
R> [1] 5
Caso quiséssemos dois elementos, escreveríamos:
# sample two elements of my_vecmy_rnd_vec <-sample(my_vec, size =2)# print itprint(my_rnd_vec)
R> [1] 15 10
Também é possível selecionar valores de uma amostra menor para a criação de um vetor maior. Por exemplo, considere o caso em que se tem um vetor com os números c(10, 15, 20) e deseja-se criar um vetor aleatório com dez elementos retirados desse vetor menor, com repetição. Para isso, podemos utilizar a opção replace = TRUE.
Vale destacar que a função sample() funciona para qualquer tipo ou objeto, não sendo, portanto, exclusiva para vetores numéricos. Poderíamos, também, escolher elementos aleatórios de um vetor de texto ou então uma lista:
# example of sample with charactersprint(sample(c('elem 1', 'elem 2', 'elem 3'),size =1))
R> [1] "elem 1"
# example of sample with listprint(sample(list(x =c(1,1,1),y =c('a', 'b')),size =1))
R> $x
R> [1] 1 1 1
É importante ressaltar que a geração de valores aleatórios no R (ou qualquer outro programa) não é totalmente aleatória! De fato, o próprio computador escolhe os valores dentre uma fila de valores possíveis. Cada vez que funções tal como rnorm() , runif() e sample() são utilizadas, o computador escolhe um lugar diferente dessa fila de acordo com vários parâmetros, incluindo a data e o horário atual do sistema. Portanto, do ponto de vista do usuário, os valores são gerados de forma imprevisível. Para o computador, porém, essa seleção é determinística e previsível.
Uma possibilidade interessante no R é selecionar uma posição específica na fila de valores aleatórios utilizando função set.seed() . É ela que fixa a semente para gerar os valores. Na prática, o resultado é que todos os números e seleções aleatórias realizadas pelo código serão iguais em cada execução, independente do computador ou horário. O uso de set.seed() é bastante recomendado para manter a reprodutibilidade dos códigos envolvendo aleatoriedade. Veja o exemplo a seguir, onde utiliza-se essa função.
# fix seedset.seed(seed =10)# set vec and printmy_rnd_vec_1 <-runif(5)print(my_rnd_vec_1)
No código anterior, o valor de set.seed() é um inteiro escolhido pelo usuário. Após a chamada de set.seed() , todas as seleções e números aleatórios irão iniciar do mesmo ponto e, portanto, serão iguais. Motivo o leitor a executar o código anterior em sua sessão do R. Verás que os valores de my_rnd_vec_1 e my_rnd_vec_2 serão exatamente iguais aos valores colocados aqui.
O uso de set.seed() também funciona para o caso de sample() . Veja a seguir:
Novamente, execute os comandos anteriores no R e verás que o resultado na tela bate com o apresentado aqui.
6.1.2 Acessando Elementos de um Vetor Numérico
Todos os elementos de um vetor numérico podem ser acessados através do uso de colchetes ([ ]). Por exemplo, caso quiséssemos apenas o primeiro elemento de x, teríamos:
# set vectorx <-c(-1, 4, -9, 2)# get first elementfirst_elem_x <- x[1]# print itprint(first_elem_x)
R> [1] -1
A mesma notação é válida para extrair porções de um vetor. Caso quiséssemos um subvetor de x com o primeiro e o segundo elemento, faríamos essa operação da seguinte forma:
# sub-vector of xsub_x <- x[1:2]# print itprint(sub_x)
R> [1] -1 4
Para acessar elementos nomeados de um vetor numérico, basta utilizar seu nome junto aos colchetes.
# set named vectorx <-c(item1 =10, item2 =14, item3 =-9, item4 =-2)# access elements by nameprint(x['item2'])
R> item2
R> 14
print(x[c('item2','item4')])
R> item2 item4
R> 14 -2
O acesso aos elementos de um vetor numérico também é possível através de testes lógicos. Por exemplo, caso tivéssemos interesse em saber quais os valores de x que são maiores do que 0, o código resultante seria da seguinte forma:
# find all values of x higher than zeroprint(x[x >0])
R> item1 item2
R> 10 14
Os usos de regras de segmentação dos dados de acordo com algum critério é chamado de indexação lógica. Os objetos do tipo logical serão tratados mais profundamente em seção futura deste capítulo.
6.1.3 Modificando e Removendo Elementos de um Vetor Numérico
A modificação de um vetor numérico é muito simples. Basta indicar a posição dos elementos e os novos valores com o símbolo de assign (<-):
# set vectormy_x <-1:4# modify first element to 5my_x[1] <-5# print resultprint(my_x)
R> [1] 5 2 3 4
Essa modificação também pode ser realizada em bloco:
# set vectormy_x <-0:5# set the first three elements to 5my_x[1:3] <-5# print resultprint(my_x)
R> [1] 5 5 5 3 4 5
O uso de condições para definir elementos é realizada pela indexação:
# set vectormy_x <--5:5# set any value lower than 2 to 0my_x[my_x<2] <-0# print resultprint(my_x)
R> [1] 0 0 0 0 0 0 0 2 3 4 5
A remoção de elementos é realizada com o uso de índices negativos:
# create vectormy_x <--5:5# remove first and second element of my_xmy_x <- my_x[-(1:2)]# show resultprint(my_x)
R> [1] -3 -2 -1 0 1 2 3 4 5
Note como o uso do índice negativo em my_x[-(1:2)] retorna o vetor original sem o primeiro e segundo elemento.
6.1.4 Criando Grupos
Algumas situações de análise de dados requerem que grupos numéricos sejam identificados. Por exemplo, imagine um conjunto de idades de pessoas em determinada cidade. Uma possível análise seria dividir as idades em intervalos, e verificar o percentual de ocorrência dos valores em cada um destes. Esta análise numérica é bastante semelhante à construção e visualização de histogramas.
A função cut() serve para criar grupos de intervalos a partir de um vetor numérico. Veja o exemplo a seguir, onde cria-se um vetor aleatório oriundo da distribuição Normal e cinco grupos a partir de intervalos definidos pelos dados.
# set rnd vecmy_x <-rnorm(10)# "cut" it into 5 piecesmy_cut <-cut(x = my_x, breaks =5)print(my_cut)
Observe que os nomes dos elementos da variável my_cut são definidos pelos intervalos e o resultado é um objeto do tipo fator. Em seções futuras, iremos explicar melhor esse tipo de objeto e as suas propriedades.
No exemplo anterior, os intervalos para cada grupo foram definidos automaticamente. No uso da função cut() , também é possível definir quebras customizadas nos dados e nos nomes dos grupos. Veja a seguir:
# set random vectormy_x <-rnorm(10)# create groups with 5 breaksmy_cut <-cut(x = my_x, breaks =5)# print it!print(my_cut)
Note que os nomes dos elementos em my_cut foram definidos como intervalos e o resultado é um objeto do tipo fator. É possível também definir intervalos e nomes customizados para cada grupo com o uso dos argumentos labels e breaks:
R> [1] Normal High Normal High Normal Normal Normal Normal
R> [9] Normal High
R> Levels: Low Normal High
Como podemos ver, os nomes dos grupos estão mais amigáveis para uma futura análise. Adicionalmente, uma função muito útil para contar o número de casos é table() :
# print counttable(my_cut)
R> my_cut
R> Low Normal High
R> 0 7 3
6.1.5 Outras Funções Úteis
as.numeric() - Converte determinado objeto para numérico.
my_text <-c('1', '2', '3')class(my_text)
R> [1] "character"
my_x <-as.numeric(my_text)print(my_x)
R> [1] 1 2 3
class(my_x)
R> [1] "numeric"
sum() - Soma os elementos de um vetor.
my_x <-1:50my_sum <-sum(my_x)print(my_sum)
R> [1] 1275
max() - Retorna o máximo valor numérico do vetor.
x <-c(10, 14, 9, 2)max_x <-max(x)print(max_x)
R> [1] 14
min() - Retorna o mínimo valor numérico do vetor.
x <-c(12, 15, 9, 2)min_x <-min(x)print(min_x)
R> [1] 2
which.max() - Retorna a posição do máximo valor numérico do vetor.
x <-c(100, 141, 9, 2)which.max_x <-which.max(x)cat(paste('The position of the maximum value of x is ', which.max_x))
R> The position of the maximum value of x is 2
cat(' and its value is ', x[which.max_x])
R> and its value is 141
which.min() - Retorna a posição do mínimo valor numérico do vetor.
x <-c(10, 14, 9, 2)which.min_x <-which.min(x)cat(paste('The position of the minimum value of x is ', which.min_x, ' and its value is ', x[which.min_x]))
R> The position of the minimum value of x is 4 and its value is 2
A classe de caracteres, ou texto, serve para armazenar informações textuais. Um exemplo prático seria analisar os tweets de determinada personalidade ao longo do tempo. Este tipo de dado tem sido utilizado cada vez mais em pesquisa empírica (Gentzkow, Kelly, e Taddy 2017), resultando em uma diversidade de pacotes.
O R possui vários recursos que facilitam a criação e manipulação de objetos de tipo texto. As funções básicas fornecidas com a instalação de R são abrangentes e adequadas para a maioria dos casos. No entanto, pacote {stringr}(Wickham 2023b) do universo {tidyverse}(Wickham 2023c) fornece muitas funções que expandem as funcionalidades básicas do R.
Um aspecto positivo de {stringr}(Wickham 2023b) é que as funções começam com o nome str_ e possuem nomes informativos. Combinando isso com o recurso de preenchimento automático (autocomplete) pela tecla tab, fica fácil de localizar os nomes das funções do pacote. Seguindo a prioridade ao universo do {tidyverse}(Wickham 2023c), esta seção irá dar preferência ao uso das funções do pacote {stringr}(Wickham 2023b). As rotinas nativas de manipulação de texto serão apresentadas, porém de forma limitada.
6.2.1 Criando um Objeto Simples de Caracteres
Todo objeto de caracteres é criado através da encapsulação de um texto por aspas duplas (" ") ou simples (' '). Para criar um vetor de caracteres com tickers de ações, podemos fazê-lo com o seguinte código:
Confirma-se a classe do objeto com a função class() :
class(my_assets)
R> [1] "character"
6.2.2 Criando Objetos Estruturados de Texto
Em muitos casos no uso do R, estaremos interessados em criar vetores de texto com algum tipo de estrutura própria. Por exemplo, o vetor c("text 1", "text 2", ..., "text 20") possui um lógica de criação clara. Computacionalmente, podemos definir a sua estrutura como sendo a junção do texto text e um vetor de sequência, de 1 até 20.
Para criar um vetor textual capaz de unir texto com número, utilizamos a função stringr::str_c() ou paste() . Veja o exemplo a seguir, onde replica-se o caso anterior com e sem espaço entre número e texto:
O mesmo procedimento também pode ser realizado com vetores de texto. Veja a seguir:
# set character valuemy_x <-'My name is'# set character vectormy_names <-c('Marcelo', 'Ricardo', 'Tarcizio')# paste and printprint(str_c(my_x, my_names, sep =' '))
R> [1] "My name is Marcelo" "My name is Ricardo"
R> [3] "My name is Tarcizio"
Outra possibilidade de construção de textos estruturados é a repetição do conteúdo de um objeto do tipo caractere. No caso de texto, utiliza-se a função stringr::str_dup() /strrep() para esse fim. Observe o exemplo a seguir:
my_char <-str_dup(string ='abc', times =5)print(my_char)
R> [1] "abcabcabcabcabc"
6.2.3 Objetos Constantes de Texto
O R também possibilita o acesso direto a todas as letras do alfabeto. Esses estão guardadas nos objetos reservados chamados letters e LETTERS:
# print all letters in alphabet (no cap)print(letters)
R> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n"
R> [15] "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
# print all letters in alphabet (WITH CAP)print(LETTERS)
R> [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N"
R> [15] "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"
Observe que em ambos os casos não é necessário criar os objetos. Por serem constantes embutidas automaticamente na área de trabalho do R pelo carregamento do pacote {base}(R Core Team 2023), elas já estão disponíveis para uso. Podemos sobrescrever o nome do objeto com outro conteúdo, porém isso não é aconselhável. Nunca se sabe onde esse objeto constante está sendo usado. Outros objetos de texto constantes no R incluem month.abb e month.name. Veja a seguir o seu conteúdo:
# print abreviation and full names of monthsprint(month.abb)
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, 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 varx <-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 vecx <-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 charmy_text <-'First line,\nSecond Line,\nThird Line'# print with new linesmessage(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 \tmy_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 charsmy_text_1 <-'I am a text'my_text_2 <-'very beautiful'my_text_3 <-'and informative.'# using paste and messagemessage(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 paste0message(paste0(my_text_1, my_text_2, my_text_3))
R> I am a textvery beautifuland informative.
Importante
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 separatormessage(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 argumentmy_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 formattingmessage(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 digitsmessage(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 digitsmessage(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 peculiar de juntar objetos. Veja um exemplo a seguir:
library(stringr)# define some varsmy_name <-'Pedro'my_age <-23# using base::paste0my_str_1 <-paste0('My name is ', my_name, ' and my age is ', my_age)# using stringr::str_cmy_str_2 <-str_c('My name is ', my_name, ' and my age is ', my_age)# using stringr::str_gluemy_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 stringr::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.
6.2.5 Selecionando Pedaços de um Texto
Um erro comum praticado por iniciantes é tentar selecionar pedaços de um texto através do uso de colchetes. Observe o código abaixo:
# set char objectmy_char <-'ABCDE'# print its second character: 'B' (WRONG - RESULT is NA)print(my_char[2])
R> [1] NA
O resultado NA indica que o segundo elemento de my_char não existe. Isso acontece porque o uso de colchetes refere-se ao acesso de elementos de um vetor atômico, e não de caracteres dentro de um texto maior. Observe o que acontece quando utilizamos my_char[1]:
print(my_char[1])
R> [1] "ABCDE"
O resultado é simplesmente o texto ABCDE, que está localizado no primeiro item de my_char. Para selecionar pedaços de um texto, devemos utilizar a função específica stringr::str_sub() /substr() :
# print third and fourth charactersmy_substr <-str_sub(string = my_char,start =4,end =4)print(my_substr)
R> [1] "D"
Esta função também funciona para vetores atômicos. Vamos assumir que você importou dados de texto e o conjunto de dados bruto contém um identificador de 3 dígitos de uma empresa, sempre na mesma posição do texto. Vamos simular a situação no R:
# build char vecmy_char_vec <-paste0(c('123','231','321'),' - other ignorable text')print(my_char_vec)
R> [1] "123 - other ignorable text"
R> [2] "231 - other ignorable text"
R> [3] "321 - other ignorable text"
Só estamos interessados na informação das três primeiras letras de cada elemento em my_char_vec. Para selecioná-los, podemos usar as mesmas funções que antes.
# get ids with stringr::str_subids.vec <-str_sub(my_char_vec, 1, 3)print(ids.vec)
R> [1] "123" "231" "321"
Importante
Operações vetorizadas são comuns e esperadas no R. Quase tudo o que você pode fazer para um único elemento pode ser expandido para vetores. Isso facilita o desenvolvimento de rotinas pois pode-se facilmente realizar tarefas complicadas em uma série de elementos, em uma única linha de código.
6.2.6 Localizando e Substituindo Pedaços de um Texto
Uma operação útil na manipulação de textos é a localização de letras e padrões específicos com funções stringr::str_locate() /regexpr() e stringr::str_locate_all() /gregexpr() . É importante destacar que estas funções utilizam de expressões do tipo regex - expressões regulares (Thompson 1968) - uma linguagem de computador específica para processar textos. Diversos símbolos são utilizados para estruturar, procurar e isolar padrões textuais.
Usualmente, o caso mais comum em pesquisa é verificar a posição ou a existência de um texto menor dentro de um texto maior. Isto é, um padrão explícito e fácil de entender. Por isso, a localização e substituição de caracteres no próximo exemplo será do tipo fixo, sem o uso de regex. Tal informação pode ser passada às funções do pacote {stringr}(Wickham 2023b) através de outra função chamada stringr::fixed() .
O exemplo a seguir mostra como encontrar o caractere D dentre uma série de caracteres.
Observe que a função stringr::str_locate() retorna apenas a primeira ocorrência de D. Para resgatar todas as ocorrências, devemos utilizar a função stringr::str_locate_all() :
# set objectmy_char <-'ABCDEF-ABCDEF-ABC'# find position of ALL 'D' using str_locate_allpos =str_locate_all(string = my_char, pattern =fixed('D'))print(pos)
R> [[1]]
R> start end
R> [1,] 4 4
R> [2,] 11 11
Para substituir caracteres em um texto, basta utilizar a função stringr::str_replace() ou sub() e stringr::str_locate_all() ou gsub() . Vale salientar que str_replace substitui a primeira ocorrência do caractere, enquanto stringr::str_locate_all() executa uma substituição global - isto é, aplica-se a todas as ocorrências. Veja a diferença a seguir:
# set char objectmy_char <-'ABCDEF-ABCDEF-ABC'# substitute the FIRST 'ABC' for 'XXX' with submy_char <-sub(x = my_char,pattern ='ABC',replacement ='XXX')print(my_char)
R> [1] "XXXDEF-ABCDEF-ABC"
# substitute the FIRST 'ABC' for 'XXX' with str_replacemy_char <-'ABCDEF-ABCDEF-ABC'my_char <-str_replace(string = my_char,pattern =fixed('ABC'),replacement ='XXX')print(my_char)
R> [1] "XXXDEF-ABCDEF-ABC"
E agora fazemos uma substituição global dos caracteres.
# set char objectmy_char <-'ABCDEF-ABCDEF-ABC'# substitute the FIRST 'ABC' for 'XXX' with str_replacemy_char <-str_replace_all(string = my_char,pattern ='ABC',replacement ='XXX')print(my_char)
R> [1] "XXXDEF-XXXDEF-XXX"
Mais uma vez, vale ressaltar que as operações de substituição também funcionam em vetores. Dê uma olhada no próximo exemplo.
# set char objectmy_char <-c('ABCDEF','DBCFE','ABC')# create an example of vectormy_char_vec <-str_c(sample(my_char, 5, replace = T),sample(my_char, 5, replace = T),sep =' - ')# show itprint(my_char_vec)
Em algumas situações, principalmente no processamento de textos, é possível que se esteja interessado em quebrar um texto de acordo com algum separador. Por exemplo, o texto abc;bcd;adf apresenta informações demarcadas pelo símbolo ;. Para separar um texto em várias partes, utilizamos a função stringr::str_split() /strsplit() . Essas quebram o texto em diversas partes de acordo com algum caractere escolhido. Observe os exemplos a seguir:
# set charmy_char <-'ABCXABCXBCD'# split it based on 'X' and using stringr::str_splitsplit_char <-str_split(my_char, 'X')# print resultprint(split_char)
R> [[1]]
R> [1] "ABC" "ABC" "BCD"
A saída dessa função é um objeto do tipo lista. Para acessar os elementos de uma lista, deve-se utilizar o operador [[ ]]. Por exemplo, para acessar o texto bcd da lista split_char, executa-se o seguinte código:
print(split_char[[1]][2])
R> [1] "ABC"
Para visualizar um exemplo de dividir textos em vetores, veja o próximo código.
# set charmy_char_vec <-c('ABCDEF','DBCFE','ABFC','ACD')# split it based on 'B' and using stringr::strsplitsplit_char <-str_split(my_char_vec, 'B')# print resultprint(split_char)
Observe como, novamente, um objeto do tipo list é retornado. Cada elemento é correspondente ao processo de quebra de texto em my_char.
6.2.8 Descobrindo o Número de Caracteres de um Texto
Para descobrir o número de caracteres de um texto, utilizamos a função stringr::str_length() /nchar() . Ela também funciona para vetores atômicos de texto. Veja os exemplos mostrados a seguir:
# set charmy_char <-'abcdef'# print number of characters using stringr::str_lengthprint(str_length(my_char))
R> [1] 6
E agora um exemplo com vetores.
#set charmy_char <-c('a', 'ab', 'abc')# print number of characters using stringr::str_lengthprint(str_length(my_char))
R> [1] 1 2 3
6.2.9 Gerando Combinações de Texto
Um truque útil no R é usar as funções expand.grid() e tidyr::expand_grid() para criar todas as combinações possíveis de elementos em diferentes objetos. Isso é útil quando você quer criar um vetor de texto combinando todos os elementos possíveis de diferentes vetores. Por exemplo, se quisermos criar um vetor com todas as combinações entre dois vetores de texto, podemos escrever:
library(tidyverse)# set vectorsmy_vec_1 <-c('John ', 'Claire ', 'Adam ')my_vec_2 <-c('is fishing.', 'is working.')# create df with all combinationsmy_df <- tidyr::expand_grid(name = my_vec_1,verb = my_vec_2)# print dfprint(my_df)
R> # A tibble: 6 × 2
R> name verb
R> <chr> <chr>
R> 1 "John " is fishing.
R> 2 "John " is working.
R> 3 "Claire " is fishing.
R> 4 "Claire " is working.
R> 5 "Adam " is fishing.
R> 6 "Adam " is working.
# paste columns together in tibblemy_df <- my_df |>mutate(phrase =paste0(name, verb) )# print resultprint(my_df)
R> # A tibble: 6 × 3
R> name verb phrase
R> <chr> <chr> <chr>
R> 1 "John " is fishing. John is fishing.
R> 2 "John " is working. John is working.
R> 3 "Claire " is fishing. Claire is fishing.
R> 4 "Claire " is working. Claire is working.
R> 5 "Adam " is fishing. Adam is fishing.
R> 6 "Adam " is working. Adam is working.
Aqui, usamos a função tidyr::expand_grid() para criar um dataframe contendo todas as combinações possíveis de my_vec_1 e my_vec_2. Posteriormente, colamos o conteúdo das colunas do dataframe usando stringr::str_c() .
6.2.10 Codificação de Objetos character
Para o R, um string de texto é apenas uma sequência de bytes. A tradução de bytes para caracteres é realizada de acordo com uma estrutura de codificação. Em dados de textos oriundos de países de língua inglesa, a codificação de caracteres não é um problema pois os textos importados no R já possuem a codificação correta. Ao lidar com dados de texto em diferentes idiomas, tal como Português do Brasil, a codificação de caracteres é algo que você deve entender pois eventualmente precisará lidar com isso.
Vamos explorar um exemplo. Aqui, vamos importar dados de um arquivo de texto com a codificação 'ISO-8859-9' e verificar o resultado.
# read text filemy_f <- introR::data_path('CH07_FileWithLatinChar_Latin1.txt')my_char <- readr::read_lines(my_f)# print itprint(my_char)
R> [1] "A casa \xe9 bonita e tem muito espa\xe7o"
O conteúdo original do arquivo é um texto em português. Como você pode ver, a saída de readr::read_lines() mostra todos os caracteres latinos com símbolos estranhos. Isso ocorre pois a codificação foi manualmente trocada no arquivo para 'ISO-8859-9', enquanto a função readr::read_lines() utiliza 'UTF-8' como padrão. A solução mais fácil e direta é modificar a codificação esperada do arquivo nas entradas de readr::read_lines() . Veja a seguir, onde importamos um arquivo com a codificação correta ('Latin1'):
Os caracteres latinos agora estão corretos pois a codificação em readr::read_lines() é a mesma do arquivo, 'Latin1'. Uma boa política neste tópico é sempre verificar a codificação de arquivos de texto importados e combiná-lo em R. A maioria das funções de importação tem uma opção para fazê-lo. Quando possível, sempre dê preferência para 'UTF-8'. Caso necessário, programas de edição de texto, tal como o notepad++, possuem ferramentas para verificar e trocar a codificação de um arquivo.
6.2.11 Outras Funções Úteis
stringr::str_to_lower() /tolower() - Converte um objeto de texto para letras minúsculas.
print(stringr::str_to_lower('ABC'))
R> [1] "abc"
stringr::str_to_upper() /toupper() - Converte um texto em letras maiúsculas.
print(toupper('abc'))
R> [1] "ABC"
print(stringr::str_to_upper('abc'))
R> [1] "ABC"
6.3 Fatores
A classe de fatores (factor() ) é utilizada para representar grupos ou categorias dentro de uma base de dados no formato tabular. Por exemplo, imagine um banco de informações com os gastos de diferentes pessoas ao longo de um ano. Nessa base de dados existe um item que define o gênero do indivíduo: masculino ou feminino (M ou F). Essa respectiva coluna pode ser importada e representada como texto, porém, no R, a melhor maneira de representá-la é através do objeto fator, uma vez que a mesma representa uma categoria.
A classe de fatores oferece um significado especial para denotar grupos dentro dos dados. Essa organização é integrada aos pacotes e facilita muito a vida do usuário. Por exemplo, caso quiséssemos criar um gráfico para cada grupo dentro da nossa base de dados, poderíamos fazer o mesmo simplesmente indicando a existência de uma variável de fator para a função de criação da figura. Outra possibilidade é determinar se as diferentes médias de uma variável numérica são estatisticamente diferentes para os grupos dos nossos dados. Podemos também estimar um determinado modelo estatístico para cada grupo. Quando os dados de categorias são representados apropriadamente, o uso das funções do R torna-se mais fácil e eficiente.
6.3.1 Criando Fatores
A criação de fatores dá-se através da função factor() :
Observe, no exemplo anterior, que a apresentação de fatores com a função print() mostra os seus elementos e também o item chamado Levels. Esse último identifica os possíveis grupos que abrangem o vetor - nesse caso apenas M e F. Se tivéssemos um número maior de grupos, o item Levels aumentaria.
Um ponto importante na criação de fatores é que os Levels são inferidos através dos dados criados, e isso pode não corresponder à realidade. Por exemplo, observe o seguinte exemplo:
Nota-se que, por ocasião, os dados mostram apenas uma categoria: Solteiro. Entretanto, sabe-se que outra categoria do tipo Casado é esperada. No caso de utilizarmos o objeto my_status da maneira que foi definida anteriormente, omitiremos a informação de outros gêneros, e isso pode ocasionar problemas no futuro tal como a criação de gráficos incompletos. Nessa situação, o correto é definir os Levels manualmente da seguinte maneira:
Um ponto importante sobre os objetos do tipo fator é que seus Levels são imutáveis e não atualizam-se com a entrada de novos dados. Em outras palavras, não é possível modificar os valores dos Levels após a criação do objeto. Toda nova informação que não for compatível com os Levels do objeto será transformada em NA (Not available) e uma mensagem de warning irá aparecer na tela. Essa limitação pode parecer estranha a primeira vista porém, na prática, ela evita possíveis erros no código. Veja o exemplo a seguir:
# set factormy_factor <-factor(c('a', 'b', 'a', 'b'))# change first element of a factor to 'c'my_factor[1] <-'c'
R> Warning in `[<-.factor`(`*tmp*`, 1, value = "c"): invalid
R> factor level, NA generated
# print resultprint(my_factor)
R> [1] <NA> b a b
R> Levels: a b
Nesse caso, a maneira correta de proceder é primeiro transformar o objeto da classe fator para a classe caractere e depois realizar a conversão:
# set factormy_factor <-factor(c('a', 'b', 'a', 'b'))# change factor to charactermy_char <-as.character(my_factor)# change first elementmy_char[1] <-'c'# mutate it back to class factormy_factor <-factor(my_char)# show resultprint(my_factor)
R> [1] c b a b
R> Levels: a b c
Utilizando essas etapas temos o resultado desejado no vetor my_factor, com a definição de três Levels: a, b e c.
O universo {tidyverse}(Wickham 2023c) também possui um pacote próprio para manipular fatores, o {forcats}(Wickham 2023a). Para o problema atual de modificação de fatores, podemos utilizar função forcats::fct_recode() . Veja um exemplo a seguir, onde trocamos as siglas dos fatores:
Observe como o uso da função forcats::fct_recode() é intuitivo. Basta indicar o novo nome dos grupos com o operador de igualdade.
6.3.3 Convertendo Fatores para Outras Classes
Outro ponto importante no uso de fatores é a sua conversão para outras classes, especialmente a numérica. Quando convertemos um objeto de tipo fator para a classe caractere, o resultado é o esperado:
# create factormy_char <-factor(c('a', 'b', 'c'))# convert and printprint(as.character(my_char))
R> [1] "a" "b" "c"
Porém, quando fazemos o mesmo procedimento para a classe numérica, o que o R retorna é longe do esperado:
# set factormy_values <-factor(5:10)# convert to numeric (WRONG)print(as.numeric(my_values))
R> [1] 1 2 3 4 5 6
Esse resultado pode ser explicado pelo fato de que, internamente, fatores são armazenados como índices, indo de 1 até o número total de Levels. Essa simplificação minimiza o uso da memória do computador. Quando pedimos ao R para transformar esses fatores em números, ele entende que buscamos o número do índice e não do valor. Para contornar o problema é fácil, basta transformar o objeto fator em caractere e, depois, em numérico, conforme mostrado a seguir:
# converting factors to character and then to numericprint(as.numeric(as.character(my_values)))
R> [1] 5 6 7 8 9 10
Cuidado
Tenha muito cuidado ao transformar fatores em números. Lembre-se sempre de que o retorno da conversão direta serão os índices dos levels e não os valores em si. Esse é um bug bem particular que pode ser difícil de identificar em um código mais complexo.
6.3.4 Criando Tabelas de Contingência
Após a criação de um fator, podemos calcular a ocorrência de cada fator com a função table() . Essa também é chamada de tabela de contingência. Em um caso simples, com apenas um fator, a função table() conta o número de ocorrências de cada categoria, como a seguir:
Um caso mais avançado do uso de table() é utilizar mais de um fator para a criação da tabela. Veja o exemplo a seguir:
# set factorsmy_factor_1 <-factor(sample(c('Pref', 'Ord'),size =20,replace =TRUE))my_factor_2 <-factor(sample(paste('Grupo', 1:3),size =20,replace =TRUE))# print contingency table with two factorsprint(table(my_factor_1, my_factor_2))
R> my_factor_2
R> my_factor_1 Grupo 1 Grupo 2 Grupo 3
R> Ord 2 4 3
R> Pref 3 4 4
A tabela criada anteriormente mostra o número de ocorrências para cada combinação de fator. Essa é uma ferramenta descritiva simples, mas bastante informativa para a análise de grupos de dados.
6.3.5 Outras Funções
levels() - Retorna os Levels de um objeto da classe fator.
split() - Com base em um objeto de fator, cria uma lista com valores de outro objeto. Esse comando é útil para separar dados de grupos diferentes e aplicar alguma função com sapply() ou lapply.
my_factor <-factor(c('A','B','C','C','C','B'))my_x <-1:length(my_factor)my_l <-split(x = my_x, f = my_factor)print(my_l)
Testes lógicos em dados são centrais no uso do R. Em uma única linha de código podemos testar condições para uma grande quantidade de casos. Esse cálculo é muito utilizado para encontrar casos extremos nos dados (outliers) e também para separar diferentes amostras de acordo com algum critério.
6.4.1 Criando Valores Lógicos
Em uma sequência de 1 até 10, podemos verificar quais são os elementos maiores que 5 com o seguinte código:
# set numericalmy_x <-1:10# print a logical testprint(my_x >5)
# print position of elements from logical testprint(which(my_x >5))
R> [1] 6 7 8 9 10
A função which() do exemplo anterior retorna os índices onde a condição é verdadeira (TRUE). O uso do which() é recomendado quando se quer saber a posição de elementos que satisfazem alguma condição.
Para realizar testes de igualdade, basta utilizar o símbolo de igualdade duas vezes (==).
# create charmy_char <-rep(c('abc','bcd'), 5)# print its contentsprint(my_char)
Destaca-se que também é possível testar condições múltiplas, isto é, a ocorrência simultânea de eventos. Utilizamos o operador & para esse propósito. Por exemplo: se quiséssemos verificar quais são os valores de uma sequência de 1 a 10 que são maiores que 4 e menores que 7, escreveríamos:
my_x <-1:10# print logical for values higher than 4 and lower than 7print((my_x >4)&(my_x <7) )
# print the actual valuesidx <-which( (my_x >4)&(my_x <7) )print(my_x[idx])
R> [1] 5 6
Para testar condições não simultâneas, isto é, ocorrências de um ou outro evento, utilizamos o operador |. Por exemplo: considerando a sequência anterior, acharíamos os valores maiores que 7 ou menores que 4 escrevendo:
# location of elements higher than 7 or lower than 4idx <-which( (my_x >7)|(my_x <4) )# print elements from previous conditionprint(my_x[idx])
R> [1] 1 2 3 8 9 10
Observe que, em ambos os casos de uso de testes lógicos, utilizamos parênteses para encapsular as condições lógicas. Poderíamos ter escrito idx <- which( my_x > 7|my_x < 4 ), porém o uso do parênteses deixa o código mais claro ao isolar os testes de condições e sinalizar que o resultado da operação será um vetor lógico. Em alguns casos, porém, o uso do parênteses indica hierarquia na ordem das operações e portanto não pode ser ignorado.
Outro uso interessante de objetos lógicos é o teste para saber se um item ou mais pertence a um vetor ou não. Para isso utilizamos o operador %in%. Por exemplo, imagine que tens os tickers de duas ações, c('ABC', 'DEF') e queres saber se é possível encontrar esses tickers na coluna de outra base de dados. Essa é uma operação semelhante ao uso do teste de igualdade, porém em notação vetorial. Veja um exemplo a seguir:
library(dplyr)# location of elements higher than 7 or lower than 4my_tickers <-c('ABC', 'DEF')# set dfn_obs <-100df_temp <-tibble(tickers =sample(c('ABC', 'DEF', 'GHI', 'JKL'),size = n_obs,replace =TRUE),ret =rnorm(n_obs, sd =0.05) )# find rows with selected tickersidx <- df_temp$tickers %in% my_tickers# print elements from previous conditionglimpse(df_temp[idx, ])
O dataframe mostrado na tela possui dados apenas para ações em my_tickers.
6.5 Datas e Tempo
Manipular datas e horários de forma correta, levando em conta mudanças decorridas de horário de verão, feriados locais, em diferentes zonas de tempo, não é uma tarefa fácil! Felizmente, o R fornece um grande suporte para qualquer tipo de operação com datas e tempo.
Nesta seção estudaremos as funções e classes nativas que representam e manipulam o tempo em R. Aqui, daremos prioridade as funções do pacote {lubridate}(Spinu, Grolemund, e Wickham 2023). Existem, no entanto, muitos pacotes que podem ajudar o usuário a processar objetos do tipo data e tempo. Caso alguma operação com data e tempo não for encontrada aqui, sugiro o estudo dos pacotes {chron}(James e Hornik 2023), {timeDate}(Wuertz et al. 2023) e {bizdays}(Freitas 2024).
Antes de começarmos, vale relembrar que toda data no R segue o formato ISO 8601 (YYYY-MM-DD), onde YYYY é o ano em quatro números, MM é o mês e DD é o dia. Por exemplo, uma data em ISO 8601 é 2024-04-29. Deves familiarizar-se com esse formato pois toda importação de dados com formato de datas diferente desta notação exigirá conversão. Felizmente, essa operação é bastante simples de executar com o {lubridate}(Spinu, Grolemund, e Wickham 2023).
6.5.1 Criando Datas Simples
No R, existem diversas classes que podem representar datas. A escolha entre uma classe de datas e outra baseia-se na necessidade da pesquisa. Em muitas situações não é necessário saber o horário, enquanto que em outras isso é extremamente pertinente pois os dados são coletados ao longo de um dia.
A classe mais básica de datas é Date. Essa indica dia, mês e ano, apenas. No {lubridate}(Spinu, Grolemund, e Wickham 2023), criamos datas verificando o formato da data de entrada e as funções lubridate::ymd() (year-month-date), lubridate::dmy() (day-month-year) e lubridate::mdy() (month-day-year). Veja a seguir:
library(lubridate)# set Date objectprint(ymd('2021-06-24'))
R> [1] "2021-06-24"
# set Date objectprint(dmy('24-06-2021'))
R> [1] "2021-06-24"
# set Date objectprint(mdy('06-24-2021'))
R> [1] "2021-06-24"
Note que as funções retornam exatamente o mesmo objeto. A diferença no uso é somente pela forma que a data de entrada está estruturada com a posição do dia, mês e ano.
Um benefício no uso das funções do pacote {lubridate}(Spinu, Grolemund, e Wickham 2023) é que as mesmas são inteligentes ao lidar com formatos diferentes. Observe no caso anterior que definimos os elementos das datas com o uso do traço (-) como separador e valores numéricos. Outros formatos também são automaticamente reconhecidos:
# set Date objectprint(ymd('2021/06/24'))
R> [1] "2021-06-24"
# set Date objectprint(ymd('2021&06&24'))
R> [1] "2021-06-24"
# set Date objectprint(ymd('2021 june 24'))
R> [1] "2021-06-24"
# set Date objectprint(dmy('24 of june 2021'))
R> [1] "2021-06-24"
Isso é bastante útil pois o formato de datas no Brasil é dia/mês/ano (DD/MM/YYYY). Ao usar lubridate::dmy() para uma data brasileira, a conversão é correta:
# set Date from dd/mm/yyyymy_date <-dmy('24/06/2021')# print resultprint(my_date)
R> [1] "2021-06-24"
Já no pacote {base}(R Core Team 2023), a função correspondente é as.Date() . O formato da data, porém, deve ser explicitamente definido com argumento format() , conforme mostrado a seguir:
# set Date from dd/mm/yyyy with the definition of formatmy_date <-as.Date('24/06/2021', format ='%d/%m/%Y')# print resultprint(my_date)
R> [1] "2021-06-24"
Os símbolos utilizados na entrada format() , tal como %d e %Y, são indicadores de formato, os quais definem a forma em que a data a ser convertida está estruturada. Nesse caso, os símbolos %Y, %m e %d definem ano, mês e dia, respectivamente. Existem diversos outros símbolos que podem ser utilizados para processar datas em formatos específicos. Um panorama das principais codificações é apresentado a seguir:
Código
Valor
Exemplo
%d
dia do mês (decimal)
0
%m
mês (decimal)
12
%b
mês (abreviado)
Abr
%B
mês (nome completo)
Abril
%y
ano (2 dígitos)
16
%Y
ano (4 dígitos)
2021
Os símbolos anteriores permitem a criação de datas a partir de variados formatos. Observe como a utilização das funções do {lubridate}(Spinu, Grolemund, e Wickham 2023), em relação a {base}(R Core Team 2023), são mais simples e fáceis de utilizar, justificando a nossa escolha.
6.5.2 Criando Sequências de Datas
Um aspecto interessante no uso de objetos do tipo Date é que eles interagem com operações de adição de valores numéricos e com testes lógicos de comparação de datas. Por exemplo, caso quiséssemos adicionar dez dias à data my_date criada anteriormente, bastaria somar o valor 10 ao objeto:
A propriedade também funciona com vetores, o que deixa a criação de sequências de datas muito fácil. Nesse caso, o próprio R encarrega-se de verificar o número de dias em cada mês.
# create a sequence of Datesmy_date_vec <- my_date +0:15# print itprint(my_date_vec)
Uma maneira mais customizável de criar sequências de datas é utilizar a função seq() . Com ela, é possível definir intervalos diferentes de tempo e até mesmo o tamanho do vetor de saída. Caso quiséssemos uma sequência de datas de dois em dois dias, poderíamos utilizar o seguinte código:
# set first and last Datemy_date_1 <-ymd('2021-03-07')my_date_2 <-ymd('2021-03-20')# set sequencemy_date_date <-seq(from = my_date_1,to = my_date_2,by ='2 days')# print resultprint(my_date_date)
Caso quiséssemos de duas em duas semanas, escreveríamos:
# set first and last Datemy_date_1 <-ymd('2021-03-07')my_date_2 <-ymd('2021-04-20')# set sequencemy_date_date <-seq(from = my_date_1,to = my_date_2,by ='2 weeks')# print resultprint(my_date_date)
Outra forma de utilizar seq() é definir o tamanho desejado do objeto de saída. Por exemplo, caso quiséssemos um vetor de datas com 10 elementos, usaríamos:
# set first and last Datemy_date_1 <-ymd('2021-03-07')my_date_2 <-ymd('2021-10-20')# set sequencemy_date_vec <-seq(from = my_date_1,to = my_date_2,length.out =10)# print resultprint(my_date_vec)
A saída da operação de subtração é um objeto da classe diffdate, o qual possui a classe de lista como sua estrutura básica. Destaca-se que a notação de acesso aos elementos da classe diffdate é a mesma utilizada para listas. O valor numérico do número de dias está contido no primeiro elemento de diff_date:
# print difference of days as numerical valueprint(diff_date[[1]])
R> [1] 366
Podemos testar se uma data é maior do que outra com o uso das operações de comparação:
# set date and vectormy_date_1 <-ymd('2016-06-20')my_date_vec <-ymd('2016-06-20') +seq(-5,5)# test which elements of my_date_vec are older than my_date_1my_test <- (my_date_vec > my_date_1)# print resultprint(my_test)
A operação anterior é bastante útil quando se está buscando filtrar um determinado período de tempo nos dados. Nesse caso, basta buscar nas datas o período específico em que estamos interessados e utilizar o objeto lógico da comparação para selecionar os elementos.
6.5.4 Lidando com Data e Tempo
O uso da classe Date é suficiente quando se está lidando apenas com datas. Em casos em que é necessário levar em consideração o horário, temos que utilizar um objeto do tipo datetime.
No pacote {base}(R Core Team 2023), uma das classes utilizadas para esse fim é a POSIXlt, a qual armazena o conteúdo de uma data na forma de uma lista. Outra classe que também é possível utilizar é a POSIXct, que armazena as datas como segundos contados a partir de 1970-01-01. Junto ao {lubridate}(Spinu, Grolemund, e Wickham 2023), a classe utilizada para representar data-tempo é POSIXct e portanto daremos prioridade a essa. Vale destacar que todos os exemplos apresentados aqui também podem ser replicados para objetos do tipo POSIXlt.
O formato tempo/data também segue a norma ISO 8601, sendo representado como ano-mês-dia horas:minutos:segundos zonadetempo (YYYY-MM-DD HH:mm:SS TMZ). Veja o exemplo a seguir:
# creating a POSIXct objectmy_timedate <-as.POSIXct('2024-01-01 16:00:00')# print resultprint(my_timedate)
R> [1] "2024-01-01 16:00:00 -03"
Pacote {lubridate}(Spinu, Grolemund, e Wickham 2023) também oferece funções inteligentes para a criação de objetos do tipo data-tempo. Essas seguem a mesma linha de raciocínio que as funções de criar datas. Veja a seguir:
library(lubridate)# creating a POSIXlt objectmy_timedate <-ymd_hms('2021-01-01 16:00:00')# print itprint(my_timedate)
R> [1] "2021-01-01 16:00:00 UTC"
Destaca-se que essa classe adiciona automaticamente o fuso horário. Caso seja necessário representar um fuso diferente, é possível fazê-lo com o argumento tz:
# creating a POSIXlt object with custom timezonemy_timedate_tz <-ymd_hms('2021-01-01 16:00:00',tz ='GMT')# print itprint(my_timedate_tz)
R> [1] "2021-01-01 16:00:00 GMT"
É importante ressaltar que, para o caso de objetos do tipo POSIXlt e POSIXct, as operações de soma e diminuição referem-se a segundos e não dias, como no caso do objeto da classe Date.
# Adding values (seconds) to a POSIXlt object and printing itprint(my_timedate_tz +30)
R> [1] "2021-01-01 16:00:30 GMT"
Assim como para a classe Date, existem símbolos específicos para lidar com componentes de um objeto do tipo data/tempo. Isso permite a formatação customizada de datas. A seguir, apresentamos um quadro com os principais símbolos e os seus respectivos significados.
Código
Valor
Exemplo
%H
Hora (decimal, 24 horas)
23
%I
Hora (decimal, 12 horas)
11
%M
Minuto (decimal, 0-59)
12
%p
Indicador AM/PM
AM
%S
Segundos (decimal, 0-59)
50
A seguir veremos como utilizar essa tabela para customizar datas.
6.5.5 Personalizando o Formato de Datas
A notação básica para representar datas e data/tempo no R pode não ser a ideal em algumas situações. No Brasil, por exemplo, indicar datas no formato YYYY-MM-DD pode gerar bastante confusão em um relatório formal. É recomendado, portanto, modificar a representação das datas para o formato esperado, isto é, DD/MM/YYYY.
Para formatar uma data, utilizamos a função format() . Seu uso baseia-se nos símbolos de data e de horário apresentados anteriormente. A partir desses, pode-se criar qualquer customização. Veja o exemplo a seguir, onde apresenta-se a modificação de um vetor de datas para o formato brasileiro:
Para extrair elementos de datas tal como o ano, mês, dia, hora, minuto e segundo, uma alternativa é utilizar função format() . Observe o próximo exemplo, onde recuperamos apenas as horas de um objeto POSIXct:
library(lubridate)# create vector of date-timemy_datetime <-seq(from =ymd_hms('2021-01-01 12:00:00'),to =ymd_hms('2021-01-01 18:00:00'),by ='1 hour')# get hours from POSIXltmy_hours <-as.numeric(format(my_datetime, '%H'))# print resultprint(my_hours)
R> [1] 12 13 14 15 16 17 18
Da mesma forma, poderíamos utilizar os símbolos %M e %S para recuperar facilmente minutos e segundos de um vetor de objetos POSIXct.
# create vector of date-timemy_datetime <-seq(from =ymd_hms('2021-01-01 12:00:00'),to =ymd_hms('2021-01-01 18:00:00'),by ='15 min')# get minutes from POSIXltmy_minutes <-as.numeric(format(my_datetime, '%M'))# print resultprint(my_minutes)
Outras funções também estão disponíveis para os demais elementos de um objeto data-hora.
6.5.7 Conhecendo o Horário e a Data Atual
O R inclui várias funções que permitem o usuário utilizar no seu código o horário e data atual do sistema. Isso é bastante útil quando se está criando registros e é importante que a data e horário de execução do código seja conhecida futuramente.
Para conhecer o dia atual, basta utilizarmos a função Sys.Date() ou lubridate::today() :
library(lubridate)# get todayprint(Sys.Date())
R> [1] "2024-04-29"
# print itprint(today())
R> [1] "2024-04-29"
Para descobrir a data e horário, utilizamos a função Sys.time() ou lubridate::now() :
# get time!print(Sys.time())
R> [1] "2024-04-29 08:13:10 -03"
# get time!print(now())
R> [1] "2024-04-29 08:13:10 -03"
Com base nessas, podemos escrever:
library(stringr)# example of log messagemy_str <-str_c('This code was executed in ', now())# print itprint(my_str)
R> [1] "This code was executed in 2024-04-29 08:13:10.551632"
6.5.8 Outras Funções Úteis
weekdays() - Retorna o dia da semana de uma ou várias datas.
# set date vectormy_dates <-seq(from =ymd('2021-01-01'),to =ymd('2021-01-5'),by ='1 day')# find corresponding weekdaysmy_weekdays <-weekdays(my_dates)# print itprint(my_weekdays)
OlsonNames() - Retorna um vetor com as zonas de tempo disponíveis no R. No total, são mais de 500 itens. Aqui, apresentamos apenas os primeiros cinco elementos.
# get possible timezonespossible_tz <-OlsonNames()# print itprint(possible_tz[1:5])
Sys.timezone() - Retorna a zona de tempo do sistema.
# get current timezoneprint(Sys.timezone())
R> [1] "America/Sao_Paulo"
cut() - Retorna um fator a partir da categorização de uma classe de data e tempo.
# set example date vectormy_dates <-seq(from =ymd('2021-01-01'),to =ymd('2021-03-01'),by ='5 days')# group vector based on monthly breaksmy_month_cut <-cut(x = my_dates,breaks ='1 month')# print resultprint(my_month_cut)
# set example datetime vectormy_datetime <-as.POSIXlt('2021-01-01 12:00:00') +seq(0,250,15)# set groups for each 30 secondsmy_cut <-cut(x = my_datetime, breaks ='30 secs')# print resultprint(my_cut)
Uma das principais inovações do R em relação a outras linguagens de programação é a representação de dados omissos através de objetos da classe NA (Not Available). A falta de dados pode ter inúmeros motivos, tal como a falha na coleta de informações ou simplesmente a não existência dos mesmos. Esses casos são tratados por meio da remoção ou da substituição dos dados omissos antes realizar uma análise mais profunda. A identificação desses casos, portanto, é de extrema importância.
6.6.1 Definindo Valores NA
Para definirmos os casos omissos nos dados, basta utilizar o símbolo NA:
# a vector with NAmy_x <-c(1, 2, NA, 4, 5)# print itprint(my_x)
R> [1] 1 2 NA 4 5
Vale destacar que a operação de qualquer valor NA com outro sempre resultará em NA.
# example of NA interacting with other objectsprint(my_x +1)
R> [1] 2 3 NA 5 6
Isso exige cuidado quando se está utilizando alguma função com cálculo recursivo, tal como cumsum() e cumprod() . Nesses casos, todo valor consecutivo ao NA será transformado em NA. Veja os exemplos a seguir com as duas funções:
# set vector with NAmy_x <-c(1:5, NA, 5:10)# print cumsum (NA after sixth element)print(cumsum(my_x))
R> [1] 1 3 6 10 15 NA NA NA NA NA NA NA
# print cumprod (NA after sixth element)print(cumprod(my_x))
R> [1] 1 2 6 24 120 NA NA NA NA NA NA NA
Cuidado com NAs
Toda vez que utilizar as funções cumsum() e cumprod() , certifique-se de que não existe algum valor NA no vetor de entrada. Lembre-se de que todo NA é contagiante e o cálculo recursivo irá resultar em um vetor repleto de dados faltantes.
6.6.2 Encontrando e Substituindo Valores NA
Para encontrar os valores NA em um vetor, basta utilizar a função is.na() :
# set vector with NAmy_x <-c(1:2, NA, 4:10)# find location of NAidx_na <-is.na(my_x)print(idx_na)
Para substituí-los, use indexação com a saída de is.na() :
# set vectormy_x <-c(1, NA, 3:4, NA)# replace NA for 2my_x[is.na(my_x)] <-2# print resultprint(my_x)
R> [1] 1 2 3 4 2
Outra maneira de limpar o objeto é utilizar a função na.omit() , que retorna o mesmo objeto mas sem os valores NA. Note, porém, que o tamanho do vetor irá mudar e o objeto será da classe omit, o que indica que o vetor resultante não inclui os NA e apresenta, também, a posição dos elementos NA encontrados.
# set vectormy_char <-c(letters[1:3], NA, letters[5:8])# print itprint(my_char)
R> [1] "a" "b" "c" NA "e" "f" "g" "h"
# use na.omit to remove NAmy_char <-na.omit(my_char)# print resultprint(my_char)
Apesar do tipo de objeto ter sido trocado, devido ao uso de na.omit() , as propriedades básicas do vetor inicial se mantêm. Por exemplo: o uso de nchar() no objeto resultante é possível.
# trying nchar on a na.omit objectprint(nchar(my_char))
R> [1] 1 1 1 1 1 1 1
Para outros objetos, porém, recomenda-se cautela quando no uso da função na.omit() .
6.6.3 Outras Funções Úteis
complete.cases() - Retorna um vetor lógico que indica se as linhas do objeto possuem apenas valores não omissos. Essa função é usada exclusivamente para dataframes e matrizes.
# create matrixmy_mat <-matrix(1:15, nrow =5)# set an NA valuemy_mat[2,2] <-NA# print index with rows without NAprint(complete.cases(my_mat))
R> [1] TRUE FALSE TRUE TRUE TRUE
6.7 Exercícios
Q.1 - Considere os seguintes os vetores x e y:
set.seed(7)
x <- sample (1:3, size = 5, replace = T)
y <- sample (1:3, size = 5, replace = T)
Qual é a soma dos elementos de um novo vetor resultante da multiplicação entre os elementos de x e y?
41
31
55
34
48
Q.2 - Caso realizássemos uma soma cumulativa de uma sequência entre 1 e 100, em qual elemento esta soma iria passar de 50?
3
1
10
7
5
Q.3 - Utilizando o R, crie uma sequência em objeto chamado seq_1 entre -15 e 10, onde o intervalo entre valores é sempre igual a 2. Qual o valor da soma dos elementos de seq_1?
-39
-27
-17
-52
-91
Q.4 - Defina outro objeto chamado seq_2 contendo uma sequência de tamanho 1000, com valores entre 0 e 100. Qual é o desvio padrão (função sd() ) dessa sequência?
39.30614
44.90486
33.70742
28.91085
50.50359
Q.5 - Deina dois vetores, um com a sequência entre 1 e 10, e outro como a sequência entre 1 e 5. Caso somássemos os elementos de ambos vetores, a operação funcionaria apesar do tamanho diferente dos vetores? Explique sua resposta. Caso funcionar, qual o maior valor do vetor resultante?
28
32
21
25
18
Q.6 - Vamos supor que, em certa data, você comprou 100 ações de uma empresa, a price_purchase reais por ação. Depois de algum tempo, você vendeu 30 ações por 18 reais cada e as 70 ações restantes foram vendidas por 22 reais em um dia posterior. Usando um script em R, estruture este problema financeiro criando objetos numéricos. Qual é o lucro bruto desta transação no mercado de ações?
R$ 580
R$ 1.658
R$ 223
R$ 389
R$ 1.078
Q.7 - Crie um vetor x de acordo com a fórmula a seguir, onde \(i=1...100\). Qual é o valor da soma dos elementos de x?
\[
x_i=\frac{-1^{i+1}}{2i-1}
\]
0.3297478
0.7828982
0.9760899
0.5317707
1.758988
Q.8 - Crie um vetor \(z_i\) de acordo com a fórmula a seguir onde \(x_i=1...50\) e \(y_i=50...1\). Qual é o valor da soma dos elementos de \(z_i\)? Dica: veja o funcionamento da função dplyr::lag.
\[
z_i=\frac{y_i - x_{i-1}}{y_{i-2}}
\]
-23.15975
-65.95709
-10.09432
-49.29059
-36.22517
Q.9 - Usando uma semente de valor 43 em set.seed(), crie um objeto chamado x com valores aleatórios da distribuição Normal com média igual a 10 e desvio padrão igual a 10. Usando função cut() , crie outro objeto que defina dois grupos com base em valores de x maiores que 15 e menores que 15. Qual a quantidade de observações no primeiro grupo?
1047
1188
905
684
763
Q.10 - Crie o seguinte objeto com o código a seguir:
Qual a quantidade de vezes que a letra 'x' é encontrada no objeto de texto resultante?
297
198
264
231
174
Q.11 - Baseado no objeto my_char criado anteriormente, caso dividíssemos o mesmo em diversos pedaços menores utilizando a letra "b", qual é o número de caracteres no maior pedaço encontrado?
188
125
167
146
110
Q.12 - No endereço https://www.gutenberg.org/ebooks/2264.txt.utf-8 é possível acessar um arquivo .txt contendo o texto integral do livro Pride and Prejudice de Jane Austen. Utilize funções download.file() e readr::read_lines() para importar o livro inteiro como um vetor de caracteres chamado my_book no R. Quantas linhas o objeto resultante possui?
5146
3761
8907
2596
1737
Q.13 - Junte o vetor de caracteres em my_book para um único valor (texto) em outro objeto chamado full_text usando função paste0(my_book, collapse = '\n'). Utilizando este último e pacote {stringr}(Wickham 2023b), quantas vezes a palavra 'King' é repetida na totalidade do texto?
55
93
70
30
163
Q.14 - Para o objeto full_text criado anteriormente, utilize função stringr::str_split() para quebrar o texto inteiro em função de espaços em branco. Com base nesse, crie uma tabela de frequência. Qual a palavra mais utilizada no texto? Dica: Remova todos os casos de caracteres vazios ('').
and
to
is
with
the
Q.15 - Assumindo que uma pessoa nascida em 2000-05-12 irás viver for 100 anos, qual é o número de dias de aniversário que cairão em um final de semana (sábado ou domingo)? Dica: use operador %in% para checar uma condição múltipla nos dados.
29
11
45
21
74
Q.16 - Qual data e horário é localizado 10^{4} segundos após 2021-02-02 11:50:02?
McLeish, Don L. 2011. Monte Carlo simulation and finance. Vol. 276. John Wiley & Sons.
R Core Team. 2023. R: A Language and Environment for Statistical Computing. Vienna, Austria: R Foundation for Statistical Computing. https://www.R-project.org/.
Spinu, Vitalie, Garrett Grolemund, e Hadley Wickham. 2023. lubridate: Make Dealing with Dates a Little Easier. https://lubridate.tidyverse.org.
Thompson, Ken. 1968. «Programming techniques: Regular expression search algorithm». Communications of the ACM 11 (6): 419–22.
Wickham, Hadley. 2023a. forcats: Tools for Working with Categorical Variables (Factors). https://forcats.tidyverse.org/.
Wuertz, Diethelm, Tobias Setz, Yohan Chalabi, e Georgi N. Boshnakov. 2023. timeDate: Rmetrics - Chronological and Calendar Objects. https://geobosh.github.io/timeDateDoc/.