Tracks
/
R
R
/
Temario
/
Strings
St

Strings en R

2 ejercicios

Acerca de Strings

Antecedentes

Un string es un fragmento de texto entre comillas.

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

Por desgracia, la terminología puede resultar confusa para quienes están familiarizados con otros lenguajes.

Aunque el type es character, R no distingue entre una sola letra (a la que en varios otros lenguajes se le llama char, pero no en R) y un string largo. Ambos son un solo elemento y pueden ir entre comillas simples ' ' o dobles " " de manera 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

Un character vector es, entonces, un vector de strings, donde cada string es un solo 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 individual es, obviamente, posible, como con nchar() en el ejemplo anterior, pero requiere funciones específicas que veremos más adelante.

Escapado

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

La lista no sorprenderá a la mayoría de quienes ya tienen experiencia programando, ya que este tema está bastante estandarizado entre lenguajes.

Además de las comillas (como se mostró antes), la lista incluye saltos de línea \n, tabulaciones \t, la barra invertida \\ y símbolos Unicode que empiezan con \U o \u.

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

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

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

Configuración regional

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

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

Además, muchas funciones de R conocen el locale y tratan de seguir las convenciones locales para ordenar strings, convertir a mayúsculas o minúsculas, fechas y horas, etc.

Este temario mostrará ejemplos principalmente para el inglés de Estados Unidos: tanto porque es un valor predeterminado común como porque los autores del temario viven en Estados Unidos.

La configuración regional es un tema complejo, así que te recomendamos buscar documentación para tu propia situación.

Funciones para strings

Casi todo es posible en R base, pero a muchas personas que programan les parece que el soporte para manipular strings es más bien 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 string 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. Quienes mantienen el track de R suelen adoptar este enfoque al escribir soluciones de ejemplo para validar los ejercicios de nuestro repositorio de GitHub.

Por lo tanto, todavía vale la pena aprender algo sobre los enfoques antiguos.

Las funciones que aparecen a continuación se presentan en su forma más simple, pero la mayoría también tiene 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 del alcance de esta página. Solo fíjate en un par de puntos de los ejemplos anteriores:

  • Aunque internamente estén implementados en C, los strings en R no terminan en \0 (lo que sumaría 1 a la longitud).
  • Los caracteres Unicode de varios bytes se manejan como podrías esperar o desear. «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 para valores que no son character, aunque esto ha mejorado en versiones recientes de R.

grep

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

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

Combina una cantidad arbitraria de strings en un solo string.

De forma opcional, se puede especificar un separador para paste: el valor predeterminado es un espacio.

paste0(v) es equivalente a paste(v, sep = "").

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

Para tener un control más fino sobre el 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 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 evitar recortar ambos extremos.

substr

Opera sobre un solo 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, según 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 verán en un concepto aparte. Si todavía no has llegado a esa parte del temario, por ahora basta con saber que la función unlist() (en este caso) convierte una lista en un vector.

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

Son la abreviatura de «expresión regular»: estas funciones buscan apariciones de un patrón en un string y devuelven una lista de datos (potencialmente) útiles. Solo las mencionamos para ser exhaustivos: ¡hay mejores opciones!

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

Como sugiere el nombre, el patrón puede ser una regex, que se verá en un concepto posterior.

Advanced

¿Por qué estas viejas funciones de string se parecen a los comandos de shell de Unix?

Aunque R se creó en 1993, se diseñó para ser en gran medida compatible con versiones anteriores del 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).

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

Tidyverse

Siendo generosos, podríamos decir que las funciones de string al estilo antiguo no son lo mejor que R tiene para ofrecer (hay opiniones menos generosas).

Para mejorar la situación, la biblioteca stringr está 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 las funciones llevan el prefijo str_ y tratan de dejar claro su propósito.
  • El orden de los argumentos trata de ser coherente, con los datos de entrada como primer argumento.
  • Los argumentos pueden ir desde un solo string hasta estructuras de datos complejas. Las funciones tratan de adaptarse y hacer lo más sensato.

Como todos los paquetes de Tidyverse, stringr tiene buena documentación, normalmente escrita por quienes crearon el paquete.

En stringr hay docenas de funciones disponibles y a menudo tienen una variedad de argumentos opcionales. Al principio esto puede ser un poco abrumador, así que la documentación es tu mejor aliada.

Piensa en lo que quieres poner (¿un solo string? ¿un vector? ¿algo más complejo?) y en lo que quieres obtener.

Probablemente haya una manera fácil de hacer lo que quieras, siempre que encuentres la función correcta 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 que está a la izquierda del pipe se convierte en el primer argumento de la función que está 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 hacer el pipe hacia una llamada a función, no solo hacia un nombre, así que los paréntesis siempre son obligatorios (esto es distinto de algunos otros lenguajes).

El ejemplo de abajo ilustra la sintaxis. Las funciones individuales se verá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"
Manejar longitudes

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

Otras varias funciones manipulan los espacios en blanco, como str_trim() y str_squish(). A menudo esta es una tarea de limpieza fundamental 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 partes de strings

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 a la vez.

Hay mucha flexibilidad. start y end toman por defecto el primer y el último carácter, respectivamente. Pueden ser vectores de índices. A diferencia de la indexación de vectores (lo cual se agradece), 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 solo 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 solo vector de caracteres, con reciclaje cuando sea 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 trae a R una interpolación de strings sencilla. Las expresiones dentro de las llaves { } se interpretan y se sustituye su representación como texto.

Si se proporcionan varios strings de expresiones, también se unirán como en str_flatten. Además, de forma opcional se pueden recortar los espacios en blanco al principio o 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 una variedad aún mayor de posibilidades.

Dividir strings

Un uso muy común de R es convertir datos desordenados en un formato consistente para su análisis. Para ayudarte con esto, hay varias maneras de dividir texto en substrings.

str_split_1 es la más simple: convierte un solo string en un vector de partes, dividiéndolo 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 n-ésimo substring 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. En nuestro temario hay un concepto sobre listas 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 verán en un concepto posterior.

Coincidir strings

Para comparar el string completo, str_equal() puede usar varias definiciones de «igualdad». En el caso más simple, el valor predeterminado distingue entre mayúsculas y minúsculas, pero se puede cambiar. La configuración regional 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 verán con más detalle en el concepto sobre expresiones regulares.

Modificar strings

Algunos lenguajes (principalmente europeos) distinguen entre letras MAYÚSCULAS y minúsculas. Hay un conjunto de funciones de stringr para convertir entre ambas de formas apropiadas para la configuración regional:

  • str_to_lower()
  • str_to_upper()
  • str_to_title() : la primera letra de cada palabra a mayúscula
  • str_to_sentence() : la primera letra de la oración 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 reemplazar substrings según coincidencias de patrones, str_replace() cambia la primera aparición del patrón y str_replace_all() cambia todas las apariciones.

Para reemplazar substrings según índices 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 los elimina.

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 entradas duplicadas de un vector, pero la función de stringr también puede manejar la distinción entre mayúsculas y minúsculas y varias coincidencias específicas de la configuración regional.

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, que contiene funciones de más bajo nivel.

Si tienes necesidades específicas que no se hayan tratado antes, entonces:

  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ísimas más funcionalidades.
Editar en GitHub El enlace se abre en una ventana o pestaña nueva

Aprende Strings

La práctica está bloqueada

Desbloquea 1 ejercicio más para practicar Strings