8.1 Funções para manipulação e busca de textos

8.1.1 Colar ou concatenar textos

A função paste() concatena textos.

?paste # veja o help dessa funcao

Vamos criar dois vetores de texto, txt e txt2, para, em seguida, concatená-los com esta função:

# um vetor de texto
txt <- c("banana", "maça", "pera")
# outro vetor
txt2 <- LETTERS[1:3]
# concatenamos os textos par a par
paste(txt, txt2)
## [1] "banana A" "maça B"   "pera C"
# mudamos o separador
paste(txt, txt2, sep = "-")
## [1] "banana-A" "maça-B"   "pera-C"

Podemos também unir os elementos contidos em um vetor em um único elemento, separados por algum símbolo. Basta usar o argumento collapse e indicar qual será o elemento que unirá os elementos contidos em um vetor:

# imagine que queremos juntar os elementos de um vetor
# num único texto, separados por ";"
paste(txt, collapse = ";")
## [1] "banana;maça;pera"
paste(txt, collapse = "/")
## [1] "banana/maça/pera"
paste(txt, collapse = " e ")
## [1] "banana e maça e pera"
paste(txt, collapse = " mais ")
## [1] "banana mais maça mais pera"

8.1.2 Quebrar ou desconcatenar textos

A função strsplit() quebra um vetor de texto segundo um separador determinado pelo argumento split, e retorna uma lista como resultado.

?strsplit
txt <- "Este é um texto com várias palavras"
strsplit(txt, split = " ")
## [[1]]
## [1] "Este"     "é"        "um"       "texto"    "com"      "várias"   "palavras"

Note que o resultado da ação é um objeto de classe lista:

class(strsplit(txt, split = " "))
## [1] "list"

Então, para pegar o resultado da ação da função strsplit(), devemos usar o que aprendemos em indexação de listas. Para indexar listas, devemos usar duplo colchetes [[n]], em que n é o número do elemento que desejamos reter. Vejamos abaixo que retemos o resultado de strsplit() no vetor txt em um objeto pl:

pl <- strsplit(txt, split = " ")
pl
## [[1]]
## [1] "Este"     "é"        "um"       "texto"    "com"      "várias"   "palavras"

Para pegar o tamanho do objeto pl, usamos length(pl) que nos dá o resultado de 1. Por isso, se desejamos reter essa ação como um vetor de texto, nosso n é 1, logo, [[1]]:

pl <- strsplit(txt, split = " ")[[1]]
pl
## [1] "Este"     "é"        "um"       "texto"    "com"      "várias"   "palavras"

Agora o objeto pl é um vetor de texto e de tamanho corresponde ao número de palavras contidas no vetor:

class(pl)
## [1] "character"
length(pl) # numero de palavras no texto
## [1] 7

8.1.2.1 Um exemplo prático

Suponha que você tenha uma tabela em que o nome da espécie esteja em uma única coluna e você queira separar o gênero do epíteto. Podemos usar a função strsplit() para resolver essa situação. Vejamos:

# um dado qualquer
spp <- c("Ocotea guianensis", "Ocotea longifolia", "Licaria tenuicarpa")
dap <- c(10, 20, 30)
dd <- data.frame(ESPECIE = spp, DAP = dap, stringsAsFactors = F)
dd
ESPECIE DAP
Ocotea guianensis 10
Ocotea longifolia 20
Licaria tenuicarpa 30

Vamos criar uma função para receber um vetor de texto e quebrá-lo segundo o separador sep = " ", isto é, vai quebrar a palavra onde houver um espaço " ".

# criamos um funcao
peganome <- function(x, qual = 1, sep = " ") {
  # pega o texto e quebra segundo o separador informado
  xx <- strsplit(x, split = sep)[[1]]
  # retorna o valor
  return(xx[qual])
}

Com a função criada acima, peganome(), vamos utilizar a função lapply() para aplicar esta função sobre cada elemento do vetor dd$ESPECIE:

# usamos essa funcao com o lapply para pegar o genero
lapply(dd$ESPECIE, peganome, qual = 1)
## [[1]]
## [1] "Ocotea"
## 
## [[2]]
## [1] "Ocotea"
## 
## [[3]]
## [1] "Licaria"

