14 Cadenas de caracteres

14.1 Introducción

Este capítulo te introduce en la manipulación de cadenas de caracteres en R. Si bien aprenderás los aspectos básicos acerca de cómo funcionan y cómo crearlas a mano, el foco del capítulo estará puesto en las expresiones regulares (o regex). Como las cadenas de caracteres suelen contener datos no estructurados o semiestructurados, las expresiones regulares resultan útiles porque permiten describir patrones en ellas a través de un lenguaje conciso. Cuando mires por primera vez una expresión regular te parecerá que un gato caminó sobre tu teclado, pero a medida que vayas ampliando tu conocimiento pronto te empezarán a hacer sentido.

14.1.1 Prerequisitos

Este capítulo se enfocará en el paquete para manipulación de cadenas llamado stringr (del inglés string, cadena). stringr no es parte central del Tidyverse porque no siempre tienes que trabajar con datos textuales. Es por eso que necesitamos cargarlo explícitamente.

14.2 Cadenas: elementos básicos

Puedes crear una cadena utilizando comillas simples o dobles. A diferencia de otros lenguajes, no hay diferencias en su comportamiento. Nuestra recomendación es siempre utilizar ", a menos que quieras crear una cadena que contenga múltiples ".

Si olvidas cerrar las comillas, verás un + en la consola, que es el signo de continuación para indicar que el código no está completo:

> "Esta es una cadena de caracteres sin comillas de cierre
+ 
+ 
+ AYUDA, ESTOY ATASCADO

Si esto te ocurre, ¡presiona la tecla Escape e inténtalo de nuevo!

Para incluir comillas simples o dobles de manera literal en una cadena puedes utilizar \ para “escaparlas” (“escapar” viene de la tecla escape):

Esto quiere decir que si quieres incluir una barra invertida, necesitas duplicarla: "\\".

Ten cuidado con que la representación impresa de una cadena no es equivalente a la cadena misma, ya que la representación muestra las barras utilizadas para “escapar” caracteres, es decir, para sean interpretados en su sentido literal, no como caracteres especiales. Para ver el contenido crudo de una cadena utiliza writeLines():

Existe una serie de otros caracteres especiales. Los más comunes son "\n", para salto de línea, y "\t", para tabulador. Puedes ver la lista completa pidiendo ayuda acerca de ": ?'"' o ?"'". A veces también verás cadenas del tipo "\u00b5", que es la manera de escribir caracteres que no están en inglés para que funcionen en todas las plataformas:

Usualmente se guardan múltiples cadenas en un vector de caracteres. Puedes crearlo usando c():

14.2.1 Largo de cadena

R Base tiene muchas funciones para trabajar con cadenas de caracteres, pero las evitaremos porque pueden ser incosistentes, lo que hace que sean difíciles de recordar. En su lugar, utilizaremos funciones del paquete stringr. Estas tienen nombres más intuitivos y todas empienzan con str_. Por ejemplo, str_length() te dice el número de caracteres de una cadena (length en inglés es largo):

El prefijo común str_ es particularmente útil si utilizas RStudio, ya que al escribir str_ se activa el autocompletado, lo que te permite ver todas las funciones de stringr:

14.2.2 Combinar cadenas

Para combinar dos o más cadenas utiliza str_c():

Usa el argumento sep para controlar cómo separlas:

Al igual que en muchas otras funciones de R, los valores perdidos son contagiosos. Si quieres que se impriman como "NA", utiliza str_replace_na() (replace = remplazar):

Como se observa arriba, str_c() es una función vectorizada que automáticamente recicla los vectores más cortos hasta alcanzar la extensión del más largo:

Los objetos de extensión 0 se descartan de manera silenciosa. Esto es particularmente útil en conjunto con if (si):

Para colapsar un vector de cadenas en una sola, utiliza collapse:

14.2.3 Dividir cadenas

Puedes extraer partes de una cadena utilizando str_sub(). Al igual que la cadena, str_sub() tiene como argumentos start (inicio) y end (fin), que indican la posición (inclusiva) del subconjunto que se quiere extraer:

Ten en cuenta que str_sub() no fallará si la cadena es muy corta; simplemente devolverá todo lo que sea posible:

También puedes utilizar str_sub() en forma de asignación para modificar una cadena:

14.2.4 Locales

Arriba utilizamos str_to_lower() para cambiar el texto a minúsculas. También puedes utilizar str_to_upper() o str_to_title(), si quieres modificar el texto a mayúsculas o formato título, respectivamente. Sin embargo, este tipo de cambios puede ser más complicado de lo parece a primera vista, ya que las reglas no son iguales en todos los idiomas. Puedes selecionar qué tipo de reglas aplicar especificando el entorno local o locale:

El entorno local o locale se especifica con un código de idioma ISO 639, que es una abreviación de dos letras. Si todavía no conoces el código para tu idioma, en Wikipedia puedes encontrar una buena lista. Si dejas el locale en blanco, se aplicará el que estés utilizando actualmente, que es provisto por tu sistema operativo.

Otra operación importante que es afectada por el locale es ordenar. Las funciones order() y sort() de R Base ordenan las cadenas usando el locale actual. Si quieres un comportamiento consistente a través de diferentes computadoras, sería preferible usar str_sort() y str_order(), que aceptan un argumento adicional para definir el locale:

14.2.5 Ejercicios

  1. En ejemplos de código en los que no se utiliza stringr, verás usualmente paste() y paste0() (paste = pegar). ¿Cuál es la diferencia entre estas dos funciones? ¿A qué función de stringr son equivalentes? ¿Cómo difieren estas dos funciones respecto de su manejo de los NA?

  2. Describe con tus propias palabras la diferencia entre los argumentos sep y collapse de la función str_c().

  3. Utiliza str_length() y str_sub() para extraer el caracter del medio de una cadena. ¿Qué harías si el número de caracteres es par?

  4. ¿Qué hace str_wrap()? (wrap = envolver) ¿Cuándo podrías querer utilizarla?

  5. ¿Qué hace str_trim()? (trim = recortar) ¿Cuál es el opuesto de str_trim()?

  6. Escribe una función que convierta, por ejemplo, el vector c("a", "b", "c") en la cadena a, b, y c. Piensa con detención qué debería hacer dado un vector de largo 0, 1, o 2.

14.2.6 Buscar coincidencia de patrones con expresiones regulares

Las expresiones regulares son un lenguaje conciso que te permite describir patrones en cadenas de caracteres. Toma un tiempo entenderlas, pero una vez que lo hagas te darás cuenta que son extremadamente útiles.

Para aprender sobre expresiones regulares usaremos str_view() y str_view_all() (view = ver). Estas funciones toman un vector de caracteres y una expresión regular y te muestran cómo coinciden. Partiremos con expresiones regulares simples que gradualmente se irán volviendo más y más complejas. Una vez que domines la coincidencia de patrones, aprenderás cómo aplicar estas ideas con otras funciones de stringr.

14.2.7 Coincidencias básicas

Los patrones más simples buscan coincidencias con cadenas exactas:

El siguiente paso en complejidad es ., que coincide con cualquier caracter (excepto un salto de línea):

Pero si “.” coincide con cualquier caracter, ¿cómo buscar una coincidencia con el caracter “.”? Necesitas utilizar un “escape” para decirle a la expresión regular que quieres hacerla coincidir de manera exacta, no usar su comportamiento especial. Al igual que en las cadenas, las expresiones regulares usan la barra invertida, \, para “escapar” los comportamientos especiales. Por lo tanto, para hacer coincidir un ., necesitas la expresión regular \.. Lamentablemente, esto crea una problema. Estamos usando cadenas para representar una expresión regular y en ellas \ también se usa como símbolo de “escape”. Por lo tanto, para crear la expresión regular \. necesitamos la cadena "\\.".

Si \ se utiliza para escapar un caracter en una expresión regular, ¿cómo coincidir de manera literal una \? Bueno, necesitarías escaparla creando la expresión regular \\. Para crear esa expresión regular necesitas usar una cadena, que requiere también escapar la \. Esto quiere decir que para coincidir literalmente \ necesitas escribir "\\\\" — ¡necesitas cuatro barras invertidas para coincidir una!

