Capítulo 4 Strings no R

Na ciência da computação chamamos uma sequência de caracteres de string. Para o desenvolvimento de análises automatizadas de conteúdo, é necessário saber como processar esse tipo especial de dado (o texto como dado)3. Nesse sentido, três coisas são importantes de serem lembradas aqui:

  1. Computadores não interpretam letras. No limite, todos os caracteres são transformados em sequências compostas por zeros e uns. Logo, é através de padrões que caracteres são interpretados e os computadores armazenam os dados que retornam a nossos olhos.

  2. Programar é escrever! Não é à toa que chamamos as formas de escrita em programação de linguagens de programação. Nesse livro, por exemplo, usamos a linguagem R. Sabendo disso, o desafio de se trabalhar com o texto como dado é o desafio de fazer com que o computador diferencie código escrito do "texto como dado" que ele precisará processar de acordo com os interesses do analista.

  3. Como nós brasileiros lemos, o código e texto é processado pelo computador no seguinte sentido: da esquerda para a direita e de cima para baixo. Logo, ao desenvolver seu script é importante ter atenção em relação à ordem de escrita para que o computador possa desempenhar corretamente suas tarefas.

É possível utilizar toda a versatilidade de estruturas de dados no R (vetores, matrizes, listas, data.frame, etc.) para processar sequências de caracteres. Como trabalhar com strings no R, portanto?

4.1 Strings e vetores

Para declarar uma string, utilizamos aspas simples ' ou aspas dupla “““. Vejamos o caso dos dois vetores abaixo, ambos recebendo a letra”a”.

# Vetores de caracteres 
caracter1 <- "a"
caracter2 <- 'A'

class(caracter1)
## [1] "character"
class(caracter2)
## [1] "character"

Ambos são da classe character.

4.1.1 O R é case sensitive

O R diferencia letras maiúsculas de letras minúsculas. Se compararmos os dois objetos criados acima, temos:

caracter1 == caracter2
## [1] FALSE

4.1.2 Sequências de caracteres

# string
txt <- "uma string é uma sequência de caracteres"
txt <- 'também pode ser utilizada com aspas simples'

txt <- "no caso de aspas dupla, usa-se 'aspas simples' na string"
txt <- 'no caso de aspas simples, usa-se "aspas dupla" na string'
txt <- "para usar \"aspas dupla\" na string é necessário usar \\"
cat(txt) 
## para usar "aspas dupla" na string é necessário usar \

O R armazena a sequência de caracteres conforme ela é apresentada. Porém, é possível fazer uso de caracteres especiais para que o computador interprete e apresente o texto de forma adequada. Como vimos acima, o objeto txt armazena a string conforme foi redigida, mas com o uso da função cat() podemos apresentá-lo de forma adequada. Perceba a diferença entre o resultado e a sequência de caracteres que, de fato, foi armazenada no objeto txt.

4.1.3 Operações básicas com vetores de strings

É possível declarar um vetor de caracteres vazio.

# vetor de caracteres com 5 strings vazias
palmeiras <- character(5)
palmeiras
## [1] "" "" "" "" ""

Vejamos seu tamanho:

length(palmeiras)  # verificando o tamanho do vetor
## [1] 5

Vemos que o objeto palmeiras possui 5 elementos, todos sem qualquer conteúdo, mas da classe character.

Será que é possível inserir conteúdo em elementos específicos do vetor? Vejamos:

# incluindo string no primeiro e terceiro elementos do vetor
palmeiras[1] <- "Quando surge o alviverde imponente"
palmeiras[3] <- "Sabe bem o que vem pela frente"
palmeiras
## [1] "Quando surge o alviverde imponente" ""                                  
## [3] "Sabe bem o que vem pela frente"     ""                                  
## [5] ""

Ótimo! Significa que podemos ter um vetor no com o Hino do Palmeiras, sendo cada um de seus elementos um verso dessa bela poesia.

E seria possível ter um vetor cujos elementos fossem os hinos (sequências de caracteres/strings) de todos os times do país? Sim!

4.1.3.1 Atenção

Um vetor com uma string vazia é diferente de um vetor sem strings

# Atenção:
str_vazia <- ""  # string vazia
char_vazio <- character(0)  # caracter vazio

length(str_vazia)
## [1] 1
length(char_vazio)
## [1] 0

4.1.4 Caracteres e outros tipos de dados

É importante saber como o R processa o texto como dado (character) em conjunto com outros formatos.

frase <- "Campeonatos Brasileiros vencidos pelo Palmeiras."
is.numeric(frase)
## [1] FALSE
is.character(frase)
## [1] TRUE

Acima verificamos que a classe do objeto frase é de tipo character.

quantidade <- 5 + 5
quantidade
## [1] 10
is.numeric(quantidade)
## [1] TRUE
is.character(quantidade)
## [1] FALSE