Repare que o resultado da ação é uma lista, pois lapply() sempre retorna uma lista! Podemos converter esta lista em um vetor usando o procedimento abaixo:

# como é uma lista transformamos num vetor
gg <- as.vector(lapply(X = dd$ESPECIE, FUN = peganome, qual = 1), mode = "character")
gg
## [1] "Ocotea"  "Ocotea"  "Licaria"

Podemos também gerar um vetor de texto simplesmente utilizando a função vapply() para aplicar a função peganome() em cada elemento de dd$ESPECIE, informando à função vapply() que esperamos como retorno um vetor de texto de tamanho 1 (FUN.VALUE = as.character(1)). O resultado é o mesmo obtido no objeto gg:

vapply(X = dd$ESPECIE, FUN = peganome, FUN.VALUE = as.character(1), qual = 1)
##  Ocotea guianensis  Ocotea longifolia Licaria tenuicarpa 
##           "Ocotea"           "Ocotea"          "Licaria"

Agora que temos o nome do gênero separado do epíteto, vamos guardar o nome do gênero estocado em gg no data.frame dd:

# ADICIONAMOS NO OBJETO ORIGINAL
dd$GENERO <- gg
dd
ESPECIE DAP GENERO
Ocotea guianensis 10 Ocotea
Ocotea longifolia 20 Ocotea
Licaria tenuicarpa 30 Licaria

Agora vamos pegar o epíteto da espécie, mudando o argumento qual da função peganome():

# usamos essa funcao com o lapply para pegar o epiteto
spp <- as.vector(lapply(dd$ESPECIE, peganome, qual = 2), mode = "character")
spp
## [1] "guianensis" "longifolia" "tenuicarpa"

Repetimos então o procedimento de guardar o epíteto spp em nosso data.frame dd:

# adiciona
dd$SPP <- spp
# mostra
dd
ESPECIE DAP GENERO SPP
Ocotea guianensis 10 Ocotea guianensis
Ocotea longifolia 20 Ocotea longifolia
Licaria tenuicarpa 30 Licaria tenuicarpa
str(dd)
## 'data.frame':    3 obs. of  4 variables:
##  $ ESPECIE: chr  "Ocotea guianensis" "Ocotea longifolia" "Licaria tenuicarpa"
##  $ DAP    : num  10 20 30
##  $ GENERO : chr  "Ocotea" "Ocotea" "Licaria"
##  $ SPP    : chr  "guianensis" "longifolia" "tenuicarpa"

8.1.3 Altera caixa do texto

As funções toupper() e tolower() mudam a caixa do texto, isto é, convertem o texto para totalmente maiúsculas ou minúsculas, respectivamente.

txt <- "Um texto QUALQUER"
# transforma para caixa baixa
tolower(txt)
## [1] "um texto qualquer"
# caixa alta
toupper(txt)
## [1] "UM TEXTO QUALQUER"
# apenas a primeira letra em maiusculo
# temos que construir nossa funcao para isso
mudatexto <- function(x) {
  xx <- strsplit(x, split = "")[[1]]
  xx <- tolower(xx)
  xx[1] <- toupper(xx[1])
  xx <- paste(xx, collapse = "")
  return(xx)
}
txt
## [1] "Um texto QUALQUER"
mudatexto(txt)
## [1] "Um texto qualquer"

8.1.4 Remove espaços em branco

A função trimws() remove espaços em branco no início e final do texto.
Repare que isso ocorre no objeto txt2 criado abaixo:

txt2 <- "   Outro texto com espacos em branco no inicio e fim     "

Podemos removê-los usando a função trimws() e informando para o argumento which em qual(is) posição(ões) queremos remover os espaços em branco. Vejamos:

# Remove dos dois lados
trimws(txt2, which = "both")
## [1] "Outro texto com espacos em branco no inicio e fim"
# remove do lado direito
trimws(txt2, which = "right")
## [1] "   Outro texto com espacos em branco no inicio e fim"
# Remove do lado esquerdo
trimws(txt2, which = "left")
## [1] "Outro texto com espacos em branco no inicio e fim     "

8.1.5 Substitui ou pega parte de texto

A função gsub() busca padrões de um texto para substituí-los por outro em um vetor de caracteres.

