Tracks
/
R
R
/
Temario
/
Strings
St

Strings en R

2 ejercicios

Acerca de Strings

Contexto

Un string es un fragmento de texto encerrado entre comillas.

mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"

Por desgracia, la terminología puede resultar confusa para los programadores familiarizados con otros lenguajes.

Aunque el type es character, R no distingue entre una sola letra (llamada char en varios otros lenguajes, pero no en R) y un string largo. Ambos son un único elemento, y pueden ir entre comillas simples ' ' o dobles " " de forma intercambiable.

La guía de estilo recomienda usar comillas dobles, salvo cuando quieres usar comillas dobles dentro del string sin escaparlas.

str2 <- 'String with "quotes"'  # convenience format
str2
#> [1] "String with \"quotes\""    # standard format

Así pues, un character vector es un vector de strings, y cada string es un único elemento.

s <- c("some", "strings")
s
#> [1] "some"    "strings"
length(s)
#> [1] 2
length(s[1])  # the number of vector elements, not the string length
#> [1] 1

nchar(s[1])  # the number of characters in the string "some"
#> [1] 4

En el ejemplo anterior, length(s[1]) equivale a length("some"): el número de elementos del vector, no la longitud del string.

Manipular un string concreto es, obviamente, posible, como con nchar() en el ejemplo anterior, pero requiere funciones específicas que veremos a continuación.

Escapado

Como vimos antes, algunos signos de puntuación deben «escaparse» anteponiéndoles una barra invertida \.

La lista no sorprenderá a la mayoría de los programadores con experiencia, ya que este tema está bastante estandarizado entre lenguajes.

Además de las comillas (como se muestra arriba), la lista incluye saltos de línea \n, tabuladores \t, la barra invertida \\ y símbolos Unicode que empiezan por \U o \u.

Para evitar usar muchos caracteres de escape, es posible definir strings sin procesar dentro de r"( )":

r"(raw "string")"
#> [1] "raw \"string\""

Para los strings sin procesar que contienen paréntesis ( ), hay muchas otras opciones de sintaxis.

Locales

Cualquier versión de R con soporte actual codifica los strings en UTF-8, así que se pueden admitir la mayoría de los idiomas del mundo.

"आधुनिक मानक हिन्दी"
#> [1] "आधुनिक मानक हिन्दी"

Además, muchas funciones de R tienen en cuenta el locale e intentan seguir las convenciones locales para ordenar strings, convertir a mayúsculas/minúsculas, fechas y horas, etc.

Este temario mostrará ejemplos principalmente en inglés estadounidense: tanto porque es un valor predeterminado habitual como porque los autores del temario residen en Estados Unidos.

Los locales son un tema complejo, así que busca documentación para tu situación concreta.

Funciones para strings

Casi todo es posible en R base, pero muchos programadores consideran que el soporte para manipular strings es bastante limitado y confuso.

Por suerte, existen paquetes de tidyverse mucho más intuitivos, flexibles y coherentes entre sí.

R base

Todavía hay mucho código heredado que usa funciones de strings al estilo antiguo. Esto incluye la mayoría de las soluciones de la comunidad en Exercism.

Además, para tareas pequeñas puede ser útil usar funciones integradas en lugar de importar una biblioteca grande. Los mantenedores de la pista de R suelen adoptar este enfoque al escribir soluciones ejemplares para validar los ejercicios en nuestro repositorio de GitHub.

Por tanto, sigue mereciendo la pena conocer un poco los enfoques antiguos.

Las funciones siguientes se presentan en su forma más simple, pero la mayoría también tienen argumentos opcionales. Consulta la documentación para más detalles.

nchar

Devuelve el número de caracteres de un string.

nchar("string")
#> [1] 6

nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7

El tema (¡complicado!) de qué significa «carácter» queda fuera de nuestro alcance. Basta con fijarse en un par de puntos de los ejemplos anteriores:

  • Aunque internamente están implementados en C, los strings de R no terminan en \0 (lo que añadiría 1 a la longitud).
  • Los caracteres Unicode multibyte se tratan como cabría esperar. «Hrōðgār» tiene 7 caracteres (también llamados «runas» o «puntos de código»), pero necesita 10 bytes para almacenarse.

