Manipulação de texto com stringr

Este é o post inicial de uma série de posts que faremos sobre manipulações de texto com o pacote stringr. Iremos criar alguns problemas e mostrar quais funções do pacote devemos utilizar para solucionar esses problemas. Aprenderemos a limpar os dados e a padronizá-los. Se você tem dificuldade com manipulação de texto, esse post é para você.

Miguel Cleaver
10-13-2019

Este post inicia uma série de posts que iremos fazer sobre manipulação de texto. Para esse fim, utilizaremos o pacote stringr. Se você tem dificuldade com a manipulação de dados textuais no R, esse post é para você. Ao longo da série, vamos criar diferentes problemas e mostrar como podemos resolvê-los. Em posts futuros, ainda teremos a oportunidade de aprender alguns detalhes básicos de expressões regulares (regex), que são fundamentais para a manipulação de texto.

Regex não é difícil, mas exige um pouco de paciência. Para quem já tiver interesse de ir se informando sobre o assunto, vale a pena dar uma olhada na página do Wikipédia sobre regex e neste link mais prático sobre regex.

Criação de dados textuais

Esta etapa visa criar uma pequena base de dados para que possamos fazer nossos exercícios de manipulações de texto.

Primeiramente, carregamos os pacotes dplyr, stringr, tidyr e data.table.


library(dplyr)
library(stringr)
library(tidyr)
library(data.table)

Para que você possa replicar este exercício vamos usar a função set.seed (obs. se sua versão do R for anterior à versão 3.6.0, você não vai conseguir replicar o exercício, mas pode ficar tranquilo que ainda assim vai conseguir acompanhar a lógica das funções que iremos explorar).


# colocamos um seed para reprodutibilidade
set.seed(12345)

# criamos um vetor númerico de tamanho 200 com valores entre 1 e 99999; 
numeros <- sample(1:99999, 200) %>% 
  as.character()

## baixamos uma correlacao de nomes de paises em ingles,
## portugues e com os respectivos codigos ISOA3

paises <- fread(
  "https://raw.githubusercontent.com/mgcleaver/correlacoes/master/paises_isoa.txt",
                encoding = "UTF-8"
) %>% 
  slice(sample(1:249, 10, replace = FALSE)) %>% # selecionamos 10 observações aleatórias
  select(ISOA, pais_port)

paises <- paises %>% 
  unite(col = pais, pais_port, ISOA, sep = "-") %>%  # unimos os elementos de duas colunas
  pull(pais)

paises <- sample(paises, 200, replace = TRUE)

df <- tibble(codigos = numeros,
             pais = paises)

Problema n. 1: Como adicionar caracteres para que as observações de uma coluna tenham sempre o mesmo tamanho?

Suponhamos que você precisa padronizar a primeira coluna de forma que ela tenha sempre seis dígitos. Assim, para uma observação com o número “10”, a qual tem dois dígitos, precisaríamos adicionar mais quatro dígitos para que a observação tenha no total seis dígitos.

Suponha que desejemos manter a informação que os nossos dados nos fornecem, isto é, não queremos perder a informação do valor da coluna. Se optarmos em preencher os demais dígitos da observação de valor “10” com zeros, deveremos preenchê-los da esquerda para a direita para obter “000010”. Já se preenchermos nossa observação “10” com zeros da direita para esquerda obteríamos “100000”. Isso geraria uma ambiguidade, pois não saberíamos se o dado tinha inicialmente o valor 10 ou o valor 10.000. Por isso, nesse caso, o ideal seria preencher nossa observação com zeros da esquerda para a direita.

Mas, afinal, como preencher caracteres adicionais nos nossos dados para que todas as observações tenham seis dígitos?

A função que resolve nosso problema se chama str_pad.

No primeiro argumento da função devemos inserir o objeto que queremos padronizar. No nosso caso, suponhamos que queremos padronizar a primeira coluna do objeto df (coluna “codigos”). Todos os elementos da coluna possuem até cinco dígitos, mas queremos que os dados dessa coluna tenham sempre seis dígitos.