Já a função substr() extrai ou substitui pedaços de um texto em um vetor de caracteres.

Por fim, a função nchar() conta o número de caracteres contidos em um vetor de caracteres.

# substitui palavras ou parte de palavras
txt <- "Um texto que contém muita COISA"
gsub("m", "M", txt)
## [1] "UM texto que contéM Muita COISA"
gsub("que", "o qual", txt)
## [1] "Um texto o qual contém muita COISA"
# pega uma parte do texto
substr(txt, 1, 10)
## [1] "Um texto q"
substr(txt, 11, 20)
## [1] "ue contém "
# qual o numero de caracteres num texto
nchar(txt)
## [1] 31
substr(txt, nchar(txt) - 10, nchar(txt)) # dez ultimos caracteres
## [1] "muita COISA"

8.1.6 Expressões Regulares

Expressões regulares são modelos lógicos para buscar caracteres em textos. É uma ferramenta muito poderosa usada em computação. Não é fácil entender e usar expressões regulares, mas é muito poderoso e importante conhecer os recursos. Aqui apresentamos apenas um exemplo muito simples do uso de expressões regulares.

txt <- "Um texto com varios numeros 1 2 9 no meio e 5 7 8 20 3456"
txt
## [1] "Um texto com varios numeros 1 2 9 no meio e 5 7 8 20 3456"
# imagine que eu queira apagar todos os números desse texto
gsub("[0-9]", "", txt) # paga números de 0 a 9
## [1] "Um texto com varios numeros    no meio e     "
# todas as letras minusculas
gsub("[a-z]", "", txt)
## [1] "U     1 2 9    5 7 8 20 3456"
# todas as letras
gsub("[a-z|A-Z]", "", txt)
## [1] "     1 2 9    5 7 8 20 3456"
# todas as minúculas e numeros
gsub("[a-z|0-9]", "", txt)
## [1] "U               "

Entender expressões regulares (regular expressions) é muito importante porque é fonte frequente de erros quando escrevemos códigos. Algumas funções que buscam por padrões interpretam expressões regulares para buscar por padrões em objetos de texto. Por exemplo, gsub() e grep() são duas funções parecidas, que buscam por um padrão (argumento pattern) em textos. Enquanto gsub() busca e permite substituir o padrão por outro texto, grep() retorna o índice do elemento que contém o padrão de interesse. O argumento pattern, citado anteriormente, pode ser um texto simples ou uma expressão regular, então é importante que você entenda um pouco sobre isso. Expressões regulares podem ser bem complexas e até existem sites que lhe permitem entender e gerar uma expressão regular para uma busca especifica (veja abaixo na seção Para saber mais).

8.1.7 Remove Acentos

Remover acentos e padronizar textos podem ser importantes quando estamos comparando dados. Por exemplo, quando queremos verificar se os nomes que aparecem em uma coluna estão padronizados, devemos checar se há duplicidade de palavras devido à presença de um acento em uma palavra e ausência em outra (e.g., odores “cítrico” e “citrico” são computados como duas palavras diferentes). Não há uma função específica, mas como tudo pode ser feito no R, uma solução é mostrada aqui.

txt <- "Palavrão ou bobalhão têm acentos"
# convertemos quebrando acentos
xx <- iconv(txt, to = "ASCII//TRANSLIT")
xx
## [1] "Palavrao ou bobalhao tem acentos"
# apagamos os acentos sozinhos
xx <- gsub("[~|^|~|\"|'|`]", "", xx)
xx
## [1] "Palavrao ou bobalhao tem acentos"
# podemos colocar isso numa funcao
removeacentos <- function(x) {
  xx <- iconv(x, to = "ASCII//TRANSLIT")
  xx <- gsub("[~|^|~|\"|'|`]", "", xx)
  return(xx)
}

# usando a funcao criada
removeacentos(txt)
## [1] "Palavrao ou bobalhao tem acentos"
txt2 <- "macarrão também tem acentos, né?"
removeacentos(txt2)
## [1] "macarrao tambem tem acentos, ne?"

8.1.8 Metacaracteres