En este libro escribiremos las expresiones regulares como \. y las cadenas que representan a las expresiones regulares como "\\.".

14.2.7.1 Ejercicios

  1. Explica por qué cada una de estas cadenas no coincide con \: "\", "\\", "\\\".

  2. ¿Cómo harías coincidir la secuencia "'\?

  3. ¿Con qué patrones coincidiría la expresión regular\..\..\..? ¿Cómo la representarías en una cadena?

14.2.8 Anclas

Por defecto, las expresiones regulares buscarán una coincidencia en cualquier parte de una cadena. Suele ser útil anclar una expresión regular para que solo busque coincidencias al inicio o al final. Puedes utilizar:

  • ^ para buscar la coincidencia al inicio de la cadena.
  • $ para buscar la coincidencia al final de la cadena.

Para recordar cuál es cuál, puedes intentar este recurso mnemotécnico que aprendimos de Evan Misshula: si empiezas con potencia (^), terminarás con dinero ($).

Para forzar que una expresión regular coincida con una cadena completa, ánclala usando tanto ^ como $:

También puedes coincidir el límite entre palabras con \b. No utilizamos frecuentemente esta forma en R, pero sí a veces cuando buscamos en RStudio el nombre de una función que también compone el nombre de otras funciones. Por ejemplo, buscaríamos \bsum\b para evitar la coincidencia con summarise, summary, rowsum y otras.

14.2.8.1 Ejercicios

  1. ¿Cómo harías coincidir la cadena "$^$" de manera literal?

  2. Dado el corpus de palabras comunes en datos::palabras, crea una expresión regular que busque palabras que:

    1. Empiecen con “y”.
    2. Terminen con “x”
    3. Tengan una extensión de exactamente tres letras. (¡No hagas trampa usando str_length()!)
    4. Tengan siete letras o más.

    Dado que esta será una lista larga, podrías quere usar el argumento match en str_view() para mostrar solo las palabras que coincidan o no coincidan.

14.2.9 Clases de caracteres y alternativas

Existe una serie de patrones especiales que coinciden con más de un caracter. Ya has visto ., que coincide con cualquier caracter excepto un salto de línea. Hay otras cuatro herramientas que son de utilidad:

  • \d: coindice con cualquier dígito.
  • \s: coincide con cualquier espacio en blanco (e.g. espacio simple, tabulador, salto de línea).
  • [abc]: coincide con a, b, o c.
  • [^abc]: coincide con todo menos con a, b, o c.

Recuerda que para crear una expresión regular que contenga \d o \s, necesitas escapar la \ en la cadena, por lo que debes escribir "\\d" o "\\s".

Utilizar una clase de caracter que contenga en su interior un solo caracter puede ser una buena alternativa a la barra invertida cuando quieres incluir un solo metacaracter en la expresión regular. Muchas personas encuentran que así es más fácil de leer.

