Club Lectura R

Trabajando con expresiones regulares

rgc

dic 17, 2024

Repaso

Metacaracteres

Básicos:

. (punto): Coincide con cualquier carácter individual, excepto una nueva línea

^ (tilde circunflejo): Coincide con el inicio de una cadena.

$ (dólar): Coincide con el final de una cadena.

* (asterisco): Coincide con cero o más ocurrencias del carácter o grupo precedente.

+ (más): Coincide con una o más ocurrencias del carácter o grupo precedente.

? (interrogación): Coincide con cero o una ocurrencia del carácter o grupo precedente (lo hace opcional)

[] (corchetes): Define una clase de carácter. Coincide con cualquier carácter dentro de los corchetes.

() (paréntesis): Sirven para agrupar caracteres y crear subpatrones. También se usan para capturar grupos coincidentes para su posterior uso (captura de grupos).

| (barra vertical): Actúa como un operador OR (o lógico). Coincide con el patrón a la izquierda o el patrón a la derecha.

{} (llaves): Encierren uno o varios números.

Funciones en R

Base R: grep(), grepl(), regexpr(), gregexpr(), sub(), gsub(), regmatches().

librería stringr: str_detect(), str_extract(), str_extract_all(), str_locate(), str_replace(), str_replace_all(), str_split().

Existen otras librerías que trabajan con expresiones regulares como stringi la cual es muy versátil y rápida, sin embargo veremos stringr que es un wrapper de stringi pues viene por defecto en tidyverse eso nos permite explorar este paradigma junto a Rbase

Funciones en R

  • […]
color <- c("gris", "gray", "grey", "Grey", "Gray", "gary", "grey", "grey")


grepl("gr[a|e]y", color)
[1] FALSE  TRUE  TRUE FALSE FALSE FALSE  TRUE  TRUE
grepl("^g", color)
[1]  TRUE  TRUE  TRUE FALSE FALSE  TRUE  TRUE  TRUE
texto <- "El gato y el raton juegan."
gsub("gato", "perro", texto)
[1] "El perro y el raton juegan."
gsub("([gr])at(o)", "\\1om", texto)
[1] "El gom y el romn juegan."

Ejemplos en R

Identificar posiciones

archivos <- c(
    "informe_2023.pdf", "carta_cliente.docx",
    "informe_final.txt", "presentacion.pptx"
)


grep("informe", archivos) # Devuelve los índices
[1] 1 3
grepl("informe", archivos) # Devuelve TRUE/FALSE
[1]  TRUE FALSE  TRUE FALSE
library(stringr)
str_detect(archivos, "informe") # Devuelve TRUE/FALSE
[1]  TRUE FALSE  TRUE FALSE

Extraer patrones

anios_base <- regmatches(archivos, regexpr("[0-9]{4}", archivos))
anios_base[length(anios_base) == 0] <- NA
print(paste("Base R:", anios_base))
[1] "Base R: 2023"
anios_stringr <- str_extract(archivos, "[0-9]{4}")
print(paste("stringr:", anios_stringr))
[1] "stringr: 2023" "stringr: NA"   "stringr: NA"   "stringr: NA"  

Reemplazar un patrón

archivos_base_r <- gsub("informe", "reporte", archivos)
print(paste("Base R:", archivos_base_r))
[1] "Base R: reporte_2023.pdf"   "Base R: carta_cliente.docx"
[3] "Base R: reporte_final.txt"  "Base R: presentacion.pptx" 
archivos_stringr <- str_replace_all(archivos, "informe", "reporte")
print(paste("stringr:", archivos_stringr))
[1] "stringr: reporte_2023.pdf"   "stringr: carta_cliente.docx"
[3] "stringr: reporte_final.txt"  "stringr: presentacion.pptx" 

Dividir una cadena

cadena <- "nombre_apellido_edad"