Metacaracteres são caracteres que em uma expressão regular apresentam um significado especial e por isso não são interpretados como tal. Alguns metacaracteres são:

  • . encontra qualquer coisa que exista;

  • + encontra o item que precede esse metacaractere uma ou mais vezes;

  • * encontra o item que precede esse metacaractere zero ou mais vezes;

  • ^ encontra qualquer espaço vazio no início da linha. Quando usado numa expressão de classe de caractere (veja abaixo), encontra qualquer caractere exceto aqueles que seguem esse metacaractere;

  • \$ encontra qualquer espaço vazio no final da linha;

  • | operador que significa OU e é utilizado em buscas do tipo busque isso OU aquilo;

  • ( e ) para agrupamentos;

  • [ e ] definem classes de caracteres em expressões regulares (veja abaixo na seção Classes de caracteres).

Suponha que você importe ao R um conjunto de dados de uma planilha usando, por exemplo, a função read.table(), e que os nomes das colunas no original tinham muitos espaços em branco. Como o R não aceita espaços em branco, ele substitui espaços por pontos ao utilizar esta função, então os nomes das colunas do data.frame dos dados terão muitos pontos, algumas vezes em sequência. Suponha também que isso acontece toda vez que você importa dados ao R e já está cansado desse comportamento e você quer colocar no seu script um comando que elimina esses pontos, de forma a não se preocupar com isso no editor de planilhas onde estão os seus dados.

# vamos criar um data.frame que simule esta situação
Número.....da.......árvore <- c(1, 10, 15)
Diâmetro..cm. <- c(10, 15, 20)
Altura.....m.... <- c(15, 20, 39)
dados <- data.frame(Número.....da.......árvore, Diâmetro..cm., Altura.....m....)
class(dados)
## [1] "data.frame"
dim(dados)
## [1] 3 3
colnames(dados)
## [1] "Número.....da.......árvore" "Diâmetro..cm."             
## [3] "Altura.....m...."
# nome de colunas, que também neste caso pode ser obtido com
names(dados)
## [1] "Número.....da.......árvore" "Diâmetro..cm."             
## [3] "Altura.....m...."
nn <- names(dados)
# então digamos que eu queira substituir pontos multiplos por apenas 1 ponto. como eu quero manter 1 ponto eu poderia repetir várias vezes um substituicao de dois pontos por um ponto:
gsub(pattern = "..", replacement = ".", nn)
## [1] "............." "......."       "........"
# estranho né? tudo virou ponto

# isso é porque . é um metacaractere e o R não interpreta isso como ponto. Ponto significa pega 'pega qualquer caractere' numa expressão regular

# pelo fato de ponto ser um metacaractere, precisamos indicar que queremos o caractere ponto e não o metacaractere. Você faz isso usando uma ou duas barras invertidas (backslash)
gsub(pattern = "\\.\\.", replacement = ".", nn)
## [1] "Número...da....árvore" "Diâmetro.cm."          "Altura...m.."
# diminuiu o número de pontos, mas preciso fazer isso várias vezes para ficar com um único ponto
nn2 <- gsub(pattern = "\\.\\.", replacement = ".", nn)
nn2 <- gsub(pattern = "\\.\\.", replacement = ".", nn2)
nn2
## [1] "Número..da..árvore" "Diâmetro.cm."       "Altura..m."
nn2 <- gsub(pattern = "\\.\\.", replacement = ".", nn2)
nn2
## [1] "Número.da.árvore" "Diâmetro.cm."     "Altura.m."
# pronto consegui

# note que isso fica restrito a quantos pontos tem nos meus dados, e portanto, não é um método genérico ou eficiente em termos de programação, por o número que vezes que isso deve ser feito irá variar dependendo do conjunto de dados.

