14  Strings

14.1 Introdução

Até agora, você utilizou várias cadeias de caracteres (strings) sem compreender os detalhes por trás delas. Contudo, chegou o momento de explorar as strings, compreender o seu funcionamento e dominar algumas das poderosas ferramentas de manipulação de strings que estão à sua disposição.

Vamos iniciar abordando os aspectos envolvidos na criação de strings e vetores de caracteres. Você aprenderá a gerar strings a partir de dados e, em seguida, o inverso: extrair strings dos dados. Em seguida, discutiremos sobre ferramentas que funcionam com letras individuais. Por fim, o capítulo explora funções que lidam com letras individuais e apresenta uma breve discussão sobre como as premissas do inglês podem induzir ao erro ao trabalhar com outros idiomas.

No próximo capítulo, continuaremos trabalhando com strings, mas você aprenderá mais sobre o poder das expressões regulares.

14.1.1 Pré-requisitos

Neste capítulo, vamos utilizar funções do pacote stringr, que faz parte do ecossistema do tidyverse. Também utilizaremos a base de dados bebes do pacote dados, pois ela oferece algumas strings divertidas para a manipulação.

Você facilmente reconhecerá quando estiver utilizando uma função do stringr, pois todas as funções do pacote iniciam com str_. Essa característica é particularmente útil se você estiver utilizando o RStudio, pois a funcionalidade de autocompletar permitirá que você se lembre das funções disponíveis ao digitar str_.

Função str_c  digitada no console do RStudio com a ferramenta de preenchimento automático sendo exibida ao topo, que lista funções que começam com str_c. O nome e o início da página de manual da função, destacados na lista do preenchimento automático, são exibidos em um painel à sua direita.

14.2 Criando uma string

Em outras seções do livro, nós criamos strings de forma rápida, porém não abordamos os detalhes. Em primeiro lugar, você pode criar uma string utilizando tanto aspas simples (') quanto aspas duplas ("). Não há qualquer distinção entre as duas formas. Entretanto, para uma maior consistência, o guia de estilo do tidyverse sugere o uso de ", a menos que a string contenha múltiplas ".

string1 <- "Esta é uma string"
string2 <- 'Se eu quiser incluir "aspas" em uma string, uso aspas simpes'

Caso esqueça de fechar aspas, você verá +, o qual indica à continuação do prompt:

> "Esta é uma string sem fechar aspas
+ 
+ 
+ SOCORRO, ESTOU PRESO EM UMA STRING

Se isso acontecer com você e não conseguir descobrir qual aspas fechar, pressione a tecla Escape (Esc) para cancelar e tente novamente.

14.2.1 Caracteres de escape

Para incluir literalmente uma aspas simples ou duplas em uma string, você pode utilizar \ para “escapar” do significado reservado e inserir o caractere de modo literal:

aspas_duplas <- "\"" # ou '"'
aspas_simples <- '\'' # ou "'"

Assim, se você desejar incluir uma barra invertida literal em sua string, será necessário escapar do significado reservado: "\\":

barra_invertida <- "\\"

Observe que a representação impressa de uma string não é o mesmo que a própria string, pois a representação impressa mostra os escapes (em outras palavras, quando você imprime uma string, pode copiar e colar a saída para recriar essa string). Para visualizar o conteúdo bruto da string, use str_view()1:

x <- c(aspas_simples, aspas_duplas, barra_invertida)
x
#> [1] "'"  "\"" "\\"

str_view(x)
#> [1] │ '
#> [2] │ "
#> [3] │ \

14.2.2 Strings brutas

Criar uma string com várias aspas ou barras invertidas pode se tornar uma tarefa complexa rapidamente. Para ilustrar o problema, vamos criar uma string que contenha o conteúdo do bloco de código que definimos as variáveis aspas_duplas e aspas_simples.

string_complexa <- "aspas_duplas <- \"\\\"\" # ou '\"'
aspas_simples <- '\\'' # ou \"'\""
str_view(string_complexa)
#> [1] │ aspas_duplas <- "\"" # ou '"'
#>     │ aspas_simples <- '\'' # ou "'"

Realmente, são muitas barras invertidas! (Isso é comumente chamado de leaning toothpick syndrome.) Para eliminar a necessidade de escapar de caracteres com significado reservado, você pode utilizar uma string bruta2:

string_complexa <- r"(aspas_duplas <- "\"" # ou '"'
aspas_simples <- '\'' # ou "'")"
str_view(string_complexa)
#> [1] │ aspas_duplas <- "\"" # ou '"'
#>     │ aspas_simples <- '\'' # ou "'"

Uma string bruta geralmente começa com r"( e termina com )". Mas, se a sua string contém )", você pode usar r"[]" ou r"{}". Se isso ainda não for suficiente, você pode inserir hífens adicionais para tornar os pares de abertura e fechamento únicos, por exemplo, r"--()--", r"---()---", etc. As strings brutas são flexíveis o suficiente para lidar com qualquer texto.

