Código
# carregando o stringr através do tidyverse
library(tidyverse)
Pedro de Brito Neto
1 de julho de 2021
Um desafio muito grande na manipulação de dados é extrair informações de caracteres. Em resumo caracteres são letras, símbolos, sinais, números que representem algo escrito, etc.. Essa sequência de caracteres formam o que chamamos de string
. Diversas vezes encontramos variáveis com categorias não padronizadas, como, por exemplo, uma variável contendo “São Paulo”, “sao paulo” e “sp”. Apesar de representarem o mesmo estado, elas são diferentes. Nesse sentido, uma parte muito importante no tratamento de dados é “lapidar” esse conjunto de caracteres para que seja possível usá-los nas análises.
Algumas tarefas relacionadas a string’s podem ser listadas, como:
Uma das principais soluções para isso é fazer o uso de funções e expressões regulares, do inglês regular expressions (Regex). O proprio R possui algumas funções que nos ajudam a trabalhar com string’s. Porém, o pacote stringr
pertencente ao universo tidyverse
deixa essas (e outras) funções de uma maneira muito mais intuitiva de ser utilizada.
Vamos primeiro mostrar algumas funções básicas do pacote que podem nos auxiliar nessa manipulação de caracteres e depois falaremos sobre as expressões regulares. Com um breve conhecimento em ambas as partes (funções e Regex), podemos juntá-las e com isso tornar nossas análises muito mais fáceis.
Para usar o pacote podemos importá-lo diretamente ou através do tidyverse. Para o post vamos carregar o tidyverse pois iremos utilizar algumas funções de outros pacotes. Vale lembrar que já fizemos um post sobre o tidyverse
e você pode acessá-lo clicando aqui.
str_length
A primeira função que vamos falar é a str_length
. Essa é uma função bem simples do stringr
que tem como objetivo fornecer o número de caracteres de uma string, ou seja, o comprimento da string (não confunda com o length()
de um vetor). Uma observação é que para valores NA
ela irá retornar NA
, ao invés de contar o número de caracteres na string NA
. Vamos a um exemplo
[1] | 7 | 3 | 8 | 3 | 10 | NA | 1 |
str_sub
É comum você precisar pegar partes fixas de strings, como apenas a parte final das strings ou apenas os 2 primeiros caracteres. Essa função possui 5 argumentos, os quais podem ser obtidos utilizando o help do R (rodar ?str_sub
no console). Os três primeiros argumentos são em geral os mais utilizados. O primeiro irá receber um vetor de caracteres, o objeto no qual a função irá fazer as buscas. O segundo start
você indica onde irá começar e o argumento end
indica até onde ir na busca. Para este exemplo em particular vamos criar uma vetor mais organizado para ficar mais claro.
Primeiro vamos supor que a gente queira pegar apenas os nomes. Como nesse caso os caracteres possuem tamanhos diferentes para os nomes, mas a idade possui dois dígitos para todos. Uma característica interessante do str_sub()
é sua capacidade de trabalhar com índices negativos nas posições start e end. Quando usamos uma posição negativa, str_sub() conta regressivamente a partir do último caracter:
[1] | “João” | “Maria” | “Pedro” | “Tereza” |
Caso a gente queira pegar apenas as idade podemos utilizar o argumento start
para indicar onde iremos começar.
[1] | “42” | “29” | “20” | “50” |
Também é possível utilizar o start
e o end
em conjunto.
[1] | “SP” | “MG” | “RJ” | “ES” |
str_trim
Dentro de um conjunto de dados é comum que a gente encontre textos com espaços a mais ou em lugares que não deveriam ter espaços. Espaços antes e após o texto são especialmente chatos, pois pode ser difícil detectá-los. Isso pode acontecer principalmente quando estamos tratando de dados provenientes de formulários que contém respostas abertas, ou seja, cada usuário pode escrever da forma que preferir. Isso pode gerar alguns problemas, como criar categorias diferentes para valores que deveriam ser iguais. A função str_trim
pode nos auxiliar na resolução desse problema removendo os espaços excedentes antes e depois da string. Os argumentos da função são dois: o objeto , e side
, onde você indica o lado que deseja tirar os espaços, com as opções side = c("both", "left", "right"))
.
[1] | “vetor” | “para” | “exemplificar” | “o uso” | “da função” |
[1] | “vetor” | ” para” | “exemplificar” | “o uso” | ” da função” |
[1] | “vetor” | “para” | “exemplificar” | “o uso” | “da função” |
str_detect
e str_which
Caso a gente queira detectar alguma característica dentro de um vetor como números, letras ou palavras por exemplo, poderíamos fazer o uso da função str_detect
.
Como dito antes, iremos utilizar algumas outras funções e uma delas é o operador pipe (%>%
). No post sobre tidyverse
ensinamos a usar esse operador.
[1] | TRUE | FALSE | FALSE | FALSE | TRUE | TRUE |
[1] | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE |
Note que na segunda vez que utilizamos função, escrevemos “centimetro” e o vetor retornou um TRUE
na posição dois, entretando ela está no plural (centimetros). É exatamento isso que a função faz: ela não está buscando toda a palavra, ela busca padrões, qualquer string que tiver, nesse caso, “centimetro” ele irá retornar o valor TRUE
. Neste mesmo conceito, existe a função str_which
que no lugar de retornar um vetor booleano, ela retorna as posições no vetor em que a string buscada foi encontrada.
[1] | 2 | 3 |
str_subset
Digamos agora que a gente queira não só detectar essas características (números, pontuação, letras), mas utilizá-las. A função str_subset()
retorna essas strings compatíveis com as características fornecidas, sendo possível armazenar essas strings em um objeto, por exemplo.
[1] | “1 metro e 60 centimetros” | “191 cm” |
str_remove
e str_remove_all
A função str_remove
é usada para remover padrões das strings, por exemplo, você pode desejar tirar alguma letra, algum número ou algum caractere. Essa função (mas não só ela) possui umas variante que é str_remove_all
que basicamente funciona da mesma maneira que a primeira, porém ela vai remover TODOS os padrões dentro da mesma string e não somente a primeira possibilidade. Vamos demonstrar em um exemplo.
[1] | “1.5” | “1 metro e 60 centimetros” |
[3] | “191 cm” | “167” |
[5] | “1,5 metros” | “18” |
[1] | “1.5” | “1 metro e 60 centimetros” |
[3] | “191 cm” | “167” |
[5] | “1,5 metros” | “1” |
Observe que ao utilizar o str_remove
, ainda ficou um “8” no último objeto. Isso acontece basicamente porque essa função remove o padrão escolhido de todas as strings, entretando apenas o primeiro encontrado. O str_remove_all
é uma alternativa caso queira contornar isso.
str_replace
e str_replace_all
Agora digamos que a gente não queira remover certos padrões e sim substituir por alguma outra coisa. A função str_replace
nos auxilia nisso e assim como a str_remove
, ela também possui a variante str_replace_all
. Essas funções possuem 3 argumentos. Em resumo, o primeiro argumento string
irá receber um vetor de caracteres. O segundo argumento pattern
irá receber o padrão a ser procurado. Por fim, o terceiro argumento replacement
recebe um vetor de caracteres de substituições e deve ter comprimento um ou o mesmo comprimento que string
ou pattern
. Vamos a um exemplo simples.
[1] | “1.85” | “1 metro e 60 cm.” | “191 cm.” | “167” |
[5] | “1,58 cm.” | “188” |
[1] | “1.85” | “1 metro e 60 cm2.” | “191 cm1.” | “167” |
[5] | “1,58” | “188” |
Agora que já conhecemos algumas das funções do pacote stringr
, vamos falar um pouco sobre as expressões regulares (regex). Para trabalhar com textos de uma maneira fácil, é necessário saber um pouco de regex. Elas permitem identificar conjuntos de caracteres, palavras e outros padrões por meio de uma sintaxe concisa. O pacote divide algumas “características” das expressões regulares em subgrupos, como são muitos e normalmente seguem um padrão vamos falar apenas de algumas. Você pode ter acesso a toda parte de regex através do seguinte cheat sheet. Para facilitar o entendimento, após introduzir uma base das expressões regulares, vamos seguir com os exemplos práticos, exemplicando passo a passo o que está acontecendo.
x|y
ou[xy]
qualquer um de[^xy]
qualquer coisa menosx-y
entre^x
começo da stringx$
fim da stringx(?=y)
seguido porx(?!y)
não seguido por(?<=x)y
precedido por(?<!x)y
não precedido porx?
zero ou umx*
zero ou maisx+
um ou maisx{n}
exatamente “n”x{n,}
“n” ou maisx{n,m}
entre “n” e “m”\\s
espaço; \\S
não espaço
\\d
qualquer dígito; \\D
não dígito
\\w
qualquer caractere de palavra
[:lower:]
letra minusculas
[:upper:]
letras maiusculas
[:punct:]
pontuação
[:graph:]
letras, números e pontuações
.
qualquer coisa (caso queira usar o “.” em sua forma literal, use \\.
)
Mostramos acima algumas classes e operadores interesserantes e que geralmente são muito utilizados. Vamos agora mostrar em alguns exemplos como eles funcionam.
Vamos criar então 2 vetores contendo as mesmas informações (3 modelos de carros), porém o segundo vetor terá espaços excedentes no meio das palavras. Imagino que poderíamos ter um conjunto de dados muito grande, “corrigir” um caractere por vez se tornaria improdutivo. Então, vamos pensar em uma solução para este exemplo usando regex.
[1] | FALSE |
Usamos a função identical
para mostrar que o R reconhece que os vetores não são iguais. Para resolver esse problema, podemos usar a seguinte lógica: sabemos que o correto é haver apenas um espaço entre as palavras. Qualquer quantidade de espaços que for maior que um, iremos corrigir. Podemos fazer isso combinando regex com a função str_replace_all
.
[1] | “Ford Mustang” | “Chevrolet Camaro” | “Cherry Tigo” |
[1] | TRUE |
Como já vimos, a função str_replace_all
nos ajuda a fazer substiuições de caracteres. Nesse caso, a regex \\s
significa “espaço”. O {2,}
indica que queremos identificar algo se repita duas ou mais vezes. Então a função basicamente irá identificar toda vez que um espaço aparecer seguidamente duas ou mais vezes e vai substituir por apenas um espaço. Note que agora o identical retorna true
, ou seja, os vetores são iguais. Lembrando que já falamos da str_trim()
que remove espaços no começo e no fim dos caracteres, agora sabemos como tratar qualquer tipo de problema relacionado a espaços excedentes.
Vamos agora voltar para um vetor que já utilizamos acima. Devido a maneira com que os caracteres estão armazenadas nesse vetor, fica complicado transformar esses caracteres em informações. Ao utilizarmos expressões regulares de uma forma bem simples, já conseguimos trabalhar com essas informaçõs.
[1] | “1.85” | “1 metro e 60 centimetros” |
[3] | “191 cm” | “167” |
[5] | “1,58 metros” | “188” |
[1] | “185” | “160” | “191” | “167” | “158” | “188” |
Observe que ao utilizar o \\D
, obtivemos apenas números. Sendo assim já conseguimos uniformizar a maneira com que as informações estão sendo exibidas. A partir daqui já podemos até obter algumas estatísticas descritivas, como a média por exemplo.
[1] | 174.8333 |
Note que para obter a média foi preciso transformar o vetor altura, originalmente da classe character
em numeric
.
Vamos agora colocar em prática tudo o que vimos em um banco de dados fictício que iremos criar. Suponha que uma empresa nacional de aluguel de veículos esteja interessada em obter algumas informações. Para isso ela irá aplicar um questionário tendo como público as pessoas que já alugaram seus veículos. No questionário as pessoas deveriam passar as seguintes informações: “Marca/modelo do veículo”, “Alugaria esse veículo novamente?”, “Grau de satisfação com a empresa”, “Qual sua cidade/estado (sigla)”. Todas as variáveis do questionario foram do tipo resposta aberta, ou seja, os respondentes poderiam escrevem de qualquer maneira suas respostas. Assim gerou-se uma dificuldade em sumarizar e obter informações relevantes desse banco de dados, já nas respostas foram encontradas erros como, espaços excedentes, palavras iguais escritas de formas diferentes, discordância entre letras maiúsculas e minúsculas, entre outros.
Torna-se necessário então realizar um bom tratamento nesses caracteres para que seja possível extrair todas as informações. Vamos então utilizar o queridinho stringr
para nos ajudar.
Existem diferentes formas de criar um banco de dados. Para este exemplo vamos criar 4 vetores e transformá-los em um data.frame
.
# veículos da locadora: "Honda Civic", "Toyota Corolla", "Fiat Argo" e "Ford Fusion"
veiculo <- c(
"Honda civic", "honda - civic", " fiat Argo", "FORD FUSION ",
"toyota Corolla", "Toyota COROLLA", "Fiat / argo",
" HONDA civic", "ford fusion ", "toyota COROLLa"
)
alugaria_de_novo <- c(
"SIM", "Sim", "nao", "sim", "não", "NAOOO", "Não",
"sim", "NÃO", "Nao"
)
grau_de_satisfacao <- c(
"8.1", "10", "3,23", "6", " 5,3", "0,0000",
"7.7", "9 ", "5", "6,666"
)
cidade <- c(
"Vitória - ES", "sao paulo - sp", "Rio de Janeiro /Rj",
"BELO HORIZONTE MG", " vitoria Es", "Florianopolis / sc ",
"TRES CORAÇÕES - mg", "salvador BA", "São Paulo - SP",
" belo horizonte / mg"
)
dados <- data.frame(
veiculo,
alugaria_de_novo,
grau_de_satisfacao,
cidade
)
veiculo | alugaria_de_novo | grau_de_satisfacao | cidade |
---|---|---|---|
Honda civic | SIM | 8.1 | Vitória - ES |
honda - civic | Sim | 10 | sao paulo - sp |
fiat Argo | nao | 3,23 | Rio de Janeiro /Rj |
FORD FUSION | sim | 6 | BELO HORIZONTE MG |
toyota Corolla | não | 5,3 | vitoria Es |
Toyota COROLLA | NAOOO | 0,0000 | Florianopolis / sc |
Fiat / argo | Não | 7.7 | TRES CORAÇÕES - mg |
HONDA civic | sim | 9 | salvador BA |
ford fusion | NÃO | 5 | São Paulo - SP |
toyota COROLLa | Nao | 6,666 | belo horizonte / mg |
Assim ficou o nosso data.frame
. Note que além dos erros visíveis, ainda temos problemas com espaços a mais antes, depois e no meio das palavras que apesar de não estar visível na apresentação da tabela, eles existem.
Vamos fazer o nosso tratamento de dados variável por variável. Iremos mostrar maneiras diferentes de organizar essas informações, lembrando que isso pode ser feito de diversas formas.
Olhando para a variável “veiculo” conseguimos notar um certo padrão. Todas as observações contém a marca do veículo seguida pelo modelo. A partir daqui já podemos pensar em algumas formas de tratamento, como, criar uma variável apenas com a marca e outra com o modelo. Note que a marca e modelo são separados ou por espaço, ou por um “-” ou por “/”. Perceber padrões no seu banco de dados é interessante para o tratamento.
dados <- dados %>%
mutate(
marca = veiculo %>%
str_trim() %>%
str_remove("\\s.+|\\s\\(.+|/.+") %>%
str_to_upper(),
modelo = veiculo %>%
str_trim() %>%
str_extract("\\s.+|\\s\\(.+|/.+") %>%
str_remove("[:punct:]") %>%
str_trim() %>%
str_to_upper(),
marca_modelo = paste(marca, modelo)
)
dados <- dados %>%
subset(select = -c(veiculo))
Acabamos de utilizar a função mutate
para criar 3 novas variáveis a partir da variável original (veiculos
), sendo elas: marca
, modelo
e marca_modelo
. Basicamente utilizamos as funções já mostradas ateriormente como a str_trim
para remover os espaços antes e depois do characteres, também utilizamos o str_remove
e str_extract
, ambos combinados com as mesmas expressões regulares (\\s.+|\\s\\(.+|/.+
). Em resumo essa regex detecta todo “espaço” seguido por qualquer coisa ou “espaço” seguido por um parênteses aberto seguido por qualquer coisa ou uma “/” seguida por qualquer coisa. Também utilizamos o str_remove("[:punct:]")
e str_to_upper
para deixar todas as letras sem acentos, maiúsculas e padronizadas. Retiramos do banco de dados a variável original veiculos
já que se tornou inutilizável.
A variável alugaria_de_novo
é do tipo binária que contém, ou pelo menos deveria conter, dois tipo de respostas: “SIM” e “NÃO”. De fato todas as respostas estão representando SIM ou NAO, entretanto, devido às diferentes maneiras com que elas foram escritas, fica difícil extrair informações. Vamos então tratar essa variável.
[1] | “SIM” | “SIM” | “NAO” | “SIM” | “NAO” | “NAO” | “NAO” | “SIM” | “NAO” | “NAO” |
Para este caso usamos o str_replace
junto com expressões regulares para substituir palavras que representam “sim” e “não” por “SIM” e o “NAO”, respectivamente. As regex usadas para esse caso foram "^S|s[Ii]"
, que representa “todo S ou s seguido por I ou i observado no início da string” e ^N|n[Aaãâ]
que representa “todo n ou N seguido por A, a, ã ou â”. Após isso, usamos o str_sub
para pegar apenas as três primeiras letras. Vale ressaltar que caso outras palavras apareçam na resposta, ela não vai funcionar, nesse caso será preciso utilizar outras funções ou apenas “aprimorar” as expressões regulares utilizadas.
Para o grau de satisfação as pessoas deram notas de 0 a 10. Notamos que algumas pessoas separaram os números utilizando “.” e outras utilizando “,”. O R entende essa variável como character
e pode ser interessante transformar essa variável em numérica (igual fizemos anteriormente) para realizar algumas análises quantitativas. O R não irá reconhecer como número uma variável com duas formas diferentes de separação para o dígito (“.” e “,”), por exemplo, para um vetor do tipo c("3.1", "3,1")
é necessário padronizar essa variável utilizando o mesmo separador.
[1] | 8.1 | 10.0 | 3.2 | 6.0 | 5.3 | 0.0 | 7.7 | 9.0 | 5.0 | 6.7 |
Substituímos todas as vírgulas por pontos por meio da função str_replace
, depois foi necessário transformar a variável em númerica, fizemos isso por meio da função as.numeric
. Logo após, utilizamos a função round
para arredondar os números para uma casa decimal. A parte de arredondar números pode ser interessante em diversar análises e o R possui algumas funções como floor
e ceiling
, além da round
. Logo, quando estiver trabalhando com arrendondamentos pode ser interessente pesquisar sobre elas.
A última variável para ser tratada é a que contém as informações de cidade/estado dos respondentes. Observe que as observações tem estruturas parecidas com as da variável veiculos
, logo um tratamento similar poderia ser útil, mas vamos fazer um pouco diferente.
[1] | “VITORIA - ES” | “SAO PAULO - SP” | “RIO DE JANEIRO - RJ” |
[4] | “BELO HORIZONTE - MG” | “VITORIA - ES” | “FLORIANOPOLIS - SC” |
[7] | “TRES CORACOES - MG” | “SALVADOR - BA” | “SAO PAULO - SP”” |
[10] | “BELO HORIZONTE - MG” |
Aqui queremos padronizar as observações para que fiquem no padrão “CIDADE - UF” então começamos removendo espaços excedentes, depois deixamos todas as letras maiúsculas e então usamos o str_remove_all("[:punct:]")
, mesmo assim ainda restou restou alguns acentos como em VITÓRIA
. Para corrigir isso utilizamos a função rm_accent
do pacote abjutils
. O último passo seria substituir os espaços que separam a cidade do UF por um “-”. Uma maneira de fazer isso seria utilizando o str_replace
, entretanto algumas cidade possuem mais de um nome, ou seja, ao fazer essa substituição todos os espaços seriam afetados e teriamos resultados como “SAO - PAULO - SP”. Uma maneira de contornar isso (nesse caso) é usar a expressão regular "\\s(?=([:alpha:]){2}$)"
. Essa expressão basicamente está pegando, no final da string, todo espaço seguido por exatamente 2 letras quaisquer "([:alpha:])"
. O ?=
faz com que seja alterada apenas a entrada, nesse caso ele vai alterar apenas o espaço. Consideramos que todos os UF’s tem exatamente duas letras. Em alguns casos pode ser interessante separar em colunas diferentes o município e a cidade. Fizemos algo parecido quando tratamos a primeira variável.
Vamos dar uma olhada em como ficou o nosso banco de dados final
marca_modelo | marca | modelo | alugaria_de_novo | cidade | grau_de_satisfacao |
---|---|---|---|---|---|
HONDA CIVIC | HONDA | CIVIC | SIM | VITORIA - ES | 8.1 |
HONDA CIVIC | HONDA | CIVIC | SIM | SAO PAULO - SP | 10.0 |
FIAT ARGO | FIAT | ARGO | NAO | RIO DE JANEIRO - RJ | 3.2 |
FORD FUSION | FORD | FUSION | SIM | BELO HORIZONTE - MG | 6.0 |
TOYOTA COROLLA | TOYOTA | COROLLA | NAO | VITORIA - ES | 5.3 |
TOYOTA COROLLA | TOYOTA | COROLLA | NAO | FLORIANOPOLIS - SC | 0.0 |
FIAT ARGO | FIAT | ARGO | NAO | TRES CORACOES - MG | 7.7 |
HONDA CIVIC | HONDA | CIVIC | SIM | SALVADOR - BA | 9.0 |
FORD FUSION | FORD | FUSION | NAO | SAO PAULO - SP | 5.0 |
TOYOTA COROLLA | TOYOTA | COROLLA | NAO | BELO HORIZONTE - MG | 6.7 |
Para finalizar é interessente ver que nosso tratamento de dados funcionou. Queremos saber se nossas observações estão corretamentes estruturadas, se por exemplo todas as respostas que representam a mesma coisa estão de fato iguais. Uma maneira legal de fazer isso é criar tabelas de frequência para as variáveis qualitativas e apresentar medidas resumo para as variáveis quantitativas.
n | % | |
---|---|---|
FIAT ARGO | 2 | 20 |
FORD FUSION | 2 | 20 |
HONDA CIVIC | 3 | 30 |
TOYOTA COROLLA | 3 | 30 |
Total | 10 | 100 |
Tabela de frequências para marca/modelo
n | % | |
---|---|---|
FLORIANOPOLIS - SC | 1 | 10 |
RIO DE JANEIRO - RJ | 1 | 10 |
SALVADOR - BA | 1 | 10 |
TRES CORACOES - MG | 1 | 10 |
BELO HORIZONTE - MG | 2 | 20 |
SAO PAULO - SP | 2 | 20 |
VITORIA - ES | 2 | 20 |
Total | 10 | 100 |
Tabela de frequências para cidade/estado
n | % | |
---|---|---|
SIM | 4 | 40 |
NAO | 6 | 60 |
Total | 10 | 100 |
Tabela de frequências para ‘alugaria de novo’
Min. | 1st Qu. | Median | Mean | 3rd Qu. | Max. |
0.000 | 5.075 | 6.350 | 6.100 | 8.000 | 10.000 |
Com o banco de dados prontos já é possível realizar inúmeras análises e conseguir apresentar os resultados/informações. Muitas dessas informações são passadas atráves de gráficos. O pacote ggplot2
é um grande aliado nesse ramo, mas isso é assunto para um outro post (spoiler).
Esperamos que com esse post você possa entender um pouco mais de como tratar string’s utilizando (mas não só) o pacote stringr
e expressões regulares. Vale ressaltar que realizar um tratamento de caracteres, inúmeros obstaculos podem surgir no caminho. É importante ter em mente que cada caso é um caso e muitas das vezes você precisará encontrar uma expressão regular que resolva aquele seu problema específico.
https://livro.curso-r.com/7-4-o-pacote-stringr.html