# seria mais fácil adicionar um metacaractere (+) e fazer isso uma única vez, sem necessidade de repetição:
nn
## [1] "Número.....da.......árvore" "Diâmetro..cm."             
## [3] "Altura.....m...."
nn2 <- gsub(pattern = "\\.+", replacement = ".", nn)
nn2
## [1] "Número.da.árvore" "Diâmetro.cm."     "Altura.m."
# procurar por qualquer letra+
txt <- "qualqueeeeeer palavra"
gsub("e+", "E", txt)
## [1] "qualquEr palavra"
# da mesma forma se eu quiser buscar pelo caractere + ao invés de usar o metacaractere, preciso usar a barra invertida:
txt <- c("um texto com simbolo +", "sem o simbolo")
gsub("+", "MAIS", txt) # nao funciona
## [1] "MAISuMAISmMAIS MAIStMAISeMAISxMAIStMAISoMAIS MAIScMAISoMAISmMAIS MAISsMAISiMAISmMAISbMAISoMAISlMAISoMAIS MAIS+MAIS"
## [2] "MAISsMAISeMAISmMAIS MAISoMAIS MAISsMAISiMAISmMAISbMAISoMAISlMAISoMAIS"
gsub("\\+", "MAIS", txt) # funciona
## [1] "um texto com simbolo MAIS" "sem o simbolo"

8.1.9 Classes de caracteres