Ten cuidado con lo que le pasas a nchar. Los resultados aún pueden ser impredecibles con valores que no sean character, aunque esto ha mejorado en las versiones recientes de R.

grep

Busca un patrón en un vector de strings y devuelve los índices en los que hay coincidencia.

grep("Jav", c("Java", "Python", "Javascript"))
#> [1] 1 3
paste y paste0

Combina un número arbitrario de strings en un único string.

Opcionalmente, se puede especificar un separador para paste: el valor predeterminado es un espacio.

paste0(v) equivale a paste(v, sep = "").

paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintf

Para un control más preciso del ensamblado de strings, R copia la función sprintf de C (y de varios lenguajes posteriores: Wikipedia enumera unos 30).

r <- 5.3
pi <- 3.14159
sprintf("A circle of radius %.1f has area %.2f", r, pi * r^2)
#> [1] "A circle of radius 5.3 has area 88.25"
trimws

Los espacios en blanco al principio y/o al final se pueden eliminar con trimws():

s <- "    messy string   "
trimws(s)
#> [1] "messy string"

Existe un parámetro which = "left" (o «right») para no recortar ambos extremos.

substr

Opera sobre un único string y devuelve el substring entre dos límites (inclusive).

substr("Exercism", 5, 7)
#> [1] "cis"
strsplit

Divide un string en una lista de R, a partir de un string separador. El separador es obligatorio (no tiene valor predeterminado), pero puede ser un string vacío.

strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R"      "Python" "Julia" 

strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"

Las listas se tratarán en un concepto aparte. Si aún no has llegado a esa parte del temario, basta con saber por ahora que la función unlist() convertirá (en este caso) una lista en un vector.

unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
regexpr y regexpr

Abreviatura de «regular expression» (expresión regular), estas funciones buscan apariciones de un patrón en un string y devuelven una lista de datos (potencialmente) útiles. Se mencionan solo por completitud: ¡hay opciones mejores!

regexpr devuelve solo la primera aparición; gregexpr devuelve todas («g» de búsqueda «global»).

Como indican sus nombres, el patrón puede ser una expresión regular, que se tratará en un concepto posterior.

Advanced

¿Por qué estas viejas funciones de strings se parecen a comandos del shell de Unix?

Aunque R se creó en 1993, se diseñó para ser en gran medida retrocompatible con el lenguaje S.

S se diseñó en Bell Labs y apareció por primera vez en 1976. Bell Labs también fue responsable de Unix (desde 1969) y del lenguaje C (1972).

¡Menudo equipo el de aquella época tan anterior! Solo podemos suponer que hablaban entre ellos e intercambiaban ideas.

Tidyverse

Siendo generosos, podríamos decir que las funciones de strings al estilo antiguo no son lo mejor que R puede ofrecer (también hay opiniones menos generosas).

Para mejorar la situación, la biblioteca stringr lleva en desarrollo desde 2009 y ahora es una parte clave de la colección Tidyverse.

Impórtala en tu espacio de nombres con library(stringr) o library(tidyverse).

Algunas funcionalidades clave son:

  • Todos los nombres de función tienen el prefijo str_ e intentan dejar claro su propósito.
  • El orden de los argumentos intenta ser coherente, con los datos de entrada como primer argumento.
  • Las entradas pueden ir desde un único string hasta estructuras de datos complejas. Las funciones intentarán adaptarse y hacer lo más sensato.

Como todos los paquetes de Tidyverse, stringr tiene buena documentación, normalmente escrita por los autores del paquete.

Hay decenas de funciones disponibles en stringr, y a menudo tienen diversos argumentos opcionales. Esto puede resultar abrumador al principio, así que la documentación es tu mejor aliada.

Piensa en qué quieres introducir (¿un único string? ¿un vector? ¿algo más complejo?) y qué quieres obtener.