Esto funciona para la mayoría (pero no para todos) los metacaracteres de las expresiones regulares: $ . | ? * + ( ) [ {. Desafortunadamente, existen unos pocos caracteres que tienen un significado especial incluso dentro de una clase de caracteres y deben manejarse con barras invertidas para escaparlos: ] \ ^ y -.

Puedes utiizar una disyunción para elegir entre uno más patrones alternativos. Por ejemplo, abc|d..a concidirá tanto con ‘“abc”’, como con "duna". Ten en cuenta que la precedencia de | es baja, por lo que abc|xyz coincidirá con abc o xyz, no con abcyz o abxyz. Al igual que en expresiones matemáticas, si el valor de | se vuelve confuso, utiliza paréntesis para dejar claro qué es lo que quieres:

14.2.9.1 Ejercicios

  1. Crea una expresión regular que encuentre todas las palabras que:

    1. Empiecen con una vocal.

    2. Solo contengan consonantes. (Pista: piensa en cómo buscar coincidencias para “no”-vocales.)

    3. Terminen en ón, pero no en ión.

    4. Terminen con ndo or ado.

  2. ¿Siempre a una “q” la sigue una “u”?

  3. Escribe una expresión regular que permita buscar un verbo que haya sido escrito usando voseo en segunda persona plural
    (por ejemplo, queréis en vez de quieren).

  4. Crea una expresión regular que coincida con la forma en que habitualmente se escriben los números de teléfono en tu país.

  5. En inglés existe una regla que dice que la letra i va siempre antes de la e, excepto cuando está después de una c". Verifica empíricamente esta regla utilizando las palabras contenidas en stringr::words.

14.2.10 Repetición

El siguiente paso en poder implica controlar cuántas veces queremos que se encuentre un patrón:

  • ?: 0 o 1
  • +: 1 o más
  • *: 0 o más

Ten en cuenta que la precedencia de este operador es alta, por lo que puedes escribir: cantái?s para encontrar tanto voseo americano como de la variante peninsular del español (es decir, cantás y cantáis). Esto quiere decir que en la mayor parte de los usos se necesitarán paréntesis, como bana(na)+.

También puedes especificar el número de coincidencias que quieres encontrar de manera precisa:

  • {n}: exactamente n
  • {n,}: n o más
  • {,m}: no más de m
  • {n,m}: entre n y m

Por defecto, este tipo de coincidencias son “avaras” (greedy): tratarán de coincidir con la cadena más larga posible. También puedes hacerlas “perezosas” (lazy) para que coincidan con la cadena más corta posible, poniendo un ? después de ellas. Esta es una característica avanzada de las expresiones regulares, pero es útil saber que existe:

14.2.10.1 Ejercicios

  1. Describe los equivalentes de ?, +, * en el formato {m,n}.

  2. Describe en palabras con qué coincidiría cada una de estas expresiones regulares: (lee con atención para ver si estamos utilizando una expresión regular o una cadena que define una expresión regular.)

    1. ^.*$
    2. "\\{.+\\}"
    3. \d{4}-\d{2}-\d{2}
    4. "\\\\{4}"
  3. Crea expresiones regulares para buscar todas las palabras que:

    1. Empiecen con dos consonantes.
    2. Tengan tres o más vocales seguidas.
    3. Tengan tres o más pares de vocal-consonante seguidos.

14.2.11 Agrupamiento y referencias previas

Anteriormente aprendiste sobre el uso de paréntesis para desambiguar expresiones complejas. Los paréntesis también sirven para crear un grupo de captura numerado (número 1, 2 etc.). Un grupo de captura guarda la parte de la cadena que coincide con la parte de la expresión regular entre paréntesis. Puedes referirte al mismo texto tal como fue guardado en un grupo de captura utilizando referencias previas, como \1, \2 etc. Por ejemplo, la siguiente expresión regular busca todas las frutas que tengan un par de letras repetido.

(En breve, también verás cómo esto es útil en conjunto con str_match().)

14.2.11.1 Ejercicios

  1. Describe en palabras con qué coinciden estas expresiones:

    1. (.)\1\1
    2. "(.)(.)\\2\\1"
    3. (..)\1
    4. "(.).\\1.\\1"
    5. "(.)(.)(.).*\\3\\2\\1"
  2. Construye una expresión regular que coincida con palabras que:

    1. Empiecen y terminen con el mismo caracter.

    2. Contengan un par de letras repetido (p. ej. “nacional” tiene “na” repetidos dos veces.)

    3. Contengan una letra repetida en al menos tres lugares (p. ej. “característica” tiene tres “a”.)

14.3 Herramientas

Ahora que has aprendido los elementos básicos de las expresiones regulares, es tiempo de aprender cómo aplicarlos en problemas reales. En esta sección aprenderás una amplia variedad de funciones de stringr que te permitirán:

  • Determinar qué cadenas coinciden con un patrón.
  • Encontrar la posición de una coincidencia.
  • Extraer el contenido de las coincidencias.
  • Remplazar coincidencias con nuevos valores.
  • Dividir una cadena de acuerdo a una coincidencia.

Una advertencia antes de continuar: debido a que las expresiones regulares son tan poderosas, es fácil intentar resolver todos los problemas con una sola expresión regular. En palabras de Jamie Zawinski:

Cuando se enfrentan a un problema, algunas personas piensan “Lo sé, usaré expresiones regulares.” Ahora tienen dos problemas.

Como advertencia, revisa esta expresión regular que chequea si una dirección de correo electrónico es válida:

(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]
)+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:
\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(
?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ 
\t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\0
31]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\
](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+
(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:
(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z
|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)
?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\
r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[
 \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)
?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t]
)*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[
 \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*
)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]
)+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)
*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+
|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r
\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t
]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031
]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](
?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?
:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?
:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?
:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?
[ \t]))*"(?:(?:\r\n)?[ \t])*)*:(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] 
\000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|
\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>
@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"
(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t]
)*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?
:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[
\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-
\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(
?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;
:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([
^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\"
.\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\
]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\
[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\
r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] 
\000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]
|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \0
00-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\
.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,
;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?
:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*
(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".
\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[
^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]
]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)(?:,\s*(
?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(
?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[
\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t
])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t
])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?
:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|
\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:
[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\
]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)
?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["
()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)
?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>
@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[
 \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,
;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t]
)*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?
(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".
\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:
\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\[
"()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])
*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])
+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\
.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z
|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(
?:\r\n)?[ \t])*))*)?;\s*)