Portanto, no argumento width devemos inserir o tamanho que queremos que cada elemento do nosso vetor tenha, isto é, width = 6. No argumento side escolhemos “left” (esquerda), uma vez que, para este exemplo, queremos completar o nosso dado com um caractere da nossa escolha pelo lado esquerdo do dado. No argumento pad deve-se escolher uma letra, número ou símbolo para preencher os elementos do nosso dado. No presente caso, como queremos preencher os dígitos remanescentes com o dígito zero, devemos especificar pad = "0".

Primeiramente vejamos a coluna codigos do objeto df:


# ver coluna "codigos" em form de vetor
df %>% 
  pull(codigos)

  [1] "76440" "98909" "64864" "75522" "65611" "95847" "91914" "62368"
  [9] "86951" "25548" "97292" "393"   "89236" "74320" "93664" "21052"
 [17] "2848"  "40996" "79497" "32719" "5265"  "15505" "16954" "14349"
 [25] "9795"  "62026" "98667" "22008" "40564" "9837"  "99251" "1258" 
 [33] "19991" "32922" "3975"  "52965" "99212" "91608" "40453" "40516"
 [41] "59147" "56643" "79631" "25130" "90423" "21024" "33746" "52444"
 [49] "77769" "44035" "12127" "52938" "62854" "98071" "24388" "36638"
 [57] "88607" "51432" "28313" "97800" "76946" "91477" "12335" "20216"
 [65] "49203" "60332" "26868" "82537" "61496" "75813" "58069" "20388"
 [73] "65991" "95570" "99177" "50171" "4434"  "42685" "45581" "17103"
 [81] "28239" "35056" "76705" "7031"  "94155" "39199" "99390" "24945"
 [89] "35031" "62858" "82555" "12036" "96890" "60014" "5443"  "52013"
 [97] "45288" "20831" "65225" "91295" "67464" "51872" "1256"  "92482"
[105] "33989" "22075" "84116" "79632" "1018"  "39234" "42824" "81067"
[113] "87829" "93828" "80104" "38386" "56943" "44097" "26446" "63068"
[121] "66577" "15025" "44227" "3894"  "47822" "57735" "45505" "41064"
[129] "49381" "40789" "47674" "37702" "51579" "11032" "15896" "47010"
[137] "82183" "94005" "95497" "53152" "44438" "77892" "2104"  "55994"
[145] "76546" "13066" "24944" "73794" "31090" "15123" "10416" "15562"
[153] "70680" "1678"  "25437" "73564" "17523" "16470" "55287" "20075"
[161] "40825" "3657"  "20372" "37179" "57401" "72710" "53024" "279"  
[169] "63370" "91754" "45630" "82312" "6023"  "60583" "26050" "24667"
[177] "19156" "92865" "41977" "86028" "7642"  "82861" "34985" "42215"
[185] "56772" "58952" "53448" "21692" "57100" "50217" "61219" "56293"
[193] "41648" "49004" "89897" "44093" "44834" "81689" "51472" "12859"

Agora façamos a alteração proposta na coluna “codigos”:


df_temp <- df %>% 
  mutate(codigos = str_pad(codigos, # reescrevemos a coluna codigos
                      width = 6, 
                      side = "left", 
                      pad = "0"))