Algumas classes de caracteres podem ser utilizadas em buscas com expressões regulares:

  • [0-9] - busca números no vetor de caracteres;

  • [a-z] - busca apenas caracteres minúsculos no vetor de caracteres;

  • [A-Z] - busca apenas caracteres maiúsculos no vetor de caracteres;

  • [a-zA-Z] - busca caracteres do alfabeto no vetor de caracteres;

  • [^a-zA-Z] - busca não alfabéticos no vetor de caracteres;

  • [a-zA-Z0-9] - busca elementos alfa-numéricos no vetor de caracteres;

  • [ \t\n\r\f\v] - busca espaçamentos no vetor de caracteres. Espaçamentos podem ser quebras de linha, tabulações etc;

  • []$*+.?[^{|(#%&~_/⇔✬!,:;❵\")}@-] - busca caracteres de pontuação no vetor de caracteres.

Perceb-se que estas classes permitem fazer buscas complexas. Suponha que você precisa substituir todos os valores que não contenham letras ou números por NA:

# suponha um vetor de palavras (nomes de cores neste exemplo)
vt <- colors()
# vejamos os 30 primeiros valores
head(vt, 30)
##  [1] "white"          "aliceblue"      "antiquewhite"   "antiquewhite1" 
##  [5] "antiquewhite2"  "antiquewhite3"  "antiquewhite4"  "aquamarine"    
##  [9] "aquamarine1"    "aquamarine2"    "aquamarine3"    "aquamarine4"   
## [13] "azure"          "azure1"         "azure2"         "azure3"        
## [17] "azure4"         "beige"          "bisque"         "bisque1"       
## [21] "bisque2"        "bisque3"        "bisque4"        "black"         
## [25] "blanchedalmond" "blue"           "blue1"          "blue2"         
## [29] "blue3"          "blue4"
# podemos mostrar os valores que contém branco:
grep("white", vt, value = TRUE)
##  [1] "white"         "antiquewhite"  "antiquewhite1" "antiquewhite2"
##  [5] "antiquewhite3" "antiquewhite4" "floralwhite"   "ghostwhite"   
##  [9] "navajowhite"   "navajowhite1"  "navajowhite2"  "navajowhite3" 
## [13] "navajowhite4"  "whitesmoke"
# se eu coloco white numa expressao regular
# vejamos os 30 primeiros valores
grep("[white]", vt, value = TRUE)[1:30]
##  [1] "white"          "aliceblue"      "antiquewhite"   "antiquewhite1" 
##  [5] "antiquewhite2"  "antiquewhite3"  "antiquewhite4"  "aquamarine"    
##  [9] "aquamarine1"    "aquamarine2"    "aquamarine3"    "aquamarine4"   
## [13] "azure"          "azure1"         "azure2"         "azure3"        
## [17] "azure4"         "beige"          "bisque"         "bisque1"       
## [21] "bisque2"        "bisque3"        "bisque4"        "blanchedalmond"
## [25] "blue"           "blue1"          "blue2"          "blue3"         
## [29] "blue4"          "blueviolet"
# neste caso significa pegue qualquer elemento que contenha uma das letras indicadas: w, h, i, t ou e. Por isso a lista aumentou
# pega só cores que tenham número
# vejamos os 30 primeiros valores
grep("[0-9]", vt, value = TRUE)[1:30]
##  [1] "antiquewhite1" "antiquewhite2" "antiquewhite3" "antiquewhite4"
##  [5] "aquamarine1"   "aquamarine2"   "aquamarine3"   "aquamarine4"  
##  [9] "azure1"        "azure2"        "azure3"        "azure4"       
## [13] "bisque1"       "bisque2"       "bisque3"       "bisque4"      
## [17] "blue1"         "blue2"         "blue3"         "blue4"        
## [21] "brown1"        "brown2"        "brown3"        "brown4"       
## [25] "burlywood1"    "burlywood2"    "burlywood3"    "burlywood4"   
## [29] "cadetblue1"    "cadetblue2"
# neste caso é o mesmo que nao-alfabeticos
# vejamos os 30 primeiros valores
grep("[^a-zA-Z]", vt, value = TRUE)[1:30]
##  [1] "antiquewhite1" "antiquewhite2" "antiquewhite3" "antiquewhite4"
##  [5] "aquamarine1"   "aquamarine2"   "aquamarine3"   "aquamarine4"  
##  [9] "azure1"        "azure2"        "azure3"        "azure4"       
## [13] "bisque1"       "bisque2"       "bisque3"       "bisque4"      
## [17] "blue1"         "blue2"         "blue3"         "blue4"        
## [21] "brown1"        "brown2"        "brown3"        "brown4"       
## [25] "burlywood1"    "burlywood2"    "burlywood3"    "burlywood4"   
## [29] "cadetblue1"    "cadetblue2"
# que tenham caracteres maiusculos
grep("[A-Z]", vt, value = TRUE) # nao tem nenhum neste vetor
## character(0)
# minusculos
# vejamos os 30 primeiros valores
grep("[a-z]", vt, value = TRUE)[1:30]
##  [1] "white"          "aliceblue"      "antiquewhite"   "antiquewhite1" 
##  [5] "antiquewhite2"  "antiquewhite3"  "antiquewhite4"  "aquamarine"    
##  [9] "aquamarine1"    "aquamarine2"    "aquamarine3"    "aquamarine4"   
## [13] "azure"          "azure1"         "azure2"         "azure3"        
## [17] "azure4"         "beige"          "bisque"         "bisque1"       
## [21] "bisque2"        "bisque3"        "bisque4"        "black"         
## [25] "blanchedalmond" "blue"           "blue1"          "blue2"         
## [29] "blue3"          "blue4"

8.1.10 Barra invertida \

O uso da barra invertida \ (em inglês, backslash) indica ao R que a expressão regular espera lidar com tabulações, quebra de linhas e outros símbolos especiais. Isso é fundamental se você quer incluir/buscar num texto por aspas, parênteses, colchetes, barras, metacaracteres, etc.

?Quotes # leia atentamente esse help
# criar um texto com aspas

Se você tentar executar o comando abaixo, perceberá que ele não funciona, porque as aspas são utilizadas para abrir e fechar textos.

txt = "Um texto que tem "aspas" no meio" 

Porém, podemos combinar aspas simples e duplas para fazer isso:

txt <- 'Um texto que tem "aspas" no meio'
txt
## [1] "Um texto que tem \"aspas\" no meio"
# veja que o R adicionou uma \ antes das aspas do texto, criando um escape character, que indica ao R que não interprete aquilo como abertura ou fechamento de textos
# entao eu posso fazer isso
txt <- "Um texto que tem \"aspas\" "
txt
## [1] "Um texto que tem \"aspas\" "
# também funciona

# isso é importante quando buscamos valores
txt <- c("com \"aspas\"", "outro objeto")
grep("\"", txt) # qual elemento tem aspas
## [1] 1
txt[grep("\"", txt)]
## [1] "com \"aspas\""
# barras
txt <- c("com \\ barras", "sem barras")
txt
## [1] "com \\ barras" "sem barras"
grep("\\\\", txt) # isso é mais complicado, veja o número de barras que preciso para buscar por 1 barra num texto, porque barra desse jeito é o código que ele usa para buscar por códigos, então complica né?
## [1] 1
# parentesis
txt <- c("sem parenteses", "com ()")
grep("(", txt) # isso não funciona por parentesis é usado para abrir e fechar funcoes, o R se atrapalha
grep("\\(", txt) # assim funciona
grep("\\)", txt)