Acima verificamos que a classe do objeto quantidade é de tipo numeric. Seria possível converter de um tipo para outro?

# convertendo quantidade
quantidade <- as.character(quantidade)
quantidade
## [1] "10"
is.character(quantidade)
## [1] TRUE

Sim! Veja que agora o valor 10 aparece entre aspas, pois o objeto quantidade foi convertido para a classe character.

E se um vetor possuir números e caracteres em diferentes elementos, como o R interpreta a classe desse vetor?

# vetor com números e strings
brasileiros <- c(10, "Campeonatos Brasileiros vencidos pelo Palmeiras.")
brasileiros
## [1] "10"                                              
## [2] "Campeonatos Brasileiros vencidos pelo Palmeiras."
class(brasileiros)
## [1] "character"

Perceba que o vetor é declarado com o número 10 no primeiro elemento e uma string no segundo elemento. Contudo, o R adota um critério de coerção de dados para que o vetor seja da classe character. Por isso, o número 10 é automaticamente convertido como caracter.

O R segue duas regras básicas de coerção de tipos de dados:

  1. Se uma cadeia de caracteres estiver presente em um vetor, todo o resto do vetor será convertido em cadeias de caracteres.

  2. Se um vetor tiver apenas elementos lógicos e números, os elementos lógicos serão convertidos em números; Valores TRUE se tornam 1 e os valores FALSE se tornam 0.

4.2 Strings e matrizes

No R matrizes são estruturas de dados que suportam apenas um tipo de classe de dados. Logo, assim como no caso do vetor visto anteriormente, ao constatar a presenção de alguma entrada de classe character automaticamente todos os elementos da matriz são convertidos.

# Matrizes ----
m <- rbind(c(1:5), letters[1:5])
m
##      [,1] [,2] [,3] [,4] [,5]
## [1,] "1"  "2"  "3"  "4"  "5" 
## [2,] "a"  "b"  "c"  "d"  "e"
class(m)
## [1] "matrix" "array"

4.3 Strings e data.frames

data.frames são as estruturas de dados mais utilizadas no R. Sua versatilidade permite ter no mesmo objeto dados de classes diferentes num formato de matriz (matriz de dados). Vejamos:

# Data Frames  ----
df1 <- data.frame(numeros = 1:5, letras = letters[1:5])
str(df1)
## 'data.frame':    5 obs. of  2 variables:
##  $ numeros: int  1 2 3 4 5
##  $ letras : chr  "a" "b" "c" "d" ...

Como padrão da função data.frame() strings são transformadas em fatores. Para manter strings como caracteres deve-se usar o argumento: stringsAsFactors = FALSE.

df1 <- data.frame(numeros = 1:5, letras = letters[1:5], stringsAsFactors = FALSE)
str(df1)
## 'data.frame':    5 obs. of  2 variables:
##  $ numeros: int  1 2 3 4 5
##  $ letras : chr  "a" "b" "c" "d" ...

4.4 Strings e listas

Das estruturas de objetos mais populares no R, listas são as mais complexas. Sua grande vantagem em relação às demais estruturas é permitir uma organização hierárquica dos dados independente de sua classe e tamanho. Vejamos um exemplo:

# Listas ----
# listas contemplam qualquer tipo de estrutura de dados
ls <- list(1:10, letters[1:5], rnorm(5), m)
ls
## [[1]]
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## [[2]]
## [1] "a" "b" "c" "d" "e"
## 
## [[3]]
## [1]  0.8857933 -1.0430847 -1.4837529 -0.1982924 -0.5363551
## 
## [[4]]
##      [,1] [,2] [,3] [,4] [,5]
## [1,] "1"  "2"  "3"  "4"  "5" 
## [2,] "a"  "b"  "c"  "d"  "e"

No exemplo acima, o objeto ls é composto por quatro elementos que contêm, cada um, diferentes tamanhos e diferentes estruturas de dados.

4.5 Processamento básico

4.5.1 Contando caracteres

A função nchar() é um forma ágil e fácil de se obter o número de caracteres de uma string ou de strings de um vetor.

nchar(c("Quantos", "caracteres?"))
## [1]  7 11
nchar("Quantos caracteres?")
## [1] 19

No exemplo acima, perceba que a função contabiliza o espaço entre palavras como caracter. Por isso, a soma do total de caracteres do primeiro caso (\(7 + 11 = 18\) caracteres) não é igual ao total de caracteres do segundo (\(19\) caracteres).

4.5.2 toupper(), tolower()

Sendo o R case sensitive, para o processamento do texto como dado, pode ser de interesse do pesquisador harmonizar o conteúdo sob análise com o objetivo de ter todos os caracteres em formato maiúsculo ou minúsculo. As funções toupper() e tolower() desempenham bem esse papel.