En cierto sentido, este es un ejemplo “patológico” (porque las direcciones de correo electrónico son de verdad sorpresivamente complejas), pero se usa en código real. Mira esta discusión (en inglés) en stackoverflow http://stackoverflow.com/a/201378 para más detalles.

No olvides que estás trabajando en un lenguaje de programación y que tienes otras herramientas a tu disposición. En vez de crear una sola expresión regular compleja, usualmente es más fácil crear una serie de expresiones regulares más simples. Si atascaste tratando de crear una sola expresión regular que resuelva tu problema, da un paso atrás y piensa cómo podrías dividir el problema en partes más pequeñas. Esto te permitirá ir resolviendo cada desafío antes de moverte al siguiente.

14.3.1 Detectar coincidencias

Para determinar si un vector de caracteres coincide con un patrón de búsqueda, puedes utilizar str_detect(). Este devuelve un vector lógico del mismo largo que el input:

Recuerda que cuando usas vectores lógicos en un contexto numérico, FALSE (falso) se convierte en 0 y TRUE (verdadero) se convierte en 1. Eso hace que sum() (suma) y mean() (media) sean funciones útiles si quieres responder preguntas sobre coincidencias a lo largo de un vector más extenso:

Cuando tienes condiciones lógicas complejas, (p. ej. encontrar a o b pero no c, salvo que d) suele ser más fácil combinar múltiples llamadas a str_detect() con operadores lógicos, que tratar de crear una sola expresión regular. Por ejemplo, hay dos maneras de buscar todas las palabras que no contengan ninguna vocal:

Los resultados son idénticos; sin embargo, creemos que la primera aproximación es significativamente más fácil de entender. Si tu expresión regular se vuelve extremadamente compleja, trata de dividirla en partes más pequeñas, dale un nombre a cada parte y luego combínalas en operaciones lógicas.

Un uso común de str_detect() es para seleccionar elementos que coincidan con un patrón. Puedes hacer eso con subdivisiones lógicas o utilizando str_subset(), que es un “envoltorio” (wrapper) de esas operaciones.

En todo caso, lo más habitual es que tus cadenas de caracteres sean una columna de un dataframe y que prefieras utilizar la función filter() (filtrar):

Una varación de str_detect() es str_count() (count = contar): más que un simple sí o no, te indica cuántas coincidencias hay en una cadena:

Es natural usar str_count() junto con mutate():

Ten en cuenta que las coincidencias nunca se superponen. Por ejemplo, en "abababa", ¿cuántas veces se encontrará una coincidencia con el patrón "aba"? Las expresiones regulares dicen que dos, no tres:

Toma nota sobre el uso de str_view_all(). Como aprenderás dentro de poco, muchas funciones de stringr vienen en pares: una función trabaja con una sola coincidencia y la otra con todas. La segunda función tendrá el sufijo _all (todas).

14.3.2 Ejercicios

  1. Para cada uno de los siguientes desafíos, intenta buscar una solución utilizando tanto una expresión regular simple como una combinación de múltiples llamadas a str_detect().

    1. Encuentra todas las palabras que empiezan o terminan con y.

    2. Encuentra todas las palabras que empiezan con una vocal y terminan con una consonante.

    3. ¿Existen palabras que tengan todas las vocales?

  2. ¿Qué palabra tiene el mayor número de vocales? ¿Qué palabra tiene la mayor proporción de vocales? (Pista: ¿cuál es el denominador?)

14.3.3 Extraer coincidencias

Para extraer el texto de una coincidencia utiliza str_extract(). Para mostrar cómo funciona, necesitaremos un ejemplo más complicado. Para ello, usaremos una selección y adaptación al español de las oraciones disponibles originalmente en stringr::sentences, y que puedes encontrar en datos::oraciones:

Imagina que quieres encontrar todas las oraciones que tengan el nombre de un color. Primero, creamos un vector con los nombres de los colores y luego lo convertimos en una sola expresión regular:

Ahora, podemos seleccionar las oraciones que contienen un color y extraer luego el color para saber de cuál se trata:

Ten en cuenta que str_extract() solo extrae la primera coincidencia. Podemos ver eso de manera sencilla seleccionando primero todas las oraciones que tengan más de una coincidencia:

Este es un patrón de coincidencia común para las funciones de stringr, ya que trabajar con una sola coincidencia te permite utilizar estructuras de datos más simples. Para obtener todas las coincidencias, utiliza str_extract_all(). Esta función devuelve una lista:

Aprenderás más sobre listas en el capítulo Listas y en Iteración.

Si utilizas simplify = TRUE (es decir, simplificar = VERDADERO), str_extract_all() devolverá una matriz con las coincidencias más cortas expandidas hasta el largo de las más extensas:

14.3.3.1 Ejercicios

  1. Te habrás dado cuenta que en el ejemplo anterior la expresión regular que utilizamos también devolvió como resultado “arrojo” y “azulejos”, que no son nombres de colores. Modifica la expresión regular para resolver ese problema.

  2. De datos::oraciones extrae:

    1. La primera palabra de cada oración.
    2. Todas las palabras que terminen en ción.
    3. Todos los plurales.

14.3.4 Coincidencias agrupadas

Antes en este capítulo hablamos sobre el uso de paréntesis para aclarar la precedencia y las referencias previas al buscar coincidencias. También puedes utilizar los paréntesis para extraer una coincidencia compleja. Por ejemplo, imagina que quieres extraer los sustantivos de una oración. Como heurística, buscaremos cualquier palabra que venga después de un artículo (el, la, un, una, etc.). Definir qué es una palabra en una expresión regular es un poco complicado, así que aquí utilizaremos una aproximación simple: una secuencia de al menos un caracter que no sea un espacio.

str_extract() nos devuelve la coincidencia completa; str_match() nos entrega cada componente. En vez de un vector de caracteres, devuelve una matriz con una columna para la coincidencia completa y una columna para cada grupo:

(Como era de esperarse, nuestra heurística para detectar sustantivos es pobre, ya que también selecciona adjetivos como “mejor” y preposiciones como “de”.)

Si tus datos están en un tibble, suele ser más fácil utilizar tidyr::extract(). Funciona como str_match() pero requiere ponerle un nombre a las coincidencias, las que luego son puestas en columnas nuevas:

Al igual que con str_extract(), si quieres todas las coincidencias para cada cadena, tienes que utilizar str_match_all().

14.3.4.1 Ejercicios

  1. Busca en datos::oraciones todas las palabras que vengan después de un “número”, como “un(o|a)”, “dos”, “tres”, etc. Extrae tanto el número como la palabra.

  2. En español a veces se utiliza el guión para unir adjetivos, establecer relaciones entre conceptos o para unir gentilicios (p. ej., teórico-práctico, precio-calidad, franco-porteño). ¿Cómo podrías encontrar esas palabras y separar lo que viene antes y después del guión?