14.2.3 Outros caracteres especiais

Além de \", \' e \\, existem alguns outros caracteres especiais que podem ser úteis. Os mais comuns são \n, que representa uma nova linha, e \t, que representa uma tabulação. Às vezes, você também verá strings contendo escapes Unicode que começam com \u ou \U. Essa é uma forma de escrever caracteres não ingleses, mas que funcionam em todos os sistemas. Você pode ver a lista completa de outros caracteres especiais em ?Quotes.

x <- c("um\ndois", "um\tdois", "\u00b5", "\U0001f604")
x
#> [1] "um\ndois" "um\tdois" "µ"        "😄"
str_view(x)
#> [1] │ um
#>     │ dois
#> [2] │ um{\t}dois
#> [3] │ µ
#> [4] │ 😄

Observe que str_view() utiliza chaves para tabulações, facilitando a identificação delas3. Um dos desafios ao trabalhar com texto é a variedade de formas pelas quais espaços em branco podem surgir. Portanto, esse comportamento da função pode ajudar você a reconhecer quando algo incomum está ocorrendo.

14.2.4 Exercícios

  1. Crie strings que contenham os seguintes valores:

    1. Ele disse: "Isso é incrível!"

    2. \a\b\c\d

    3. \\\\\\

  2. Crie a string na sua sessão do R e imprima-a. O que acontece com o caractere especial “\u00a0”? Como a função str_view() o exibe? Você poderia fazer uma busca rápida no Google para descobrir o que é esse caractere especial?

    x <- "Isso\u00a0é\u00a0complexo"

14.3 Criando várias strings a partir de dados

Agora que você já sabe o básico sobre como criar uma ou duas strings “manualmente”, vamos conhecer os detalhes de como criar strings a partir de outras strings. Isso ajudará você a resolver o problema comum de ter algum texto escrito que deseja combinar com strings de um data frame. Por exemplo, você pode combinar “Olá” com uma variável nome para criar uma saudação. Mostraremos como fazer isso com as funções str_c() e str_glue() e como você pode usá-las com a função mutate(). Isso naturalmente levanta a questão de quais funções do pacote stringr você poderia utilizar com summarize(), então finalizaremos esta seção com uma discussão sobre str_flatten(), uma função de sumarização para strings.

14.3.1 str_c()

A função str_c() recebe qualquer número de vetores como argumentos e retorna um vetor de caracteres:

str_c("x", "y")
#> [1] "xy"
str_c("x", "y", "z")
#> [1] "xyz"
str_c("Olá ", c("John", "Susan"))
#> [1] "Olá John"  "Olá Susan"

str_c() é muito similar à função base paste0(), mas foi implementada para ser utilizada com mutate(), seguindo as regras usuais do tidyverse para reciclagem e propagação de valores faltantes (missing values):

df <- tibble(nome = c("Flora", "David", "Terra", NA))
df |> mutate(saudacao = str_c("Oi ", nome, "!"))
#> # A tibble: 4 × 2
#>   nome  saudacao 
#>   <chr> <chr>    
#> 1 Flora Oi Flora!
#> 2 David Oi David!
#> 3 Terra Oi Terra!
#> 4 <NA>  <NA>

Se você deseja que os valores faltantes (missing values) sejam exibidos de outra forma, use coalesce() para substituí-los. Neste caso, você pode usar a função dentro ou fora da função str_c():

df |> 
  mutate(
    saudacao1 = str_c("Oi ", coalesce(nome, "para você"), "!"),
    saudacao2 = coalesce(str_c("Oi ", nome, "!"), "Oi!")
  )
#> # A tibble: 4 × 3
#>   nome  saudacao1     saudacao2
#>   <chr> <chr>         <chr>    
#> 1 Flora Oi Flora!     Oi Flora!
#> 2 David Oi David!     Oi David!
#> 3 Terra Oi Terra!     Oi Terra!
#> 4 <NA>  Oi para você! Oi!