tolower(c("TUdo eM MinúsCuLA", "ABCDE"))
## [1] "tudo em minúscula" "abcde"
toupper(c("TUdo eM mAiúsCula", "ABCDE"))
## [1] "TUDO EM MAIÚSCULA" "ABCDE"

4.5.2.1 Recortando strings: substr(), substring().

Para o processamento do texto como dado, também pode ser de interesse do pesquisador a seleção de trechos de uma sequência de caracteres. Isso pode ser facilmente feito com as funções substr() e substring() indicando como parâmetros a posição nas quais a string deve ser recortada.

substr("O Palmeiras é o time da virada, o Palmeiras é o time do amor.", 1, 30)
## [1] "O Palmeiras é o time da virada"
substring("O Palmeiras é o time da virada, o Palmeiras é o time do amor.", 33, 60)
## [1] "o Palmeiras é o time do amor"

4.5.2.2 União, Intersecção, Diferença, Igualdade

Operações com vetores de forma geral podem ser aplicadas a vetores com strings. Podemos, por exemplo, unir diferentes vetores.

# União 
vec1 <- c("algumas", "palavras", "aleatória", "aqui")
vec2 <- c("e", "algumas", "palavras", "ali")
union(vec1, vec2)
## [1] "algumas"   "palavras"  "aleatória" "aqui"      "e"         "ali"

Verificar a intersecção entre dois vetores.

# Intersecção 
intersect(vec1, vec2)
## [1] "algumas"  "palavras"

Verificar a diferença entre dois vetores.

# Diferença 
setdiff(vec1, vec2)
## [1] "aleatória" "aqui"

E a igualdade de elementos entre dois vetores. No caso, entre o vetor vec1 e ele mesmo.

# Igualdade 
identical(vec1, vec1)
## [1] TRUE

4.5.2.3 Elemento contido em

Outra operação básica de interesse é a verificação se um elemento (no caso, uma sequência de caracteres) está contido num objeto. Vamos verificar abaixo se a sequência “aqui” está contida no vetor vec1 através do operador %in%.

# Elemento contido em ----
elem <- "aqui"
elem %in% vec1
## [1] TRUE

É importante destacar que o exemplo acima é uma operação básica de vetores no R e não exclusiva para sequência de caracteres. Nesse sentido, ela apenas checa se no vetor vec1 há algum elemento idêntico ao padrão “aqui”. Mais adiante verificaremos como identificar a presença de sequências de caracteres no interior de outras strings sem que tenham de ser idênticas.

4.5.2.4 Ordenação

É possível ordenar um vetor de strings em ordem alfabética ou em sentido oposto como no exemplo abaixo. Tal versatilidade pode ser útil para o ordenamento de uma matriz de dados completa com base numa variável de nomes, por exemplo.

# Ordenando ----
sort(vec1, decreasing = TRUE)
## [1] "palavras"  "aqui"      "algumas"   "aleatória"

4.6 O pacote stringr

O pacote stringr integra uma coleção de pacotes projetados para a ciência de dados, o tidyverse. Combinado ao pacote stringi, você terá acesso a praticamente todas as possíveis funções necessárias para o processamento de strings em mais alto nível.

Existem quatro famílias principais de funções no stringr:

  1. Manipulação de caracteres: essas funções permitem que você manipule caracteres individuais dentro de sequências de caracteres.

  2. Ferramentas de espaço em branco para adicionar, remover e manipular espaços.

  3. Operações sensíveis à localização geográfica, cujas operações irão variar de local para local.

  4. Funções de correspondência de padrões, sendo o mais comum as expressões regulares.

Além do que veremos neste material, é altamente recomendável a consulta ao capítulo sobre strings do R for Data Science.

# carregando pacote ----
library(stringr)

4.6.1 Verificando o tamanho de uma string4

str_length("O Palmeiras é o time da virada, o Palmeiras é o time do amor.")
## [1] 61

4.6.2 Identificando caracter numa posição específica.

# vetor de strings
txt <- c("O Palmeiras é o time da virada", "o Palmeiras é o time do amor.")

Selecionando o terceiro caracter.

# identificando terceira letra
str_sub(txt, 3, 3)
## [1] "P" "P"

Selecionando do segundo caracter de trás pra frente.

str_sub(txt, 2, -2)
## [1] " Palmeiras é o time da virad" " Palmeiras é o time do amor"

4.6.3 Incluindo caracter ou string numa posicao específica.

str_sub(txt, 3, 11) <- "PALMEIRAS"
txt
## [1] "O PALMEIRAS é o time da virada" "o PALMEIRAS é o time do amor."

Preencher uma string em tamanho fixo.

str_pad(txt, 50) # por padrão: left
## [1] "                    O PALMEIRAS é o time da virada"
## [2] "                     o PALMEIRAS é o time do amor."

