14  Caracteres

14.1 Introducción

Hasta ahora, ha usado un montón de cadenas de caracteres sin aprender mucho sobre los detalles. Ahora es el momento de sumergirse en ellas, aprender qué hace que las cadenas de caracteres funcionen y dominar algunas de las poderosas herramientas de manipulación de caracteres que tiene a su disposición.

Comenzaremos con los detalles de la creación de cadenas y vectores de caracteres. Luego se sumergirá en la creación de cadenas a partir de datos, luego lo contrario; extraer cadenas de datos. Luego hablaremos de las herramientas que funcionan con letras individuales. El capítulo finaliza con funciones que funcionan con letras individuales y una breve discusión sobre dónde podrían equivocarse sus expectativas del inglés al trabajar con otros idiomas.

Seguiremos trabajando con cadenas en el próximo capítulo, donde aprenderá más sobre el poder de las expresiones regulares.

14.1.1 Requisitos previos

En este capítulo, usaremos funciones del paquete stringr, que forma parte del núcleo tidyverse. También usaremos los datos de babynames ya que proporciona algunas cadenas divertidas para manipular.

Puede saber rápidamente cuándo está usando una función stringr porque todas las funciones stringr comienzan con str_. Esto es particularmente útil si usa RStudio porque escribir str_ activará el autocompletado, lo que le permitirá refrescar su memoria de las funciones disponibles.

str_c escrito en la consola de RStudio con la información sobre herramientas de autocompletar que se muestra en la parte superior, que enumera las funciones que comienzan con str_c. La firma de la función y el comienzo de la página del manual para la función resaltada de la lista de autocompletar se muestran en un panel a su derecha.

14.2 Creando una cadena de caracteres

