tacosdedatos

Cover image for Una nube de palabras con estilo
Antonio Feregrino
Antonio Feregrino

Posted on

Una nube de palabras con estilo

Así es, el mundo necesita otro tutorial de cómo hacer una nube de palabras...

Para la suerte del mundo, existe un paquete llamado wordcloud que hace todo el trabajo por nosotros. Lo interesante que haré en este post es enseñarte cómo hacer esa nube un poco más interesante y atractiva, algo que debes seriamente considerar si quieres llamar la atención de la gente.

En este caso, trataré de hacer una nube de palabras que luzca como el emoji de la banderita roja 🚩.

Para comenzar, usaré paquetes conocidos en la ciencia de datos: nltk, pandas, pillow, matplotlib, pero además hay instalar wordcloud y unidecode:

pip install -q wordcloud
pip install -q unidecode
Enter fullscreen mode Exit fullscreen mode
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import nltk
import string
import unidecode
import wordcloud
import PIL
import collections
import random
Enter fullscreen mode Exit fullscreen mode

Antes de continuar, hay que descargar un par de imagenes auxiliares para crear la nube:

  • Una máscara, que debe ser una imagen blanco y negro en donde la porcion obscura de la imagen es el espacio que usará la nube de palabras, mientras que la porcion blanca será el fondo.
  • Un fondo, que usaré como fondo de la imagen final, en este caso es el contorno del emoji de la bandera roja.

Aquí es en donde tu implementación podría variar de la mía, podrías tu usar otras imagenes.

Las mías las descargo de internet usando wget:

wget -O flag.jpg -q https://ik.imagekit.io/thatcsharpguy/other_sites/kaggle/flag.jpg?updatedAt=1647703191239
wget -O flag-definitive.jpg -q https://ik.imagekit.io/thatcsharpguy/other_sites/kaggle/flag-definitive.jpg?updatedAt=1647703191398
Enter fullscreen mode Exit fullscreen mode

Imágenes

wordcloud es tan flexible que permite trabajar directamente con texto directamente, sin embargo, los tweets no son cualquier tipo de texto, y es por eso que hay que preprocesarlo de forma especial. Voy a crear una función llamada tokenise que tome una cadena de texto y regresae una lista de tokens.

spanish_stopwords = nltk.corpus.stopwords.words('spanish')
punctuation = string.punctuation + "¿¡"

stopwords = set(spanish_stopwords + [c for c in punctuation] + ["mas", "si", "aqui", "ser", "...", "asi"])
tokeniser = nltk.tokenize.TweetTokenizer()

starts_with = set(["https://", "#", "@"])

def tokenise(text):
    tokens = []
    for token in tokeniser.tokenize(text):
        _token = unidecode.unidecode(token.lower())
        if (
            len(_token) > 1 and
            (not _token.isnumeric()) and
            (not any([ _token.startswith(start) for start in starts_with ])) and
            (_token not in stopwords)
        ):
            tokens.append(_token)
    return tokens
Enter fullscreen mode Exit fullscreen mode
tokenise("""¡PREPAREN LA BOTANA! 🤩

- Chucky y el Napoli en la Serie A. 
- F.A. Cup. 
- Bundesliga 
- Héctor Herrera y el Atlético. 
- Clásico Regio. 

#BitácoraRÉCORD 📆

Descarga RÉCORD Plus y obtén la mejor información en todo momento ▶️ https://bit.ly/3oA6dpi""")
Enter fullscreen mode Exit fullscreen mode

Y el resultado es:

['preparen',
 'botana',
 'chucky',
# ...
 'mejor',
 'informacion',
 'momento']
Enter fullscreen mode Exit fullscreen mode

Con la función tokenizadora lista, podemos cargar los tweets, convertirlos a tokens y llevar la cuenta usando Counter:

twitter = pd.read_csv("/kaggle/input/techtuiter/tech-twitter.csv")
word_count = collections.Counter()

for text in twitter["text"]:
    word_count.update(tokenise(text))
Enter fullscreen mode Exit fullscreen mode

Para ver cuáles son los tokens más comunes podemos usar most_common:

word_count.most_common(10)
Enter fullscreen mode Exit fullscreen mode
[('video', 1397),
 ('hoy', 1387),
 ('nuevo', 1234),
 ('hacer', 1087),
 ('dia', 1062),
 ('solo', 999),
 ('bien', 939),
 ('trabajo', 911),
 ('mejor', 910),
 ('curso', 909)]
Enter fullscreen mode Exit fullscreen mode

Ya con la cuenta de tokens, podemos crear nuestra nube usando WordCloud. Los argumentos que hay que pasarle al constructor son:

  • background_color=rgba(255, 255, 255, 0), es decir, transparente.
  • mode="RGBA", indica que queremos una imagen con transparencia.
  • max_words=2500, quiero que la nube use las 2500 palabras más frecuentes.
  • mask=flag_mask, este es tal vez el argumento más importante, y es que esta es la máscara que quiero aplicar

Para terminar, hay que llamar al método generate_from_frequencies pasando el Counter que creamos más arriba.

flag_mask = np.array(PIL.Image.open("flag.jpg"))

wc = wordcloud.WordCloud(
    background_color="rgba(255, 255, 255, 0)", mode="RGBA",
    max_words=2500, mask=flag_mask).generate_from_frequencies(word_count)
Enter fullscreen mode Exit fullscreen mode

Para mostrar la imagen podemos usar matplotlib.

plt.figure(figsize=(10,10))
plt.imshow(wc)
Enter fullscreen mode Exit fullscreen mode

Primera bandera colorida

Hasta ahora todo bien, pero los colores no combinan con la 🚩 que quiero representar. Para nuestra suerte, wordcloud también incluye un método para recolorear la nube de palabras recolor, este método recibe una función que retorne el color de la palabra. Como quiero distintos tonos de rojo, haré que mi función regrese un color en la representación HSL, con variaciones entre 20% y 60%.

def red_colors(word, font_size, position, orientation, random_state=None, **kwargs):
    return f"hsl(0, 100%, {random.randint(20,60)}%)"
Enter fullscreen mode Exit fullscreen mode
plt.figure(figsize=(10,10))
plt.imshow(wc.recolor(color_func=red_colors), interpolation='bilinear')
Enter fullscreen mode Exit fullscreen mode

Bandera con tonos rojos

Ya se ve más como una bandera, pero le falta el pequeño palito para ondearla. Así que hay que agregárselo con pillow.

Lo primero es que hay que recuperar las imagenes necesarias. Primero nuestra cloud usando el método to_image y leemos la otra imagen del disco con PIL.Image.open:

cloud = wc.to_image()
staff = PIL.Image.open("flag-definitive.jpg")
Enter fullscreen mode Exit fullscreen mode

Después usamos paste para "pegar" nuestra nube sobre la imagen con el contorno de la bandera:

staff.paste(cloud, (0, 0), cloud)

plt.figure(figsize=(10,10))
plt.imshow(staff, interpolation='bilinear')
Enter fullscreen mode Exit fullscreen mode

Imagen terminada

Por último guardamos la imagen con save y listo.

staff.save(f"red-flag.jpg", format='JPEG', quality='maximum')
Enter fullscreen mode Exit fullscreen mode

Como siempre, me encuentran en @io_exception en Twitter por si tienen alguna duda. El código para reproducir el ejemplo está en Kaggle junto con el dataset.

Discussion (0)