partes_base <- strsplit(cadena, "_")[[1]]
print(paste("Base R:", partes_base))
[1] "Base R: nombre"   "Base R: apellido" "Base R: edad"    
partes_stringr <- str_split(cadena, "_", simplify = TRUE)
print(paste("stringr:", partes_stringr))
[1] "stringr: nombre"   "stringr: apellido" "stringr: edad"    

Extraer número de un texto

texto <- c(
    "Llama al 555-123-4567 o al (555) 987-6543. También puedes contactar al 555.555.5555",
    "Mi número es 1234567890 y el de mi trabajo es +1-555-111-2222"
)

patron_telefono <- "(\\+1-)?\\(?[0-9]{3}\\)?[-.\\s]?[0-9]{3}[-.\\s]?[0-9]{4}"

numeros_base <- regmatches(texto, gregexpr(patron_telefono, texto))
print(numeros_base)
[[1]]
[1] "555-123-4567" "555.555.5555"

[[2]]
[1] "1234567890"      "+1-555-111-2222"
library(stringr)

numeros_stringr <- str_extract_all(texto, "(\\+1-)?\\(?[0-9]{3}\\)?[-.\\s]?[0-9]{3}[-.\\s]?[0-9]{4}")
print(numeros_stringr)
[[1]]
[1] "555-123-4567"   "(555) 987-6543" "555.555.5555"  

[[2]]
[1] "1234567890"      "+1-555-111-2222"

Formatos de correos electrónicos

correos <- c("usuario@dominio.com", "otro.usuario@sub.dominio.net", "invalido@", "@invalido.com", "usuario@dominio.com.br")