Probablemente haya una forma sencilla de hacer lo que quieras, si encuentras la función y los argumentos correctos.

Digresión: una introducción rápida a los pipes

El uso de los pipes |> para conectar funciones se tratará con más detalle en otras partes del temario.

Por ahora, es útil saber que:

  • El resultado a la izquierda del pipe pasa a ser el primer argumento de la función a la derecha (donde «derecha» ignora los saltos de línea opcionales).
  • Los argumentos restantes se pueden incluir en la llamada a la función.
  • En R, hay que encadenar con el pipe a una llamada a función, no solo a un nombre, así que los paréntesis siempre son obligatorios (esto difiere de otros lenguajes).

El ejemplo siguiente ilustra la sintaxis. Las funciones individuales se tratarán más adelante.

library(stringr)

"Monday-25-February" |> 
  str_split("-") |>     
  unlist() |>               # [1] "Monday"   "25"       "February"
  str_sub(1, 3) |>          # [1] "Mon" "25"  "Feb"
  str_to_upper() |>         # [1] "MON" "25"  "FEB"
  str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"
Gestionar longitudes

str_length cuenta puntos de código (similar a nchars).

Otras funciones manipulan los espacios en blanco, como str_trim() y str_squish(). A menudo es una tarea de limpieza esencial al inicio de cualquier proyecto de ciencia de datos.

# The first 3 kernels in JuPyteR notebooks:
str_length(c("Julia", "Python", "R"))           #> [1] 1 6 5
#> [1] 5 6 1
str_pad(c("Julia", "Python", "R"), 8, "right")  # add spaces if necessary
#> [1] "Julia   " "Python  " "R       "

str_trim("Julia    Python    R    ")   # remove leading & trailing whitespace
#> [1] "Julia    Python    R"
str_squish("Julia   Python  R    ")  # trim, collapse multiple spaces 
#> [1] "Julia Python R"
Extraer substrings

La función str_sub(string, start, end) devuelve el substring desde start hasta end (inclusive), mientras que str_sub_all() puede operar sobre varios strings.

Hay mucha flexibilidad. start y end tienen como valores predeterminados el primer y el último carácter, respectivamente. Pueden ser vectores de índices. En un contraste bienvenido con la indexación de vectores, los valores negativos cuentan desde el final del string, al estilo de Python.

s <- "abcdefgh"
str_sub(s, 2, 4)
#> [1] "bcd"
str_sub(s, 5)
#> [1] "efgh"
str_sub(s, -3, -2)
#> [1] "fg"
str_sub(s, c(1, 4), c(2, 5))
#> [1] "ab" "de"
str_sub(s, c(1, 4, 6), c(2, 5, -1))
#> [1] "ab"  "de"  "fgh"

s |> str_sub(2, 4) # these functions are designed for pipes
#> [1] "bcd"

# str_sub_all takes vector input, gives list output
str_sub_all(c(s, "ijklmnop"), c(1, 4, 6), c(2, 5, -1))
[[1]]
#> [1] "ab"  "de"  "fgh"

[[2]]
#> [1] "ij"  "lm"  "nop"
Unir strings

str_flatten convierte un vector de caracteres en un único string. El argumento collapse se inserta entre cada elemento y su valor predeterminado es "".

c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"

str_c convierte varios vectores de caracteres en un único vector de caracteres, reciclando cuando es necesario.

str_c(LETTERS[1:8], 1:8, sep = ":")
#> [1] "A:1" "B:2" "C:3" "D:4" "E:5" "F:6" "G:7" "H:8"

str_glue aporta a R una interpolación de strings sencilla. Las expresiones entre llaves { } se interpretan y se sustituye su representación textual.

Si se proporcionan varios strings de expresiones, también se unirán como en str_flatten. Opcionalmente, también se pueden recortar los espacios en blanco al principio y al final de los strings.

r <- 5.3
pi <- 3.14159
str_glue("A circle of radius {r} has area {pi * r^2}")
A circle of radius 5.3 has area 88.2472631

