Rimador
Rimador
Esta semana los peques tenían que hacer una poesía para el cole, como veréis queridos lectores, los niños son una fuente inagotable de ideas incluso para programar con R.
Hace tiempo que abandoné mi parte artística así que, para compensar la oxidación mental se me ocurrió programar una sencilla aplicación que busque rimas y nos ayude a crear versos.
Idea del programa
La idea parece sencilla, se trata 1) de escribir una palabra, 2) que una función descomponga en sílabas esta palabra, 3) que otra función busque palabras con la misma terminación.
El resultado que buscamos será una aplicación en la que pongas una palabra y busque otras con similar terminación o rima, que nos ayude a finalizar el verso.
Manos a la obra.
Lemario
Lo primero que hice fue buscar un lemario, o tabla de palabras en español, algunos lenguajes como LaTeX tienen lemarios, pero encontré esta magnifica web en github: github.com/olea/lemarios .
En ella no solo proporcionan un lemario completo con mas de ochentamil palabras en español sino que además, tiene otro fichero con verbos conjugados que podríamos añadir al lemario para aumentar nuestra base de datos de palabras.
Descargamos estos ficheros a nuestro directorio de trabajo así:
# verbos conjugados en español
url <- "https://github.com/olea/lemarios/raw/master/verbos-espanol-conjugaciones.txt"
download.file(url,destfile = "./data/verbos-espanol-conjugaciones.txt")
# Lemario completo de términos en español
url <- "https://github.com/olea/lemarios/raw/master/lemario-general-del-espanol.txt"
download.file(url,destfile = "./data/lemario-general-del-espanol.txt")
Silabear
El lemario es una lista de palabras y será la base de datos principal, ahora debemos programar una función que cada palabra del fichero línea a línea y nos calcule las sílabas. Esto no me ha resultado tan fácil como pensaba, y el resultado no es perfecto, pero bueno ya lo mejoraremos en el futuro.
He encontrado dos librerías que tienen funciones de silabeo (separar en sílabas), la primera opción con el paquete hyphenatr
.
# Para instalar hyphenatr:
# devtools::install_github("hrbrmstr/hyphenatr")
library(hyphenatr)
#seleccionamos el diccionario de Español
switch_dict("es_ANY")
# Usamos la función hyphenate para silabear
hyphenate("alucinante",simplify="-")
## [1] "alu-ci-nan-te"
hyphenate("mordisco",simplify="-")
## [1] "mordis-co"
La función hyphenate()
no es en realidad para silabear, sino para cortar palabras al final del párrafo, es decir para separar en guiones. Y como puedes ver en los resultados anteriores no lo hace muy bien, dejando muchas palabras sin dividir. Por tanto descartamos esta opción.
Buscando soluciones encontré el paquete Korpus que tiene muchas funciones de análisis de textos. El resultado es mucho mejor, aunque no es perfecto. Os ruego que si conocéis algún paquete o función mejor para separar en sílabas lo digáis en los comentarios.
El funcionamiento en Korpus
es similar, hay que cargar la librería y después el idioma. La función de silabeo es hyphen()
, que no busca exactamente silabear sino separar en guiones fin de línea como la anterior.
# Para instalar Korpus
## install.packages("koRpus")
library(koRpus)
# para instalar el lenguaje inglés y español:
##install.koRpus.lang(lang=c("en", "es"))
# Cargamos el idioma español
library(sylly.es)
# separa en silabas una palabra
# mostramos el objeto resultado de hyphen:
aux<-hyphen("montañoso", hyph.pattern="es",min.length=1)
##
|
| | 0%
|
|======================================================================| 100%
str(aux)
## Formal class 'kRp.hyphen' [package "sylly"] with 3 slots
## ..@ lang : chr "es"
## ..@ desc :List of 5
## .. ..$ num.syll : num 4
## .. ..$ syll.distrib : num [1:6, 1] 1 1 0 100 100 0
## .. .. ..- attr(*, "dimnames")=List of 2
## .. .. .. ..$ : chr [1:6] "num" "cum.sum" "cum.inv" "pct" ...
## .. .. .. ..$ : chr "4"
## .. ..$ syll.uniq.distrib: num [1:6, 1] 1 1 0 100 100 0
## .. .. ..- attr(*, "dimnames")=List of 2
## .. .. .. ..$ : chr [1:6] "num" "cum.sum" "cum.inv" "pct" ...
## .. .. .. ..$ : chr "4"
## .. ..$ avg.syll.word : num 4
## .. ..$ syll.per100 : num 400
## ..@ hyphen:'data.frame': 1 obs. of 2 variables:
## .. ..$ syll: num 4
## .. ..$ word: chr "mon-ta-ño-so"
# mostramos solo el data frame de silabas:
aux@hyphen
## syll word
## 1 4 mon-ta-ño-so
La función hyphen()
da el resultado en una clase de objeto especial, que contiene a su vez 3 propiedades (van precedidas de @
), estos son @lang
, @desc
y @hyphen
. El que nos interesa y que contiene las sílabas es el data.frame llamado @hyphen
.
A los objetos de una clase en R se accede con el símbolo @
de la misma forma que usamos $
para una columna de una data.frame.
Tabla de rimas
Como punto final vamos a calcular una tabla que contenga la base de datos de rimas que usaremos en la app. Para cada palabra del lemario calcularemos sus sílabas, le damos la vuelta para quedarnos solo con las 3 últimas y las almacenamos en la tabla.
Para añadir las opciones del rimadoR, además de la última sílaba consonante, también vamos a calcular la rima vocal, quitando las consonantes de las sílabas calculadas en el paso anterior.
Un punto interesante en el código que vas a ver abajo es que he evitado el uso de bucles. La programación con R va mucho más fluída y rápida si lo hacemos sin bucles (for … next), y para este caso con una tabla de 87.899 registros la función map()
del paquete purrr
es imprescindible puedes ver este articulo del blog sobre purrr y listas en R .
El siguiente código crea una tabla con las palabras, calcula y separa por sílabas, da la vuelta al vector de sílabas y por último almacena en 3 columnas llamadas c0, c1 y c2 las últimas 3 sílabas consonantes de la palabra, y como v0, v1 y v2 las 3 últimas silabas vocales de la misma palabra. Luego almacenamos la tabla en un fichero llamado bdsilabas.csv que después usaremos en la aplicación shiny.
# Cargamos la librería Korpus
library(koRpus)
library(sylly.es) # cargamos el idioma esp
library(tidyverse) # para usar purrr map
# Leemos el fichero de lemario que descargamos antes
# y almacenamos las palabras en un data frame.
file<-"./data/lemario-general-del-espanol.txt"
lemario<-readLines(file, encoding="UTF-8")
dt<-data.frame(palabra=lemario)
# nrow(dt)
# calculamos las sílabas de cada palabra
silabas<-(hyphen(dt$palabra, hyph.pattern="es",min.length=1))
# guardo solo la la data frame con las sílabas y el num de silabas
bd<-cbind(dt,silabas@hyphen)
rm(silabas) # borro la variable de clase para limpiar
# separo las silabas encontradas en un vector
list_aux<- strsplit(bd$word,"-")
# le damos la vuelta al vector para dejar primero
# las últimas silabas. strsplit da como resultado una lista
# por lo que usamos map() para aplicar a cada elemento
# de la lista la función rev()
list_aux <- map(list_aux, ~.x %>% rev())
# Almacenamos las 3 columnas de rima consonante
bd$c0<- map_chr(list_aux, ~.x[1])
bd$c1<- map_chr(list_aux, ~.x[2])
bd$c2<- map_chr(list_aux, ~.x[3])
# Quitamos la consonantes para tener la rima vocal
bd$v0<- gsub("[^aeiouáéíóú]","",bd$c0)
bd$v1<- gsub("[^aeiouáéíóú]","",bd$c1)
bd$v2<- gsub("[^aeiouáéíóú]","",bd$c2)
# veamos unas palabras de ejemplo
bd[sample(1:80000,10),]
#Guardamos la tabla calculada como bdsilabas.csv
write.table(bd,"bdsilabas.csv",
quote = FALSE, # Sin dobles comillas en cadenas
sep = ";", # Separador de campos
row.names = FALSE # No coge nombres de filas del DF
)
Crear aplicación shiny
Con el material base hecho ahora nos toca hacer la app dinámica para poder hacer las consultas.
Usaré como siempre flexdashboard. Consulta aquí la serie de articulos dedicados a felx en este blog
La app será sencilla, con una barra lateral en la que podremos meter la palabra inicial y las opciones de rima, y la parte central será el resultado como consulta directa de la tabla. Puedes ver esta imagen del aspecto final:
Como cosas interesantes he usado un observeEvent()
. Lo que hace es vigilar los cambios de un inputText
y si se producen hace que se actualice -en tiempo real- otro inputText
diferente con las silabas. Es decir según escribimos la palabra, va calculando las sílabas y las escribe en otro inputText
usando dentro del observeEvent
la función updateTextInput()
.
Para filtrar la tabla con las opciones de rima usamos un checkboxGroupInput
con varias opciones. Y para mostrar los resultados una DT con todo su dinamismo. Puedes ver un truco para que los botones del DT se vean en español.
Nuestra app se ejecuta en local con este código:
---
title: "RimadoR"
output:
flexdashboard::flex_dashboard:
theme: spacelab
orientation: columns
vertical_layout: fill
editor_options:
chunk_output_type: console
runtime: shiny
---
```r '{r setup, include=FALSE}
library(flexdashboard)
library(DT)
#library(rvest)
#library(dplyr)
library(stringr) # funciones de palabras
library(koRpus)
library(sylly.es)
```
<style> .datatables{ overflow: auto; font-size: 9pt} </style>
# Rimador by Ferran VilBer
```r '{r}
# lee la tabla de lemario con silabas
bd<-read.csv("bdsilabas.csv",header = T, sep = ";" )
```
## Entradas {.sidebar data-width=150}
```r '{r}
# Barra lateral de controles
## palabra input
textInput("palabra", "Busca rima de:", value = "gladiador") # limitamos el tamaño
# calcula las silabas de la palabra anterior
textInput("silabas", "silabas", value = "silaba")
#
pal <- reactive({
sila<-hyphen(str_to_lower(input$palabra), hyph.pattern="es",min.length=1)[1,2]
rev(unlist(strsplit(sila,"-")[1]))
})
# escribe la palabra separada de silabas al revés
renderText(pal())
# insertamos una linea de separacion
tags$hr(style="border-color: black;")
#cambia en tiempo real el silabeo
observeEvent(input$palabra, {
y <- input$palabra
if (is.null(y)) y <- "gladiador"
#chs <- if(y=="invalido") 0:54 else 0:74
sila<-hyphen(y, hyph.pattern="es",min.length=1)[1,2]
updateTextInput(session, "silabas", value = sila)
})
# opciones de rima
checkboxGroupInput("rimas", "Tipo de rima:",
choices = list("consonante final", "consonante anterior", "vocal final", "vocal anterior", "vocal 3"),
selected=list("consonante final", "vocal anterior"))
```
## Tabla de datos
```r '{r tablaa}
# pinta la tabla con los filtros aplicados
DT::renderDataTable({
filtro<-bd
if("consonante final" %in% input$rimas)
filtro <- filtro[filtro$c0==pal()[1],]
if("consonante anterior" %in% input$rimas)
filtro <- filtro[filtro$c1==pal()[2],]
if("vocal final" %in% input$rimas){
v0<- gsub("[^aeiouáéíóú]","",pal()[1])
filtro <- filtro[filtro$v0==v0,]
}
if("vocal anterior" %in% input$rimas){
v1<- gsub("[^aeiouáéíóú]","",pal()[2])
filtro <- filtro[filtro$v1==v1,]}
if("vocal 3" %in% input$rimas){
v2<- gsub("[^aeiouáéíóú]","",pal()[3])
filtro <- filtro[filtro$v2==v2,]}
DT::datatable(filtro, rownames = FALSE ,options = list(
bPaginate = TRUE,
pageLength = 20,
# para pasarlo a Español visto en: https://rstudio.github.io/DT/004-i18n.html
language = list(url = '//cdn.datatables.net/plug-ins/1.10.11/i18n/Spanish.json'),
order = list(list(1, 'asc'))
))
})
```