14.3.2 str_glue()

Se você estiver combinando muitas strings fixas e variáveis com str_c(), vai perceber que digitar muitas "s torna difícil visualizar o objetivo geral do código. Uma abordagem alternativa é fornecida pela função str_glue()4 do pacote glue. Você fornece a função uma única string que possui uma característica especial: tudo dentro de {} será avaliado como se estivesse fora das aspas:

df |> mutate(saudacao = str_glue("Oi {nome}!"))
#> # A tibble: 4 × 2
#>   nome  saudacao 
#>   <chr> <glue>   
#> 1 Flora Oi Flora!
#> 2 David Oi David!
#> 3 Terra Oi Terra!
#> 4 <NA>  Oi NA!

Como você pode ver, a função str_glue() atualmente converte valores faltantes (missing values) na string "NA", o que, infelizmente, torna-a inconsistente com a função str_c().

Você também pode estar se perguntando o que acontece se precisar incluir { ou } em sua string. Se você supôs que precisará escapar, de alguma forma, do significado reservado, você está no caminho certo. O truque é que o pacote glue utiliza uma técnica de escape ligeiramente diferente: em vez de prefixar com um caractere especial como \, você deve duplicar os caracteres especiais:

df |> mutate(saudacao = str_glue("{{Oi {nome}!}}"))
#> # A tibble: 4 × 2
#>   nome  saudacao   
#>   <chr> <glue>     
#> 1 Flora {Oi Flora!}
#> 2 David {Oi David!}
#> 3 Terra {Oi Terra!}
#> 4 <NA>  {Oi NA!}

14.3.3 str_flatten()

As funções str_c() e str_glue() funcionam bem com mutate() porque suas saídas têm o mesmo comprimento que suas entradas. Mas, se você quisesse uma função que funcionasse bem com summarize(), isto é, que sempre retornasse uma única string? Neste caso, str_flatten()5 é a função adequada para essa tarefa: ela recebe um vetor de caracteres e concatena cada elemento do vetor em uma única string:

str_flatten(c("x", "y", "z"))
#> [1] "xyz"
str_flatten(c("x", "y", "z"), ", ")
#> [1] "x, y, z"
str_flatten(c("x", "y", "z"), ", ", last = " e ")
#> [1] "x, y e z"

Essa função funciona bem com summarize():

df <- tribble(
  ~ nome, ~ fruta,
  "Carmen", "banana",
  "Carmen", "maça",
  "Marvin", "nectarina",
  "Terence", "melão",
  "Terence", "mamão",
  "Terence", "tangerina"
)
df |>
  group_by(nome) |> 
  summarize(frutas = str_flatten(fruta, ", "))
#> # A tibble: 3 × 2
#>   nome    frutas                 
#>   <chr>   <chr>                  
#> 1 Carmen  banana, maça           
#> 2 Marvin  nectarina              
#> 3 Terence melão, mamão, tangerina

14.3.4 Exercícios

  1. Compare e contraste os resultados de paste0() com str_c() para as seguintes entradas:

    str_c("oi ", NA)
    str_c(letters[1:2], letters[1:3])
  2. Qual é a diferença entre paste() e paste0()? Como você pode criar o equivalente da função paste() com str_c()?

  3. Converta as seguintes expressões de str_c() para str_glue() ou vice-versa:

    1. str_c("O preço do(a) ", alimento, " é ", preco)

    2. str_glue("Eu tenho {idade} anos e moro no(a) {pais}")

    3. str_c("\\seção{", titulo, "}")

14.4 Extraindo dados de strings

É muito comum que múltiplas variáveis sejam agrupadas em uma única string. Nesta seção, você aprenderá a como usar quatro funções do tidyr para extrair múltiplas variáveis:

  • df |> separate_longer_delim(col, delim)
  • df |> separate_longer_position(col, width)
  • df |> separate_wider_delim(col, delim, names)
  • df |> separate_wider_position(col, widths)