patron_correo <- "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
valido_base <- grepl(patron_correo, correos)
print(valido_base)
[1]  TRUE  TRUE FALSE FALSE  TRUE
valido_stringr <- str_detect(correos, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")

print(valido_stringr)
[1]  TRUE  TRUE FALSE FALSE  TRUE

Ejemplo anterior con un data.frame

ejemplo_tabla <- as.data.frame(correos) |>
    dplyr::filter(str_detect(correos, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"))

print(ejemplo_tabla)
                       correos
1          usuario@dominio.com
2 otro.usuario@sub.dominio.net
3       usuario@dominio.com.br

Limpieza general de texto

textos_sucios <- c(
    "Texto con !@#$%^&*()_+=-`~[]\\{}|;':\",./<>?",
    "Otro texto con caracteres áéíóú"
)


textos_limpios_base <- gsub(
    "[^[:alnum:][:space:]]",
    "", textos_sucios
)

print(textos_limpios_base)
[1] "Texto con "                      "Otro texto con caracteres áéíóú"
textos_limpios_stringr <- str_replace_all(textos_sucios, "[^[:alnum:][:space:]]", "")
print(textos_limpios_stringr)
[1] "Texto con "                      "Otro texto con caracteres áéíóú"
textos_limpios_stringr2 <- str_replace_all(textos_sucios, "[[:punct:]]", "")
print(textos_limpios_stringr2)
[1] "Texto con $^+=`~|<>"             "Otro texto con caracteres áéíóú"

Grupos

frases <- c(
    "preparar la presentacion previamente",
    "predecir el futuro es imposible, ¿o no?", "preocupante"
)

patron_complejo <- "\\bpre[a-zA-Z]{3,}\\b" # \b para límites de palabra
coincidencias_base <- regmatches(
    frases,
    gregexpr(
        patron_complejo,
        frases
    )
)
print(coincidencias_base)
[[1]]
[1] "preparar"     "presentacion" "previamente" 

[[2]]
[1] "predecir"

[[3]]
[1] "preocupante"
coincidencias_stringr <- str_extract_all(frases, "\\bpre[a-zA-Z]{3,}\\b")
print(coincidencias_stringr)
[[1]]
[1] "preparar"     "presentacion" "previamente" 

[[2]]
[1] "predecir"

[[3]]
[1] "preocupante"

Codicioso (Greedy) vs. Perezoso (Lazy):

Codicioso

texto <- "<h1>Título</h1><p>Párrafo</p>"

regmatches(texto, gregexpr("<.*>", texto))
[[1]]
[1] "<h1>Título</h1><p>Párrafo</p>"
str_extract_all(texto, "<.*>")
[[1]]
[1] "<h1>Título</h1><p>Párrafo</p>"

Perezoso

regmatches(texto, gregexpr("<.*?>", texto))
[[1]]
[1] "<h1>"  "</h1>" "<p>"   "</p>" 
str_extract_all(texto, "<.*?>")
[[1]]
[1] "<h1>"  "</h1>" "<p>"   "</p>" 

Grupos Pasivos (?:…)

library(stringr)

nombres <- c(
    "Juan Pérez", "Sr. Pedro Gómez",
    "Sra. María López", "Ana Sánchez",
    "María del Carmen López", "Juan Pedro de la Rosa",
    "  Juan  Pérez", "Juan   ", "José Luís Sánchez"
)


nombres_formateados_stringr <- str_match(nombres, "(?:Sr\\.|Sra\\.)?\\s*([\\p{L}]+(?:\\s+[\\p{L}]+)*)\\s+([\\p{L}])")

nombres_formateados_stringr <- ifelse(!is.na(nombres_formateados_stringr[, 1]), paste(nombres_formateados_stringr[, 2], nombres_formateados_stringr[, 3]), NA)

print(nombres_formateados_stringr)
[1] "Juan P"             "Pedro G"            "María L"           
[4] "Ana S"              "María del Carmen L" "Juan Pedro de la R"
[7] "Juan P"             NA                   "José Luís S"       

Caso de uso

# Crear una tabla usando r base
nombres <- c(
    "Juan Pérez", "Sr. Pedro Gómez",
    "Sra. María López", "Ana Sánchez", "María del Carmen López",
    "Juan Pedro de la Rosa",
    "  Juan  Pérez", "Juan   ",
    "Xica Da Silva", "Sr. Mourinho Alves",
    "Sr. Juan", "Sra.  ", "  ",
    "JuanP", "Sr. Juan Pérez G",
    " Juan Pérez", " juan perez"
)

extraer_nombre_inicial_apellido <- function(nombre) {
    nombre_limpio <- gsub("^\\s*(Sr\\.|Sra\\.)?\\s*", "", nombre)

    partes <- strsplit(nombre_limpio, "\\s+")[[1]]

    if (length(partes) == 0) {
        return(c(NA, NA))
    }
    if (length(partes) == 1) {
        return(c(partes[1], NA))
    }

    primer_nombre <- paste0(
        toupper(substr(partes[1], 1, 1)),
        tolower(substr(partes[1], 2, nchar(partes[1])))
    )

    apellido <- partes[length(partes)]
    inicial_apellido <- toupper(substr(apellido, 1, 1))

    return(c(primer_nombre, inicial_apellido))
}

resultado <- t(sapply(nombres, extraer_nombre_inicial_apellido))

colnames(resultado) <- c("Primer Nombre", "Inicial Apellido")
print(resultado)
                       Primer Nombre Inicial Apellido
Juan Pérez             "Juan"        "P"             
Sr. Pedro Gómez        "Pedro"       "G"             
Sra. María López       "María"       "L"             
Ana Sánchez            "Ana"         "S"             
María del Carmen López "María"       "L"             
Juan Pedro de la Rosa  "Juan"        "R"             
  Juan  Pérez          "Juan"        "P"             
Juan                   "Juan"        NA              
Xica Da Silva          "Xica"        "S"             
Sr. Mourinho Alves     "Mourinho"    "A"             
Sr. Juan               "Juan"        NA              
Sra.                   NA            NA              
                       NA            NA              
JuanP                  "JuanP"       NA              
Sr. Juan Pérez G       "Juan"        "G"             
 Juan Pérez            "Juan"        "P"             
 juan perez            "Juan"        "P"