Hemos creado cadenas de pasada anteriormente en el libro, pero no discutimos los detalles. En primer lugar, puede crear una cadena usando comillas simples (') o comillas dobles ("). No hay diferencia en el comportamiento entre los dos, así que en aras de la coherencia, la guía de estilo de tidyverse recomienda usar ", a menos que la cadena contiene múltiples ".

string1 <- "Esta es una cadena de caracteres"
string2 <- 'Si quiero incluir una "comilla" dentro de una cadena, uso comillas simples'

Si olvida cerrar una comilla, verá +, el indicador de continuación:

> "Esta es una cadena sin comillas de cierre
+ 
+ 
+ AYUDA ESTOY ATRAPADO EN UNA CADENA

Si esto le sucede y no sabe qué comilla cerrar, presione Escape para cancelar y vuelva a intentarlo.

14.2.1 Escapadas

Para incluir una comilla simple o doble literal en una cadena, puede usar \ para “escaparla”:

double_quote <- "\"" # o '"'
single_quote <- '\'' # o "'"

Entonces, si desea incluir una barra invertida literal en su cadena, deberá escapar: "\\":

backslash <- "\\"

Tenga en cuenta que la representación impresa de una cadena no es la misma que la cadena misma porque la representación impresa muestra los escapes (en otras palabras, cuando imprime una cadena, puede copiar y pegar la salida para recrear esa cadena). Para ver el contenido sin procesar de la cadena, use str_view()1:

x <- c(single_quote, double_quote, backslash)
x
#> [1] "'"  "\"" "\\"

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

14.2.2 Cadenas de caracteres sin procesar

Crear una cadena con múltiples comillas o barras invertidas se vuelve confuso rápidamente. Para ilustrar el problema, creemos una cadena que contenga el contenido del bloque de código donde definimos las variables double_quote y single_quote:

tricky <- "double_quote <- \"\\\"\" # o '\"'
single_quote <- '\\'' # o \"'\""
str_view(tricky)
#> [1] │ double_quote <- "\"" # o '"'
#>     │ single_quote <- '\'' # o "'"

¡Eso es un montón de barras invertidas! (Esto a veces se llama [síndrome del palillo inclinado] (https://en.wikipedia.org/wiki/Leaning_toothpick_syndrome).) Para eliminar el escape, puede usar una cadena de caracteres sin procesar2:

tricky <- r"(double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'")"
str_view(tricky)
#> [1] │ double_quote <- "\"" # or '"'
#>     │ single_quote <- '\'' # or "'"

Una cadena sin procesar generalmente comienza con r"( y termina con )". Pero si su cadena contiene )", puede usar r"[]" o r"{}", y si eso aún no es suficiente, puede insertar cualquier número de guiones para hacer los pares de apertura y cierre único, por ejemplo, r"--()--", r"---()---", etc. Las cadenas sin procesar son lo suficientemente flexibles para manejar cualquier texto.

14.2.3 Otros caracteres especiales

Además de \", \' y \\, hay otros caracteres especiales que pueden ser útiles. Los más comunes son \n, una nueva línea y \t, tabulador. A veces también verá cadenas que contienen escapes Unicode que comienzan con \u o \U. Esta es una forma de escribir caracteres no ingleses que funcionan en todos los sistemas. Puede ver la lista completa de otros caracteres especiales en ?Quote.

x <- c("one\ntwo", "one\ttwo", "\u00b5", "\U0001f604")
x
#> [1] "one\ntwo" "one\ttwo" "µ"        "😄"
str_view(x)
#> [1] │ one
#>     │ two
#> [2] │ one{\t}two
#> [3] │ µ
#> [4] │ 😄

Tenga en cuenta que str_view() usa llaves para las pestañas para que sean más fáciles de detectar 3. Uno de los desafíos de trabajar con texto es que hay una variedad de formas en que los espacios en blanco pueden terminar en el texto, por lo que este fondo lo ayuda a reconocer que algo extraño está sucediendo.

14.2.4 Ejercicios

  1. Cree cadenas que contengan los siguientes valores:

    1. Él dijo: "¡Eso es increíble!"

    2. \a\b\c\d

    3. \\\\\\

  2. Cree la cadena en su sesión R e imprímala. ¿Qué sucede con el especial “\u00a0”? ¿Cómo lo muestra str_view()? ¿Puedes googlear un poco para averiguar qué es este carácter especial?

    x <- "Esto\u00a0es\u00a0complicado"

14.3 Crear muchas cadenas de caracteres a partir de datos

Ahora que ha aprendido los conceptos básicos para crear una o dos cadenas “a mano”, entraremos en los detalles de la creación de cadenas a partir de otras cadenas. Esto lo ayudará a resolver el problema común en el que tiene un texto que escribió que desea combinar con cadenas de un data frame. Por ejemplo, puede combinar “Hola” con una variable name para crear un saludo. Le mostraremos cómo hacer esto con str_c() y str_glue() y cómo puede usarlos con mutate(). Naturalmente, eso plantea la pregunta de qué funciones de stringr podría usar con summarize(), por lo que terminaremos esta sección con una discusión de str_flatten(), que es una función de resumen para cadenas.

14.3.1 str_c()

str_c() toma cualquier número de vectores como argumentos y devuelve un vector de caracteres:

str_c("x", "y")
#> [1] "xy"
str_c("x", "y", "z")
#> [1] "xyz"
str_c("Hola ", c("Juan", "Susana"))
#> [1] "Hola Juan"   "Hola Susana"

str_c() es muy similar a la base paste0(), pero está diseñado para usarse con mutate() obedeciendo las reglas habituales de tidyverse para reciclar y propagar valores faltantes:

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

Si desea que los valores faltantes se muestren de otra manera, use coalesce() para reemplazarlos. Dependiendo de lo que quieras, puedes usarlo dentro o fuera de str_c():

df |> 
  mutate(
    greeting1 = str_c("Hi ", coalesce(name, "you"), "!"),
    greeting2 = coalesce(str_c("Hi ", name, "!"), "Hi!")
  )
#> # A tibble: 4 × 3
#>   name  greeting1 greeting2
#>   <chr> <chr>     <chr>    
#> 1 Flora Hi Flora! Hi Flora!
#> 2 David Hi David! Hi David!
#> 3 Terra Hi Terra! Hi Terra!
#> 4 <NA>  Hi you!   Hi!

14.3.2 str_glue()

Si está mezclando muchas cadenas fijas y variables con str_c(), notará que escribe muchas "s, lo que dificulta ver el objetivo general del código. Un enfoque alternativo es proporcionado por el paquete glue a través de str_glue()4. Le das una sola cadena que tiene una característica especial: cualquier cosa dentro de {} se evaluará como que está fuera de las comillas:

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

Como puede ver, str_glue() actualmente convierte los valores faltantes a la cadena "NA", desafortunadamente, lo que lo hace inconsistente con str_c().

También puede preguntarse qué sucede si necesita incluir un { o } regular en su cadena. Estás en el camino correcto si crees que necesitarás escapar de alguna manera. El truco es que el pegamento usa una técnica de escape ligeramente diferente; en lugar de anteponer un carácter especial como \, se duplican los caracteres especiales:

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

14.3.3 str_flatten()

str_c() y str_glue() funciona bien con mutate() porque su salida tiene la misma longitud que sus entradas. ¿Qué pasa si quieres una función que funcione bien con summarize(), es decir, algo que siempre devuelva una sola cadena? Ese es el trabajo de str_flatten()5: toma un vector de caracteres y combina cada elemento del vector en una sola cadena:

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 = ", and ")
#> [1] "x, y, and z"