Se você observar atentamente, poderá perceber que há um padrão comum entre os nomes das funções: separate_, seguido por longer ou wider, depois _, e, por fim, delim ou position. Isso se deve ao fato de que essas quatro funções são baseadas em duas primitivas mais simples:

  • Assim como com pivot_longer() e pivot_wider(), as funções com terminação _longer tornam o data frame de entrada mais longo, criando novas linhas, enquanto as funções com terminação _wider tornam o data frame de entrada mais largo, gerando novas colunas.
  • delim divide uma string por um delimitador, como ", " ou " "; position divide em larguras específicas, como c(3, 5, 2).

Falaremos sobre o último membro dessa família, separate_wider_regex(), no Capítulo 15. Essa é a mais flexível das funções wider, mas você precisa saber um pouco sobre expressões regulares antes para poder usá-la.

As duas seções a seguir fornecerão a ideia básica por trás dessas funções para separação, primeiro separando em linhas (o que é um pouco mais simples) e, depois, separando em colunas. Finalizaremos discutindo as ferramentas que as funções wider fornecem para diagnosticar problemas.

14.4.1 Separando em linhas

Separar uma string em linhas geralmente é mais útil quando o número de componentes varia de linha para linha. O caso mais comum é utilizar separate_longer_delim() para dividir com base em um delimitador:

df1 <- tibble(x = c("a,b,c", "d,e", "f"))
df1 |> 
  separate_longer_delim(x, delim = ",")
#> # A tibble: 6 × 1
#>   x    
#>   <chr>
#> 1 a    
#> 2 b    
#> 3 c    
#> 4 d    
#> 5 e    
#> 6 f

É mais raro empregar separate_longer_position() na prática, mas alguns conjuntos de dados mais antigos utilizam um formato bastante compacto, no qual cada caractere é empregado para registrar um valor:

df2 <- tibble(x = c("1211", "131", "21"))
df2 |> 
  separate_longer_position(x, width = 1)
#> # A tibble: 9 × 1
#>   x    
#>   <chr>
#> 1 1    
#> 2 2    
#> 3 1    
#> 4 1    
#> 5 1    
#> 6 3    
#> # ℹ 3 more rows

14.4.2 Separando em colunas

Separar uma string em colunas tende a ser mais útil quando há um número fixo de componentes em cada string e você deseja distribuí-los em colunas. No entanto, as funções empregadas nesse caso são ligeiramente mais complicadas do que suas equivalentes longer porque você precisa nomear as colunas. Por exemplo, no conjunto de dados a seguir, x é composto por um código, um número de edição e um ano, estando separados por ".". Para utilizar a função separate_wider_delim(), fornecemos o delimitador e os nomes em dois argumentos distintos:

df3 <- tibble(x = c("a10.1.2022", "b10.2.2011", "e15.1.2015"))
df3 |> 
  separate_wider_delim(
    x,
    delim = ".",
    names = c("código", "edição", "ano")
  )
#> # A tibble: 3 × 3
#>   código edição ano  
#>   <chr>  <chr>  <chr>
#> 1 a10    1      2022 
#> 2 b10    2      2011 
#> 3 e15    1      2015

Se uma variável em particular não for útil, você pode utilizar NA, no lugar do nome, para omiti-la dos resultados:

df3 |> 
  separate_wider_delim(
    x,
    delim = ".",
    names = c("código", NA, "ano")
  )
#> # A tibble: 3 × 2
#>   código ano  
#>   <chr>  <chr>
#> 1 a10    2022 
#> 2 b10    2011 
#> 3 e15    2015

A função separate_wider_position() funciona de forma um pouco diferente, pois normalmente você deseja especificar a largura de cada coluna. Então, você deve fornecer à função um vetor de inteiros nomeado, em que o nome corresponde ao nome da nova coluna e o valor é a quantidade de caracteres que constitui a string correspondente. Você pode omitir valores da saída ao não nomeá-los:

df4 <- tibble(x = c("202215TX", "202122LA", "202325CA")) 
df4 |> 
  separate_wider_position(
    x,
    widths = c(ano = 4, idade = 2, estado = 2)
  )
#> # A tibble: 3 × 3
#>   ano   idade estado
#>   <chr> <chr> <chr> 
#> 1 2022  15    TX    
#> 2 2021  22    LA    
#> 3 2023  25    CA

14.4.3 Diagnosticando problemas de alargamento

A função separate_wider_delim()6 requer um conjunto fixo e conhecido de colunas. Mas, o que acontece se algumas das linhas não tiverem o número esperado de caracteres? Nesse caso, existem dois problemas possíveis, poucos ou muitos caracteres, e a função separate_wider_delim() fornece dois argumentos para ajudar: too_few e too_many. Primeiramente, vamos olhar para o caso de too_few com o seguinte conjunto de dados de exemplo:

df <- tibble(x = c("1-1-1", "1-1-2", "1-3", "1-3-2", "1"))

df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z")
  )
#> Error in `separate_wider_delim()`:
#> ! Expected 3 pieces in each element of `x`.
#> ! 2 values were too short.
#> ℹ Use `too_few = "debug"` to diagnose the problem.
#> ℹ Use `too_few = "align_start"/"align_end"` to silence this message.

Você notará que obtemos um erro, mas o erro nos dá algumas sugestões sobre como podemos proceder. Vamos começar depurando o problema:

debug <- df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_few = "debug"
  )
#> Warning: Debug mode activated: adding variables `x_ok`, `x_pieces`, and
#> `x_remainder`.
debug
#> # A tibble: 5 × 6
#>   x     y     z     x_ok  x_pieces x_remainder
#>   <chr> <chr> <chr> <lgl>    <int> <chr>      
#> 1 1-1-1 1     1     TRUE         3 ""         
#> 2 1-1-2 1     2     TRUE         3 ""         
#> 3 1-3   3     <NA>  FALSE        2 ""         
#> 4 1-3-2 3     2     TRUE         3 ""         
#> 5 1     <NA>  <NA>  FALSE        1 ""

Ao usar o modo de depuração, você observará que a saída contém três colunas extras: x_ok, x_pieces e x_remainder (se você separar uma variável com um nome diferente, obterá um prefixo diferente). Aqui, x_ok permite que você encontre rapidamente as entradas que falharam:

debug |> filter(!x_ok)
#> # A tibble: 2 × 6
#>   x     y     z     x_ok  x_pieces x_remainder
#>   <chr> <chr> <chr> <lgl>    <int> <chr>      
#> 1 1-3   3     <NA>  FALSE        2 ""         
#> 2 1     <NA>  <NA>  FALSE        1 ""

A coluna x_pieces nos informa quantas partes foram encontradas, comparadas com o número esperado de 3 (o comprimento de names). Já a coluna x_remainder não é útil quando há poucas partes, mas a veremos novamente em breve.

Às vezes, analisar essas informações de depuração revelará a você problemas com sua escolha de delimitador ou mostrará que é ainda necessário fazer algum pré-processamento antes de separar. Nesse caso, corrija o problema raiz e certifique-se de remover too_few = "debug" para garantir que novos problemas se tornem erros quando a função for executada.

Em outros casos, você pode querer preencher as partes ausentes com NAs e seguir em frente. Nesse caso, isso pode ser feito por too_few = "align_start" e too_few = "align_end", que permitem controlar onde os NAs devem ser inseridos:

df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_few = "align_start"
  )
#> # A tibble: 5 × 3
#>   x     y     z    
#>   <chr> <chr> <chr>
#> 1 1     1     1    
#> 2 1     1     2    
#> 3 1     3     <NA> 
#> 4 1     3     2    
#> 5 1     <NA>  <NA>

O mesmo é válido se você tiver um número excessivo de caracteres:

df <- tibble(x = c("1-1-1", "1-1-2", "1-3-5-6", "1-3-2", "1-3-5-7-9"))

df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z")
  )
#> Error in `separate_wider_delim()`:
#> ! Expected 3 pieces in each element of `x`.
#> ! 2 values were too long.
#> ℹ Use `too_many = "debug"` to diagnose the problem.
#> ℹ Use `too_many = "drop"/"merge"` to silence this message.

Agora, ao fazer a depuração, você perceberá a importância de x_remainder:

debug <- df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_many = "debug"
  )
#> Warning: Debug mode activated: adding variables `x_ok`, `x_pieces`, and
#> `x_remainder`.
debug |> filter(!x_ok)
#> # A tibble: 2 × 6
#>   x         y     z     x_ok  x_pieces x_remainder
#>   <chr>     <chr> <chr> <lgl>    <int> <chr>      
#> 1 1-3-5-6   3     5     FALSE        4 -6         
#> 2 1-3-5-7-9 3     5     FALSE        5 -7-9

Você tem um conjunto ligeiramente diferente de opções para lidar com um número excessivo de partes: você pode simplesmente “descartar” silenciosamente os caracteres adicionais ou “unir” todos eles na última coluna:

df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_many = "drop"
  )
#> # A tibble: 5 × 3
#>   x     y     z    
#>   <chr> <chr> <chr>
#> 1 1     1     1    
#> 2 1     1     2    
#> 3 1     3     5    
#> 4 1     3     2    
#> 5 1     3     5


df |> 
  separate_wider_delim(
    x,
    delim = "-",
    names = c("x", "y", "z"),
    too_many = "merge"
  )
#> # A tibble: 5 × 3
#>   x     y     z    
#>   <chr> <chr> <chr>
#> 1 1     1     1    
#> 2 1     1     2    
#> 3 1     3     5-6  
#> 4 1     3     2    
#> 5 1     3     5-7-9

14.5 Letras

Nesta seção, vamos apresentar funções que permitem trabalhar com letras individuais em uma string. Você aprenderá como encontrar o comprimento de uma string, extrair substrings e lidar com strings longas em gráficos e tabelas.

14.5.1 Comprimento

A função str_length() informa o número de letras presentes na string:

str_length(c("a", "R para ciência de dados", NA))
#> [1]  1 23 NA

Você poderia usar essa função em conjunto com count() para encontrar a distribuição dos comprimentos dos nomes de bebês dos EUA e, em seguida, usar filter() para ver os nomes mais longos, que têm 15 letras7:

bebes |>
  count(n_letras = str_length(nome), wt = n)
#> # A tibble: 14 × 2
#>   n_letras        n
#>      <int>    <int>
#> 1        2   338150
#> 2        3  8589596
#> 3        4 48506739
#> 4        5 87011607
#> 5        6 90749404
#> 6        7 72120767
#> # ℹ 8 more rows

bebes |> 
  filter(str_length(nome) == 15) |> 
  count(nome, wt = n, sort = TRUE)
#> # A tibble: 34 × 2
#>   nome                n
#>   <chr>           <int>
#> 1 Franciscojavier   123
#> 2 Christopherjohn   118
#> 3 Johnchristopher   118
#> 4 Christopherjame   108
#> 5 Christophermich    52
#> 6 Ryanchristopher    45
#> # ℹ 28 more rows

14.5.2 Subconjunto

Você pode extrair partes de uma string utilizando str_sub(string, start, end), em que start e end são as posições onde a substring deve começar e terminar. Os argumentos start e end são inclusivos, então o comprimento da string resultante será end - start + 1:

x <- c("Maça", "Banana", "Pera")
str_sub(x, 1, 3)
#> [1] "Maç" "Ban" "Per"

Você pode usar valores negativos para contar a partir do final da string: -1 é o último caractere, -2 é o penúltimo caractere e assim por diante.

str_sub(x, -3, -1)
#> [1] "aça" "ana" "era"

Note que str_sub() não falhará se a string for muito curta: a função simplesmente retornará a quantidade máxima de letras possível:

str_sub("a", 1, 5)
#> [1] "a"

Poderíamos utilizar a função str_sub() com mutate() para encontrar a primeira e a última letra de cada nome:

bebes |> 
  mutate(
    primeira = str_sub(nome, 1, 1),
    ultima = str_sub(nome, -1, -1)
  )
#> # A tibble: 1,924,665 × 7
#>     ano sexo  nome          n   prop primeira ultima
#>   <dbl> <chr> <chr>     <int>  <dbl> <chr>    <chr> 
#> 1  1880 F     Mary       7065 0.0724 M        y     
#> 2  1880 F     Anna       2604 0.0267 A        a     
#> 3  1880 F     Emma       2003 0.0205 E        a     
#> 4  1880 F     Elizabeth  1939 0.0199 E        h     
#> 5  1880 F     Minnie     1746 0.0179 M        e     
#> 6  1880 F     Margaret   1578 0.0162 M        t     
#> # ℹ 1,924,659 more rows

14.5.3 Exercícios

  1. Ao calcular a distribuição do comprimento dos nomes de bebês, por que usamos wt = n?
  2. Utilize str_length() e str_sub() para extrair a letra do meio de cada nome de bebê. O que você pode fazer se a string tiver um número par de caracteres?
  3. Existem tendências significativas no comprimento dos nomes de bebês ao longo do tempo? E quanto à popularidade das primeiras e últimas letras?

14.6 Texto em outro idioma