14.3.5 Remplazar coincidencias

str_replace() y str_replace_all() te permiten remplazar coincidencias en una nueva cadena. Su uso más simple es para remplazar un un patrón con una cadena fija:

Con str_replace_all() puedes realizar múltiples remplazos a través de un vector cuyos elementos tiene nombre (named vector):

En vez de hacer remplazos con una cadena fija, puedes utilizar referencias previas para insertar componentes de la coincidencia. En el siguiente código invertimos el orden de la segunda y la tercera palabra:

14.3.5.1 Ejercicios

  1. Remplaza en una cadena todas las barras por barras invertidas.

  2. Implementa una versón simple de str_to_lower() (a minúsculas) usando replace_all().

  3. Cambia la primera y la última letra en palabras. ¿Cuáles de esas cadenas siguen siendo palabras?

14.3.6 Divisiones

Usa str_split() para dividir una cadena en partes. Por ejemplo, podemos dividir oraciones en palabras:

Como cada componente podría tener un número diferente de elementos, esto devuelve una lista. Si estás trabajando con vectores de extensión 1, lo más fácil es extraer el primer elemento de la lista:

Otra opción es, al igual que con otras funciones de stringr que devuelven una lista, utilizar simplify = TRUE para obtener una matriz:

También puedes indicar un número máximo de elementos:

En vez de dividir una cadena según patrones, puedes hacerlo definiendo límites: caracter, línea, oración o palabra. Para ello, puedes utilizar la función boundary() (límite). En el siguiente ejemplo la división se hace por palabra (word):

14.3.6.1 Ejercicios

  1. Divide una cadena como "manzanas, peras y bananas" en elementos individuales.

  2. ¿Por qué es mejor dividir utilizando boundary("word") en vez de " "?

  3. ¿Qué pasa si dividimos con una cadena vacía ("")? Experimenta y luego lee la documentación

14.3.7 Buscar coincidencias

str_locate() y str_locate_all() te indican la posición inicial y final de una coincidencia. Son particularmente útiles cuando ninguna otra función hace exactamente lo que quieres. Puedes utilizzar str_locate() para encontrar los patrones de coincidencia y str_sub() para extraerlos y/o modificarlos.

14.4 Otro tipo de patrones

Cuando utilizas un patrón que es una cadena, este automáticamente es encapsulado en la función regex() (regex es la forma abreviada de regular expression, es decir, expresión regular):

Puedes utilizar los otros argumentos de regex() para controlar los detalles de la coincidencia:

Existen otras tres funciones que puedes utilizar en vez de regex():

14.4.1 Ejercicios

  1. ¿Cómo buscarías todas las cadenas que contienen \ con regex() vs. con fixed()?

  2. ¿Cuáles son las cinco palabras más comunes en oraciones?

14.5 Otros usos de las expresiones regulares.

Existen dos funciones útiles en R base que también utilizan expresiones regulares:

14.6 stringi

stringr está construido sobre la base del paquete stringi. stringr es útil cuando estás aprendiendo, ya que presenta un set mínimo de funciones, que han sido elegidas cuidadosamente para manejar las funciones de manipulación de cadenas más comunes. stringi, por su parte, está diseñado para ser comprehensivo. Contiene casi todas las funciones que podrías necesitar: stringi tiene 244 funciones, frente a las 49 de stringr.

Si en algún momento te encuentras en dificultades para hacer algo en stringr , vale la pena darle una mirada a stringi. Ambos paquetes funcionan de manera muy similar, por lo que deberías poder traducir tu conocimiento sobre stringr de manera natural. La principal diferencia es el prefijo: str_ vs. stri_.

14.6.1 Ejercicios

  1. Busca la función de stringi que:

    1. Cuenta el número de palabras.
    2. Busca cadenas duplicadas.
    3. Genera texto aleatorio.
  2. ¿Cómo puedes controlar qué lengua usa stri_sort() para ordenar?