Esto hace que funcione bien con summarize():

df <- tribble(
  ~ name, ~ fruit,
  "Carmen", "banana",
  "Carmen", "apple",
  "Marvin", "nectarine",
  "Terence", "cantaloupe",
  "Terence", "papaya",
  "Terence", "mandarin"
)
df |>
  group_by(name) |> 
  summarize(fruits = str_flatten(fruit, ", "))
#> # A tibble: 3 × 2
#>   name    fruits                      
#>   <chr>   <chr>                       
#> 1 Carmen  banana, apple               
#> 2 Marvin  nectarine                   
#> 3 Terence cantaloupe, papaya, mandarin

14.3.4 Ejercicios

  1. Compare y contraste los resultados de paste0() con str_c() para las siguientes entradas:

    str_c("hi ", NA)
    str_c(letters[1:2], letters[1:3])
  2. ¿Cuál es la diferencia entre paste() y paste0()? ¿Cómo puedes recrear el equivalente de paste() con str_c()?

  3. Convierta las siguientes expresiones de str_c() a str_glue() o viceversa:

    1. str_c("El precio de ", food, " es ", price)

    2. str_glue("Yo tengo {age} años y vivo en {country}")

    3. str_c("\\section{", title, "}")

14.4 Extraer datos de cadenas de caracteres

Es muy común que varias variables se amontonen en una sola cadena. En esta sección, aprenderá a utilizar cuatro funciones tidyr para extraerlas:

  • 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)

Si miras de cerca, puedes ver que hay un patrón común aquí: separate_, luego longer o wider, luego _, luego por delim o position. Eso es porque estas cuatro funciones se componen de dos primitivas más simples: - Al igual que con pivot_longer() y pivot_wider(), las funciones _longer hacen que el data frame de entrada sea más largo al crear nuevas filas y las funciones _wider hacen que el data frame de entrada sea más ancho al generar nuevas columnas. - delim divide una cadena con un delimitador como ", " o " "; position se divide en anchos específicos, como c(3, 5, 2).

Volveremos al último miembro de esta familia, separate_wider_regex(), en Capítulo 15. Es la más flexible de las funciones wider, pero necesita saber algo acerca de las expresiones regulares antes de poder usarla.

Las siguientes dos secciones le darán la idea básica detrás de estas funciones separadas, primero separándolas en filas (que es un poco más simple) y luego separándolas en columnas. Terminaremos discutiendo las herramientas que le brindan las funciones wider para diagnosticar problemas.

14.4.1 Separando en filas

Separar una cadena en filas tiende a ser más útil cuando el número de componentes varía de una fila a otra. El caso más común requiere que separate_longer_delim() se divida en función de un 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

Es más raro ver separate_longer_position() en la naturaleza, pero algunos conjuntos de datos más antiguos usan un formato muy compacto donde cada carácter se usa para registrar un 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 en columnas

Separar una cadena en columnas tiende a ser más útil cuando hay un número fijo de componentes en cada cadena y desea distribuirlos en columnas. Son un poco más complicados que sus equivalentes longer porque necesitas nombrar las columnas. Por ejemplo, en el siguiente conjunto de datos, x se compone de un código, un número de edición y un año, separados por ".". Para usar separate_wider_delim(), proporcionamos el delimitador y los nombres en dos argumentos:

df3 <- tibble(x = c("a10.1.2022", "b10.2.2011", "e15.1.2015"))
df3 |> 
  separate_wider_delim(
    x,
    delim = ".",
    names = c("code", "edition", "year")
  )
#> # A tibble: 3 × 3
#>   code  edition year 
#>   <chr> <chr>   <chr>
#> 1 a10   1       2022 
#> 2 b10   2       2011 
#> 3 e15   1       2015

Si una pieza específica no es útil, puede usar un nombre NA para omitirla de los resultados:

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

separate_wider_position() funciona un poco diferente porque normalmente desea especificar el ancho de cada columna. Entonces le das un vector entero con nombre, donde el nombre da el nombre de la nueva columna, y el valor es la cantidad de caracteres que ocupa. Puede omitir valores de la salida si no los nombra:

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

14.4.3 Diagnóstico de problemas de ensanchamiento

separate_wider_delim()6 requiere un conjunto fijo y conocido de columnas. ¿Qué sucede si alguna de las filas no tiene el número esperado de piezas? Hay dos posibles problemas, muy pocas o demasiadas piezas, por lo que separate_wider_delim() proporciona dos argumentos para ayudar: too_few y too_many. Primero veamos el caso too_few con el siguiente conjunto de datos de muestra:

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.

Notará que recibimos un error, pero el error nos da algunas sugerencias sobre cómo puede proceder. Comencemos por depurar el 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 ""

Cuando usa el modo de depuración, obtiene tres columnas adicionales agregadas a la salida: x_ok, x_pieces y x_remainder (si separa una variable con un nombre diferente, obtendrá un prefijo diferente). Aquí, x_ok te permite encontrar rápidamente las entradas que fallaron:

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 ""

x_pieces nos dice cuántas piezas se encontraron, en comparación con las 3 esperadas (la longitud de names). x_remainder no es útil cuando hay muy pocas piezas, pero lo veremos de nuevo en breve.

A veces, mirar esta información de depuración revelará un problema con su estrategia de delimitación o sugerirá que necesita hacer más preprocesamiento antes de separarse. En ese caso, solucione el problema aguas arriba y asegúrese de eliminar too_few = "debug" para asegurarse de que los nuevos problemas se conviertan en errores.

En otros casos, es posible que desee completar las piezas que faltan con “NA” y seguir adelante. Ese es el trabajo de too_few = "align_start" y too_few = "align_end" que le permiten controlar dónde deben ir los NA:

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>

Los mismos principios se aplican si tiene demasiadas piezas:

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.

Pero ahora, cuando depuramos el resultado, puedes ver el propósito 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

Tiene un conjunto ligeramente diferente de opciones para manejar demasiadas piezas: puede “soltar” silenciosamente cualquier pieza adicional o “fusionarlas” todas en la columna final:

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

En esta sección, le presentaremos funciones que le permitirán trabajar con letras individuales dentro de una cadena. Aprenderá a encontrar la longitud de una cadena, extraer subcadenas y manejar cadenas largas en diagramas y tablas.

14.5.1 Longitud

str_length() te dice el número de letras en la cadena:

str_length(c("a", "R for data science", NA))
#> [1]  1 18 NA

Podría usar esto con count() para encontrar la distribución de las longitudes de los nombres de bebés de EE. UU. y luego con filter() para ver los nombres más largos, que tienen 15 letras 7:

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

babynames |> 
  filter(str_length(name) == 15) |> 
  count(name, wt = n, sort = TRUE)
#> # A tibble: 34 × 2
#>   name                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

Puedes extraer partes de una cadena usando str_sub(string, start, end), donde start y end son las posiciones donde la subcadena debe comenzar y terminar. Los argumentos start y end son inclusivos, por lo que la longitud de la cadena devuelta será end - start + 1:

x <- c("Apple", "Banana", "Pear")
str_sub(x, 1, 3)
#> [1] "App" "Ban" "Pea"

Puede usar valores negativos para contar hacia atrás desde el final de la cadena: -1 es el último carácter, -2 es el penúltimo carácter, etc.

str_sub(x, -3, -1)
#> [1] "ple" "ana" "ear"