Remove espaço extra.

txt <- str_pad(txt, 50) # por padrão: left
str_trim(txt)
## [1] "O PALMEIRAS é o time da virada" "o PALMEIRAS é o time do amor."

4.6.4 Recortando uma string para obter parte da sequência de caracteres.

str_sub(txt, start = 3, end = 11)
## [1] "         " "         "

É possível fazer o recorte usando índices de trás pra frente.

str_sub(txt, start = -14, end = -1)
## [1] "time da virada" " time do amor."

Extração de palavras.

word(txt, 2)
## [1] "" ""
word(txt, -1)
## [1] "virada" "amor."

4.7 Regular Expressions no R

Até aqui você viu funções básicas e intermediárias para o processamento de sequências de caracteres no R. Para avançar, é necessário aprender o uso de expressões regulares ( Regular Expressions ).

Como definido no livro Handling Strings with R, uma expressão regular é um conjunto de símbolos que descreve um padrão de texto. Mais formalmente, uma expressão regular é um padrão que descreve um conjunto de cadeias de caracteres. Como o termo “expressão regular” é bastante longo, a maioria das pessoas usa a palavra regex para se referir à área.

Entre outras tarefas, o uso de expressões regulares pode ajudá-lo a (Wickham and Grolemund 2017):

  • Determinar cadeias de caracteres correspondentes a um padrão.
  • Encontrar as posições de padrões correspondentes.
  • Extrair o conteúdo de padrões correspondentes.
  • Substituir o padrão correspondente por novos valores.
  • Dividir uma sequência com base na correspondência de um padrão determinado.

No entanto, é preciso ter atenção, pois o uso de expressões regulares pode se tornar uma tarefa realmente complexa. Veja esta discussão do StackOverflow a respeito de seu uso para identificação de endereços de e-mail, por exemplo.

Dada a complexidade que a área pode assumir, vamos verificar o uso das regex em algumas funções do pacote stringr com base nesse tutorial. Junto a ele, é recomendável que a leitura atenta do ?regex no R.

4.7.1 Identificação e Extração de padrão

txt <- c("O Palmeiras é o time da virada", "o Palmeiras é o time do amor.")
str_extract(txt, "amor")
## [1] NA     "amor"
str_detect(txt, "amor")
## [1] FALSE  TRUE

Utilizando o operador | (“OU”):

str_detect(c("presidente", "presidencialismo", "presidencialista", "parlamentarismo"), "ente|ismo")
## [1]  TRUE  TRUE FALSE  TRUE
str_extract(c("presidente", "presidencialismo", "presidencialista", "parlamentarismo"), "ente|ismo")
## [1] "ente" "ismo" NA     "ismo"
str_extract(c("presidente", "presidencialismo", "presidencialista", "parlamentarismo"), "(presidencial|parlamentar)ismo")
## [1] NA                 "presidencialismo" NA                 "parlamentarismo"

Usar o “.” corresponde a qualquer caracter exceto uma nova linha:

txt <- c("presidente", "presidencialismo", "presidencialista", "parlamentarismo")
str_extract(txt, "..a.....")
## [1] NA         "cialismo" "cialista" "rlamenta"

Para identificar o “.” de fato, usamos “\.”. Para poder usar a “\”, adicionamos mais uma e temos:

txt <- c("O Palmeiras é o time da virada", "o Palmeiras é o time do amor.")
str_detect(txt, "\\.")
## [1] FALSE  TRUE

Para identificar a “\” de fato, usamos “\\”:

txt <- c("O Palmeiras é o time da virada \\ o Palmeiras é o time do amor.")
writeLines(txt)
## O Palmeiras é o time da virada \ o Palmeiras é o time do amor.
str_detect(txt, "\\.")
## [1] TRUE

4.7.2 Substituição

txt <- c("O Palmeiras é o time da virada", "o Palmeiras é o time do amor.")
str_replace(txt, "Palmeiras", "PALMEIRAS")
## [1] "O PALMEIRAS é o time da virada" "o PALMEIRAS é o time do amor."

4.7.3 Âncoras

Por padrão, expressões regulares buscam por correspondência em qualquer parte de uma sequência de caracteres. Porém, é extremamente útil poder ancorar a busca pela correspondência no início ou no final de uma string. Podemos usar:

  • “^” para coincidir com o início da string.
  • “$” para coincidir com o final da string.
txt <- c("O Palmeiras é o time da virada", "o Palmeiras é o time do amor.")
str_detect(txt, "^O")
## [1]  TRUE FALSE
str_detect(txt, "\\.$")
## [1] FALSE  TRUE

Referências

Wickham, Hadley, and Garrett Grolemund. 2017. R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. 1 edition. Sebastopol, CA: O’Reilly Media.