# ver como ficou a coluna "codigos" em forma de vetor
df_temp %>% 
  pull(codigos)

  [1] "076440" "098909" "064864" "075522" "065611" "095847" "091914"
  [8] "062368" "086951" "025548" "097292" "000393" "089236" "074320"
 [15] "093664" "021052" "002848" "040996" "079497" "032719" "005265"
 [22] "015505" "016954" "014349" "009795" "062026" "098667" "022008"
 [29] "040564" "009837" "099251" "001258" "019991" "032922" "003975"
 [36] "052965" "099212" "091608" "040453" "040516" "059147" "056643"
 [43] "079631" "025130" "090423" "021024" "033746" "052444" "077769"
 [50] "044035" "012127" "052938" "062854" "098071" "024388" "036638"
 [57] "088607" "051432" "028313" "097800" "076946" "091477" "012335"
 [64] "020216" "049203" "060332" "026868" "082537" "061496" "075813"
 [71] "058069" "020388" "065991" "095570" "099177" "050171" "004434"
 [78] "042685" "045581" "017103" "028239" "035056" "076705" "007031"
 [85] "094155" "039199" "099390" "024945" "035031" "062858" "082555"
 [92] "012036" "096890" "060014" "005443" "052013" "045288" "020831"
 [99] "065225" "091295" "067464" "051872" "001256" "092482" "033989"
[106] "022075" "084116" "079632" "001018" "039234" "042824" "081067"
[113] "087829" "093828" "080104" "038386" "056943" "044097" "026446"
[120] "063068" "066577" "015025" "044227" "003894" "047822" "057735"
[127] "045505" "041064" "049381" "040789" "047674" "037702" "051579"
[134] "011032" "015896" "047010" "082183" "094005" "095497" "053152"
[141] "044438" "077892" "002104" "055994" "076546" "013066" "024944"
[148] "073794" "031090" "015123" "010416" "015562" "070680" "001678"
[155] "025437" "073564" "017523" "016470" "055287" "020075" "040825"
[162] "003657" "020372" "037179" "057401" "072710" "053024" "000279"
[169] "063370" "091754" "045630" "082312" "006023" "060583" "026050"
[176] "024667" "019156" "092865" "041977" "086028" "007642" "082861"
[183] "034985" "042215" "056772" "058952" "053448" "021692" "057100"
[190] "050217" "061219" "056293" "041648" "049004" "089897" "044093"
[197] "044834" "081689" "051472" "012859"

Como se pode ver, todos os dados da coluna “codigos” tem seis caracteres, ou, mais especificamente, seis dígitos. Se você gosta de conferir o que você fez, pode pedir para o R contar o número de caracteres da seguinte forma:


df_temp %>% 
  count(caracteres = nchar(codigos))

# A tibble: 1 x 2
  caracteres     n
       <int> <int>
1          6   200

Acima contamos os caracteres da coluna “codigos”. Há 200 observações, todas com seis caracteres.

Para fins didáticos, fazemos um procedimento semelhante, mas, agora, em vez de preencher nossas observações com zeros pela esquerda, preenchemos com asteriscos (“*”) pela direita.


df_temp <- df %>% 
  mutate(codigos = str_pad(codigos,
                           width = 6,
                           side = "right",
                           pad = "*"))

df_temp %>% 
  pull(codigos)

  [1] "76440*" "98909*" "64864*" "75522*" "65611*" "95847*" "91914*"
  [8] "62368*" "86951*" "25548*" "97292*" "393***" "89236*" "74320*"
 [15] "93664*" "21052*" "2848**" "40996*" "79497*" "32719*" "5265**"
 [22] "15505*" "16954*" "14349*" "9795**" "62026*" "98667*" "22008*"
 [29] "40564*" "9837**" "99251*" "1258**" "19991*" "32922*" "3975**"
 [36] "52965*" "99212*" "91608*" "40453*" "40516*" "59147*" "56643*"
 [43] "79631*" "25130*" "90423*" "21024*" "33746*" "52444*" "77769*"
 [50] "44035*" "12127*" "52938*" "62854*" "98071*" "24388*" "36638*"
 [57] "88607*" "51432*" "28313*" "97800*" "76946*" "91477*" "12335*"
 [64] "20216*" "49203*" "60332*" "26868*" "82537*" "61496*" "75813*"
 [71] "58069*" "20388*" "65991*" "95570*" "99177*" "50171*" "4434**"
 [78] "42685*" "45581*" "17103*" "28239*" "35056*" "76705*" "7031**"
 [85] "94155*" "39199*" "99390*" "24945*" "35031*" "62858*" "82555*"
 [92] "12036*" "96890*" "60014*" "5443**" "52013*" "45288*" "20831*"
 [99] "65225*" "91295*" "67464*" "51872*" "1256**" "92482*" "33989*"