Tenga en cuenta que str_sub() no fallará si la cadena es demasiado corta: solo devolverá tanto como sea posible:

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

Podríamos usar str_sub() con mutate() para encontrar la primera y última letra de cada nombre:

babynames |> 
  mutate(
    first = str_sub(name, 1, 1),
    last = str_sub(name, -1, -1)
  )
#> # A tibble: 1,924,665 × 7
#>    year sex   name          n   prop first last 
#>   <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 Ejercicios

  1. ¿Cuando calculamos la distribución de la longitud de los nombres de los bebés, ¿por qué usamos wt = n?
  2. Use str_length() y str_sub() para extraer la letra del medio de cada nombre de bebé. ¿Qué harás si la cadena tiene un número par de caracteres?
  3. ¿Existen tendencias importantes en la longitud de los nombres de bebés a lo largo del tiempo? ¿Qué pasa con la popularidad de las primeras y últimas letras?

14.6 Texto no inglés

Hasta ahora, nos hemos centrado en el texto en inglés, con el que es particularmente fácil trabajar por dos razones. En primer lugar, el alfabeto inglés es relativamente simple: solo hay 26 letras. En segundo lugar (y quizás más importante), la infraestructura informática que usamos hoy en día fue diseñada predominantemente por angloparlantes. Desafortunadamente, no tenemos espacio para un tratamiento completo de los idiomas distintos del inglés. Aún así, queríamos llamar su atención sobre algunos de los mayores desafíos que podría encontrar: codificación, variaciones de letras y funciones dependientes de la configuración regional.

14.6.1 Codificación

Cuando se trabaja con texto que no está en inglés, el primer desafío suele ser la codificación. Para entender lo que está pasando, necesitamos sumergirnos en cómo las computadoras representan cadenas. En R, podemos llegar a la representación subyacente de una cadena usando charToRaw():

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

Cada uno de estos seis números hexadecimales representa una letra: 48 es H, 61 es a, y así sucesivamente. La asignación de un número hexadecimal a un carácter se denomina codificación y, en este caso, la codificación se denomina ASCII. ASCII hace un gran trabajo al representar los caracteres ingleses porque es el código estándar estadounidense para el intercambio de información.

Las cosas no son tan fáciles para otros idiomas además del inglés. En los primeros días de la informática, existían muchos estándares en competencia para codificar caracteres no ingleses. Por ejemplo, había dos codificaciones diferentes para Europa: Latin1 (también conocido como ISO-8859-1) se usaba para los idiomas de Europa occidental, y Latin2 (también conocido como ISO-8859-2) se usaba para los idiomas de Europa Central. En Latin1, el byte b1 es “±”, pero en Latin2, ¡es “±”! Afortunadamente, hoy en día existe un estándar que se admite en casi todas partes: UTF-8. UTF-8 puede codificar casi todos los caracteres utilizados por los humanos en la actualidad y muchos símbolos adicionales como emojis.

readr usa UTF-8 en todas partes. Este es un buen valor predeterminado, pero fallará para los datos producidos por sistemas más antiguos que no usan UTF-8. Si esto sucede, sus cadenas se verán raras cuando las imprima. A veces, solo uno o dos caracteres pueden estar en mal estado; otras veces, obtendrás un completo galimatías. Por ejemplo, aquí hay dos CSV en línea con codificaciones inusuales 8:

x1 <- "text\nEl Ni\xf1o fue particularmente malo este año"
read_csv(x1)$text
#> [1] "El Ni\xf1o fue particularmente malo este año"

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 leerlos correctamente, especifica la codificación a través del argumento locale:

read_csv(x1, locale = locale(encoding = "Latin1"))$text
#> [1] "El Ni\xf1o fue particularmente malo este año"

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

¿Cómo encuentras la codificación correcta? Si tiene suerte, se incluirá en algún lugar de la documentación de datos. Desafortunadamente, ese rara vez es el caso, por lo que readr proporciona guess_encoding() para ayudarlo a resolverlo. No es infalible y funciona mejor cuando tiene mucho texto (a diferencia de aquí), pero es un lugar razonable para comenzar. Espere probar algunas codificaciones diferentes antes de encontrar la correcta.