Até agora, focamos em características de textos em inglês, os quais são particularmente fáceis de trabalhar por duas razões. Em primeiro lugar, o alfabeto em inglês é relativamente simples: há apenas 26 letras. Em segundo lugar (e talvez o mais importante), a infraestrutura computacional que usamos atualmente foi predominantemente projetada por falantes de inglês. Infelizmente, não conseguiremos abordar com profundidade outros idiomas que não sejam o inglês. Mesmo assim, gostaríamos de chamar sua atenção para alguns dos maiores desafios que você pode encontrar: codificação de caracteres, variações de letras e funções dependentes de localidade.

14.6.1 Codificação de caracteres

Ao trabalhar com textos que não sejam em inglês, o primeiro desafio geralmente é a codificação de caracteres (encoding). Para entender o que está acontecendo, precisamos compreender primeiro como os computadores representam as strings. No R, podemos acessar a representação implícita de uma string usando charToRaw():

charToRaw("Hadley")
#> [1] 48 61 64 6c 65 79

Cada um desses seis números hexadecimais representa uma letra: 48 é H, 61 é a, e assim por diante. A correspondência do número hexadecimal com o caractere é chamada de codificação de caracteres (encoding) e, neste caso, a codificação é chamada de ASCII. ASCII faz um excelente trabalho ao representar caracteres em inglês porque é o Código Padrão Americano para o Intercâmbio de Informações (American Standard Code for Information Interchange).

Mas não é tão simples para idiomas diferentes do inglês. Nos primórdios da computação, existiam muitos padrões concorrentes para a codificação de caracteres não ingleses. Por exemplo, havia duas codificações diferentes para a Europa: o Latin1 (também conhecido como ISO-8859-1) era utilizado para idiomas da Europa Ocidental e o Latin2 (também conhecido como ISO-8859-2) era utilizado para idiomas da Europa Central. No Latin1, o byte b1 representa “±”, mas, no Latin2, representa “ą”! Felizmente, hoje existe um padrão que é suportado praticamente em todos os lugares: UTF-8. O padrão UTF-8 pode codificar praticamente todos os caracteres usados atualmente e muitos símbolos extras, como emojis.

O pacote readr utiliza o padrão UTF-8. Essa é uma configuração padrão boa, mas pode falhar para dados produzidos por sistemas mais antigos que não utilizam o UTF-8. Se isso acontecer, suas strings serão exibidas de forma estranha ao imprimi-las. Às vezes, apenas um ou dois caracteres podem estar incorretos. Em outras situações, você pode obter um conjunto de caracteres completamente sem sentido. Por exemplo, aqui estão dois CSVs com codificações incomuns8:

x1 <- "text\nEl Ni\xf1o foi particularmente severo este ano"
read_csv(x1)$text
#> [1] "El Ni\xf1o foi particularmente severo este ano"

x2 <- "text\n\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd"
read_csv(x2)$text
#> [1] "\x82\xb1\x82\xf1\x82ɂ\xbf\x82\xcd"

Para lê-los corretamente, você deve especificar a codificação por meio do argumento locale:

read_csv(x1, locale = locale(encoding = "Latin1"))$text
#> [1] "El Niño foi particularmente severo este ano"

read_csv(x2, locale = locale(encoding = "Shift-JIS"))$text
#> [1] "こんにちは"

Como encontrar a codificação correta? Se tiver sorte, ela estará incluída em algum lugar na documentação dos dados. Infelizmente, isso raramente é o caso, então o pacote readr fornece a função guess_encoding() para ajudar você a descobrir. Ela não é infalível e tem melhor desempenho quando o texto é mais extenso (diferente do caso atual), porém é um bom ponto de partida. É provável que você precise experimentar várias codificações diferentes antes de encontrar a correta.

As codificações são um tema rico e complexo; apenas começamos a explorá-la aqui. Se você deseja aprender mais, recomendamos ler a explicação detalhada em http://kunststube.net/encoding/.

14.6.2 Variações de letras

Trabalhar com idiomas que possuem acentos representa um desafio significativo ao determinar a posição das letras (por exemplo, com str_length() e str_sub()), pois as letras acentuadas podem ser codificadas de duas formas: como um único caractere individual (por exemplo, ü) ou como dois caracteres, combinando uma letra sem acento (por exemplo, u) com um diacrítico (por exemplo, ¨). Por exemplo, este código mostra duas maneiras de representar ü que aparentam ser idênticas:

u <- c("\u00fc", "u\u0308")
str_view(u)
#> [1] │ ü
#> [2] │ ü

No entanto, as strings diferem em comprimento e seus caracteres iniciais são diferentes:

str_length(u)
#> [1] 1 2
str_sub(u, 1, 1)
#> [1] "ü" "u"

Por fim, observe que uma comparação dessas strings com == as interpreta como diferentes, enquanto a função str_equal() do pacote stringr reconhece que ambas têm a mesma aparência:

u[[1]] == u[[2]]
#> [1] FALSE

str_equal(u[[1]], u[[2]])
#> [1] TRUE

14.6.3 Funções dependentes de localidade

Por fim, existem algumas funções do pacote stringr cujo comportamento depende da sua localidade. Uma localidade é semelhante a um idioma, mas inclui um especificador opcional de região para lidar com variações regionais dentro de um idioma. Uma localidade é especificada por uma abreviação do idioma em minúsculo, seguida opcionalmente por um _ e um identificador da região em maiúsculo. Por exemplo, “en” corresponde ao inglês, “en_GB” ao inglês britânico e “en_US” ao inglês americano. Se você ainda não conhece o código para o seu idioma, o Wikipedia possui uma boa lista e você pode verificar quais são suportados pelo pacote stringr empregando a função stringi::stri_locale_list().

As funções de string do R base usam automaticamente a localidade definida pelo seu sistema operacional. Isso significa que as funções de string do R base fazem o que você espera para o seu idioma, mas seu código pode funcionar de maneira diferente se for compartilhado com alguém que vive em um país diferente. Para evitar esse problema, o stringr utiliza, por padrão, as regras do inglês, usando a localidade como “en”, e exige que você especifique o argumento locale para substituir a localidade. Felizmente, há dois conjuntos de funções onde a localidade realmente importa: a função para modificar a caixa das letras e a função para ordenar as letras.

As regras para alterar maiúsculas e minúsculas diferem entre os idiomas. Por exemplo, o turco possui dois i’s: com e sem um ponto. Como são duas letras distintas, elas são capitalizadas de forma diferente:

str_to_upper(c("i", "ı"))
#> [1] "I" "I"
str_to_upper(c("i", "ı"), locale = "tr")
#> [1] "İ" "I"

Ordenar strings depende da ordem alfabética, mas essa ordem não é a mesma em todos os idiomas9! Aqui, está um exemplo: em tcheco, “ch” é uma letra composta que aparece após o h no alfabeto.

str_sort(c("a", "c", "ch", "h", "z"))
#> [1] "a"  "c"  "ch" "h"  "z"
str_sort(c("a", "c", "ch", "h", "z"), locale = "cs")
#> [1] "a"  "c"  "h"  "ch" "z"

Isso também é relevante ao ordenar strings com dplyr::arrange(), o que justifica o fato dessa função também possuir um argumento locale.

14.7 Resumo

Neste capítulo, você aprendeu sobre o poder do pacote stringr: como criar, combinar e extrair strings, além de entender alguns dos desafios que podem surgir com strings em idiomas diferentes do inglês. Agora, é hora de aprender uma das ferramentas mais importantes e poderosas para trabalhar com strings: as expressões regulares. Expressões regulares são uma linguagem concisa, porém expressiva, para descrever padrões em strings e são o tema do próximo capítulo.


  1. Ou use a função base do R writeLines().↩︎

  2. Disponível no R 4.0.0 e versões posteriores.↩︎

  3. str_view() também utiliza cores para chamar a sua atenção para tabulações, espaços, correspondências, etc. As cores atualmente não são exibidas no livro, mas você as notará ao executar o código de forma interativa.↩︎

  4. Se não estiver usando o pacote stringr, você também pode acessá-la diretamente com glue::glue().↩︎

  5. O equivalente no R base é a função paste() utilizada com o argumento collapse.↩︎

  6. Os mesmos princípios se aplicam às funções separate_wider_position() e separate_wider_regex().↩︎

  7. Olhando para essas entradas, nós deduziríamos que a base de dados referentes aos nomes de bebês eliminam espaços ou hífens e truncam após 15 letras.↩︎

  8. Aqui, o caractere especial \x é utilizado para codificar dados binários diretamente em uma string.↩︎

  9. Ordenar em idiomas que não possuem um alfabeto, como o chinês, é ainda mais complicado.↩︎