[106] "22075*" "84116*" "79632*" "1018**" "39234*" "42824*" "81067*"
[113] "87829*" "93828*" "80104*" "38386*" "56943*" "44097*" "26446*"
[120] "63068*" "66577*" "15025*" "44227*" "3894**" "47822*" "57735*"
[127] "45505*" "41064*" "49381*" "40789*" "47674*" "37702*" "51579*"
[134] "11032*" "15896*" "47010*" "82183*" "94005*" "95497*" "53152*"
[141] "44438*" "77892*" "2104**" "55994*" "76546*" "13066*" "24944*"
[148] "73794*" "31090*" "15123*" "10416*" "15562*" "70680*" "1678**"
[155] "25437*" "73564*" "17523*" "16470*" "55287*" "20075*" "40825*"
[162] "3657**" "20372*" "37179*" "57401*" "72710*" "53024*" "279***"
[169] "63370*" "91754*" "45630*" "82312*" "6023**" "60583*" "26050*"
[176] "24667*" "19156*" "92865*" "41977*" "86028*" "7642**" "82861*"
[183] "34985*" "42215*" "56772*" "58952*" "53448*" "21692*" "57100*"
[190] "50217*" "61219*" "56293*" "41648*" "49004*" "89897*" "44093*"
[197] "44834*" "81689*" "51472*" "12859*"

Problema n. 2: Como extrair um subconjunto de caracteres consecutivos de um texto?

Para esse exercício, podemos começar utilizando o objeto paises que criamos no início do post. Vejamos os primeiros dez elementos do objeto que criamos:


paises[1:10]

 [1] "Israel-ISR"       "Tonga-TON"        "Ilha Norfolk-NFK"
 [4] "Ilha Norfolk-NFK" "Dinamarca-DNK"    "Camboja-KHM"     
 [7] "Ilha Bouvet-BVT"  "Gana-GHA"         "Letônia-LVA"     
[10] "Ilha Bouvet-BVT" 

Como podemos extrair os últimos três caracteres do objeto paises? A função que vai nos ajudar com essa tarefa se chama str_sub. No primeiro argumento da função devemos inserir o objeto que desejamos manipular. No nosso caso, inserimos o objeto paises. No segundo e no terceiro argumento devemos inserir a posição incial e a final dos caracteres que desejamos extrair. Por exemplo, para extrair os três primeiros caracteres e os três ultimos caracteres de paises, podemos fazer, respectivamente, o seguinte:


# tres primeiros caracteres
str_sub(paises, 1, 3)

  [1] "Isr" "Ton" "Ilh" "Ilh" "Din" "Cam" "Ilh" "Gan" "Let" "Ilh"
 [11] "Cam" "Ilh" "Ilh" "Gan" "Isr" "Let" "Let" "Ton" "Ira" "Let"
 [21] "Ira" "San" "Din" "Gan" "Ton" "Din" "Ton" "San" "Ira" "Isr"
 [31] "Cam" "Ilh" "Ton" "Let" "Din" "Gan" "Gan" "San" "Din" "Cam"
 [41] "Ilh" "Ilh" "Ilh" "Let" "Isr" "Ton" "Din" "Ilh" "Cam" "Isr"
 [51] "San" "Gan" "Ilh" "San" "Din" "Gan" "Ira" "Cam" "Isr" "Let"
 [61] "Ilh" "San" "Gan" "Ira" "Ilh" "Ilh" "San" "Ton" "Gan" "Cam"
 [71] "Cam" "Cam" "Ilh" "Din" "Let" "Gan" "San" "Ira" "Din" "Let"
 [81] "Ton" "San" "Ton" "Ilh" "Ilh" "Din" "Gan" "San" "Isr" "Din"
 [91] "Ilh" "Gan" "Cam" "Cam" "Ira" "Ilh" "Ilh" "Ilh" "Cam" "Ilh"