Las codificaciones son un tema rico y complejo; solo hemos arañado la superficie aquí. Si desea obtener más información, le recomendamos leer la explicación detallada en http://kunststube.net/encoding/.

14.6.2 Variaciones de letras

Trabajar en idiomas con acentos plantea un desafío significativo al determinar la posición de las letras (por ejemplo, con str_length() y str_sub()), ya que las letras acentuadas pueden codificarse como un solo carácter individual (por ejemplo, ü) o como dos caracteres por combinar una letra sin acento (por ejemplo, u) con un signo diacrítico (por ejemplo, ¨). Por ejemplo, este código muestra dos formas de representar ü que parecen idénticas:

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

Pero ambas cadenas difieren en longitud y sus primeros caracteres son diferentes:

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

Finalmente, tenga en cuenta que una comparación de estas cadenas con == interpreta estas cadenas como diferentes, mientras que la práctica función str_equal() en stringr reconoce que ambas tienen la misma apariencia:

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

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

14.6.3 Funciones dependientes de la configuración regional

Finalmente, hay un puñado de funciones stringr cuyo comportamiento depende de tu locale. Una configuración regional es similar a un idioma, pero incluye un especificador de región opcional para manejar las variaciones regionales dentro de un idioma. Una configuración regional se especifica mediante una abreviatura de idioma en minúsculas, seguida opcionalmente por un _ y un identificador de región en mayúsculas. Por ejemplo, “en” es inglés, “en_GB” es inglés británico y “en_US” es inglés americano. Si aún no conoce el código de su idioma, Wikipedia tiene una buena lista, y puede ver cuáles son compatibles con stringr mirando stringi::stri_locale_list().

Las funciones de cadena base R utilizan automáticamente la configuración regional establecida por su sistema operativo. Esto significa que las funciones base de cadena R hacen lo que espera para su idioma, pero su código podría funcionar de manera diferente si lo comparte con alguien que vive en un país diferente. Para evitar este problema, stringr utiliza por defecto las reglas en inglés utilizando la configuración regional “en” y requiere que especifique el argumento locale para anularlo. Afortunadamente, hay dos conjuntos de funciones donde la configuración regional realmente importa: cambio de mayúsculas y minúsculas y clasificación.

Las reglas para cambiar entre mayúsculas y minúsculas difieren entre idiomas. Por ejemplo, el turco tiene dos i: con y sin punto. Como son dos letras distintas, se escriben en mayúsculas de manera diferente:

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

¡La clasificación de las cadenas depende del orden del alfabeto, y el orden del alfabeto no es el mismo en todos los idiomas 9! He aquí un ejemplo: en checo, “ch” es una letra compuesta que aparece después de la h en el 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"

Esto también surge al ordenar cadenas con dplyr::arrange(), por lo que también tiene un argumento locale.

14.7 Resumen

En este capítulo, aprendió algo sobre el poder del paquete stringr: cómo crear, combinar y extraer cadenas, y sobre algunos de los desafíos que puede enfrentar con cadenas que no están en inglés. Ahora es el momento de aprender una de las herramientas más importantes y poderosas para trabajar con cadenas: las expresiones regulares. Las expresiones regulares son un lenguaje muy conciso pero muy expresivo para describir patrones dentro de cadenas y son el tema del próximo capítulo.


  1. O usa la función base R writeLines().↩︎

  2. Disponible en R 4.0.0 y superior.↩︎

  3. str_view() también usa colores para llamar su atención sobre tabulaciones, espacios, coincidencias, etc. Los colores no aparecen actualmente en el libro, pero los notará cuando ejecute el código de forma interactiva.↩︎

  4. Si no está usando stringr, también puede acceder a él directamente con glue::glue().↩︎

  5. El equivalente base de R es paste() usado con el argumento collapse.↩︎

  6. Los mismos principios se aplican a separate_wider_position() y separate_wider_regex().↩︎

  7. Mirando estas entradas, supondríamos que los datos de babynames eliminan espacios o guiones y se truncan después de 15 letras.↩︎

  8. Aquí estoy usando el \x especial para codificar datos binarios directamente en una cadena.↩︎

  9. Clasificar en idiomas que no tienen alfabeto, como el chino, es aún más complicado.↩︎