Aunque str_glue es flexible (y bastante complicada), la biblioteca subyacente glue ofrece un abanico de posibilidades aún mayor.

Dividir strings

Un uso muy habitual de R es transformar datos desordenados en un formato coherente para su análisis. Para ayudar con esto, hay varias formas de dividir texto en substrings.

str_split_1 es la más sencilla: convierte un único string en un vector de fragmentos, divididos según algún patrón especificado (no hay patrón predeterminado).

s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R"      "Julia"  "Python"

str_split_i toma un vector de caracteres y un índice n, y devuelve un vector con el substring número n de cada string de entrada.

s2 <- c(s, "C Kotlin, F#")  #> [1] "R Julia Python" "C Kotlin, F#"
str_split_i(s2, " ", 2)  # 2nd element of each
#> [1] "Julia"   "Kotlin,"

str_split toma un vector de caracteres y devuelve una lista de vectores. Hay un concepto de listas en nuestro temario que explica el resultado.

str_split(s2, " ")
[[1]]
#> [1] "R"      "Julia"  "Python"

[[2]]
#> [1] "C"       "Kotlin," "F#"     

También existe str_split_fixed, que devuelve una matriz. Las matrices se tratarán en un concepto posterior.

Comparar strings

Para comparar el string completo, str_equal() puede usar varias definiciones de «igualdad». Lo más sencillo: el valor predeterminado distingue mayúsculas de minúsculas, pero se puede cambiar. Los locales y Unicode crean muchas más posibilidades.

str_equal("Odin", "odin")
#> [1] FALSE
str_equal("Odin", "odin", ignore_case = TRUE)
#> [1] TRUE

Hay muchas otras funciones, que se tratarán con más detalle en el concepto de expresiones regulares.

Transformar strings

Algunos idiomas (principalmente europeos) distinguen entre letras MAYÚSCULAS y minúsculas. Hay un conjunto de funciones de stringr para convertir entre ellas, de forma adecuada a cada local:

  • str_to_lower()
  • str_to_upper()
  • str_to_title() : primera letra de cada palabra a mayúscula
  • str_to_sentence() : primera letra de la frase a mayúscula
str_to_upper("elixir")    # defaults to locale = "en"
#> [1] "ELIXIR"
str_to_upper("ελληνικά")  # infers Greek
#> [1] "ΕΛΛΗΝΙΚΆ"

str_to_title("the cat sat on the mat")
#> [1] "The Cat Sat On The Mat"
str_to_sentence("the cat sat on the mat")
#> [1] "The cat sat on the mat"

Para sustituir substrings basándose en la coincidencia de patrones, str_replace() cambia la primera aparición del patrón y str_replace_all() cambia todas las apariciones.

Para sustituir substrings basándose en la indexación de caracteres, existe una forma de asignación de str_sub().

 s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"
Varios

str_dup crea duplicados y str_unique elimina duplicados.

De forma predeterminada, str_dup no tiene separador, pero se puede especificar uno.

str_dup("xyz", 4, sep = ", ")
#> [1] "xyz, xyz, xyz, xyz"

str_unique es una extensión de unique(). Ambas eliminan las entradas duplicadas de un vector, pero la función de stringr también puede gestionar la distinción entre mayúsculas y minúsculas y varias coincidencias específicas de cada local.

inp <- c("some", "strings", "Some", "Strings")
str_unique(inp)
#> [1] "some"    "strings" "Some"    "Strings"
str_unique(inp, ignore_case = TRUE)
#> [1] "some"    "strings"
Advanced

La biblioteca stringr se basa en la biblioteca stringi de funciones de más bajo nivel.

Si tienes necesidades concretas que no se hayan tratado ya más arriba:

  1. Consulta la documentación de stringr, que cubre más opciones de las que podemos mencionar incluso en este largo documento de concepto.
  2. Consulta la documentación de stringi, que describe muchísima más funcionalidad.
Editar en GitHub El enlace se abre en una nueva ventana o pestaña

Aprende Strings

La práctica está bloqueada

Desbloquea 1 ejercicio más para practicar Strings