[101] "San" "Ilh" "Cam" "Ton" "Ira" "Ira" "Ira" "Let" "Ira" "Gan"
[111] "Din" "Ton" "Isr" "Din" "Din" "Din" "Din" "Isr" "Isr" "Ilh"
[121] "Ton" "Isr" "Ira" "Gan" "Let" "San" "Ilh" "Ira" "Isr" "Isr"
[131] "Gan" "San" "Ton" "Let" "Cam" "Ton" "Ilh" "Ira" "Let" "San"
[141] "Gan" "Cam" "Gan" "Gan" "San" "Ilh" "Ilh" "Cam" "Let" "San"
[151] "Isr" "Ilh" "Ira" "Isr" "San" "Let" "Ilh" "Let" "Isr" "Cam"
[161] "Ira" "Isr" "Ilh" "Ilh" "San" "Isr" "Isr" "Ilh" "Gan" "Ilh"
[171] "San" "Cam" "Din" "Cam" "Cam" "Din" "Cam" "Cam" "Ton" "Ira"
[181] "Ilh" "Cam" "Ton" "Ilh" "San" "Gan" "San" "Din" "Gan" "Cam"
[191] "Ilh" "Cam" "Isr" "Gan" "Isr" "Din" "Ton" "Cam" "Din" "Let"

# tres ultimos caracteres
str_sub(paises, -3, -1) # os numeros negativos indicam que contamos de trás para frente

  [1] "ISR" "TON" "NFK" "NFK" "DNK" "KHM" "BVT" "GHA" "LVA" "BVT"
 [11] "KHM" "NFK" "NFK" "GHA" "ISR" "LVA" "LVA" "TON" "IRQ" "LVA"
 [21] "IRQ" "SHN" "DNK" "GHA" "TON" "DNK" "TON" "SHN" "IRQ" "ISR"
 [31] "KHM" "BVT" "TON" "LVA" "DNK" "GHA" "GHA" "SHN" "DNK" "KHM"
 [41] "BVT" "BVT" "BVT" "LVA" "ISR" "TON" "DNK" "NFK" "KHM" "ISR"
 [51] "SHN" "GHA" "NFK" "SHN" "DNK" "GHA" "IRQ" "KHM" "ISR" "LVA"
 [61] "NFK" "SHN" "GHA" "IRQ" "BVT" "BVT" "SHN" "TON" "GHA" "KHM"
 [71] "KHM" "KHM" "BVT" "DNK" "LVA" "GHA" "SHN" "IRQ" "DNK" "LVA"
 [81] "TON" "SHN" "TON" "BVT" "NFK" "DNK" "GHA" "SHN" "ISR" "DNK"
 [91] "NFK" "GHA" "KHM" "KHM" "IRQ" "BVT" "NFK" "BVT" "KHM" "NFK"
[101] "SHN" "NFK" "KHM" "TON" "IRQ" "IRQ" "IRQ" "LVA" "IRQ" "GHA"
[111] "DNK" "TON" "ISR" "DNK" "DNK" "DNK" "DNK" "ISR" "ISR" "NFK"
[121] "TON" "ISR" "IRQ" "GHA" "LVA" "SHN" "NFK" "IRQ" "ISR" "ISR"
[131] "GHA" "SHN" "TON" "LVA" "KHM" "TON" "NFK" "IRQ" "LVA" "SHN"
[141] "GHA" "KHM" "GHA" "GHA" "SHN" "NFK" "NFK" "KHM" "LVA" "SHN"
[151] "ISR" "NFK" "IRQ" "ISR" "SHN" "LVA" "NFK" "LVA" "ISR" "KHM"
[161] "IRQ" "ISR" "BVT" "BVT" "SHN" "ISR" "ISR" "BVT" "GHA" "BVT"
[171] "SHN" "KHM" "DNK" "KHM" "KHM" "DNK" "KHM" "KHM" "TON" "IRQ"
[181] "NFK" "KHM" "TON" "BVT" "SHN" "GHA" "SHN" "DNK" "GHA" "KHM"
[191] "NFK" "KHM" "ISR" "GHA" "ISR" "DNK" "TON" "KHM" "DNK" "LVA"

Agora, criemos uma nova coluna chamada “isoa3” que consiste nas últimas três letras da coluna pais do objeto df_temp:


df_temp <- df_temp %>% 
  mutate(isoa3 = str_sub(pais, -3, -1))

df_temp %>% 
  head()

# A tibble: 6 x 3
  codigos pais             isoa3
  <chr>   <chr>            <chr>
1 76440*  Israel-ISR       ISR  
2 98909*  Tonga-TON        TON  
3 64864*  Ilha Norfolk-NFK NFK  
4 75522*  Ilha Norfolk-NFK NFK  
5 65611*  Dinamarca-DNK    DNK  
6 95847*  Camboja-KHM      KHM  

Para consolidarmos o conhecimento, agora suponha que ainda temos o objetivo de criar uma nova coluna com apenas o terceiro caractere da coluna “codigos”. Chamemos essa coluna de “digito3”.


df_temp <- df_temp %>% 
  mutate(digito3 = str_sub(codigos, 3, 3))

head(df_temp)

# A tibble: 6 x 4
  codigos pais             isoa3 digito3
  <chr>   <chr>            <chr> <chr>  
1 76440*  Israel-ISR       ISR   4      
2 98909*  Tonga-TON        TON   9      
3 64864*  Ilha Norfolk-NFK NFK   8      
4 75522*  Ilha Norfolk-NFK NFK   5      
5 65611*  Dinamarca-DNK    DNK   6      
6 95847*  Camboja-KHM      KHM   8      

Problema n. 3: como filtrar dados com certos padrões no texto?

Para filtrar padrões de interesse em dados textuais podemos utilizar a função str_detect. Esta função retorna um valor lógico. Retorna TRUE para o caso do padrão ter sido detectado e retorna FALSE, caso contrário. No primeiro argumento, inserimos o vetor para o qual queremos detectar algum padrão. No segundo argumento, inserimos o padrão que desejamos detectar.

Ao usar essa função em conjunto com a função filter conseguimos filtrar padrões de interesse, uma vez que a função filter usa vetores lógicos para fazer filtros. Para o problema n. 3, vamos trabalhar com a coluna “pais”.

Suponha que desejamos encontrar todas as observações que possuem o padrão “ISR” na coluna “pais”. Assim, podemo fazer o seguinte:


df %>% 
  filter(str_detect(pais, "ISR"))

# A tibble: 21 x 2
   codigos pais      
   <chr>   <chr>     
 1 76440   Israel-ISR
 2 93664   Israel-ISR
 3 9837    Israel-ISR
 4 90423   Israel-ISR
 5 44035   Israel-ISR
 6 28313   Israel-ISR
 7 35031   Israel-ISR
 8 87829   Israel-ISR
 9 44097   Israel-ISR
10 26446   Israel-ISR
# ... with 11 more rows

Na coluna “pais”, somente 21 observações foram encontradas contendo o padrão “ISR”.

Bom, vamos encerrando por aqui. No próxima parte desta sequência de posts veremos um pouco mais de como identificar observações textuais, como eliminar caracteres indesejados e, também, como substitui-los por outros caracteres.

Até breve!

Citation

For attribution, please cite this work as

Cleaver (2019, Oct. 13). Fulljoin: Manipulação de texto com stringr. Retrieved from https://www.fulljoin.com.br/posts/2019-10-13-manipulao-de-texto-com-stringr/

BibTeX citation

@misc{cleaver2019manipulação,
  author = {Cleaver, Miguel},
  title = {Fulljoin: Manipulação de texto com stringr},
  url = {https://www.fulljoin.com.br/posts/2019-10-13-manipulao-de-texto-com-stringr/},
  year = {2019}
}