Home

Awesome

<h1 align="center">spanlp</h1> <p align="center"> <br> <i>spanlp es una librería escrita en Python para detectar, censurar y limpiar groserías, <br> vulgaridades, palabras de odio, racismo, xenofobia y bullying en textos escritos en <strong>Español</strong>. </i> <br> <p align="center"> <a href="https://test.pypi.org/project/spanlp/"> <img src="https://img.shields.io/badge/version-v1.1.0-green"/> </a> <a href="https://test.pypi.org/project/spanlp/"> <img src="https://img.shields.io/badge/status-stable-blue"/> </a> <a href="https://test.pypi.org/project/spanlp/"> <img src="https://img.shields.io/badge/release-v1.1.0-brightgreen"/> </a> <a href="https://test.pypi.org/project/spanlp/"> <img src="https://img.shields.io/badge/test--pypi-v0.0.7-yellow"/> </a> <a href="https://test.pypi.org/project/spanlp/"> <img src="https://img.shields.io/badge/license-MIT-brightgreen"/> </a> </p> <p align="center"> <img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/jfreddypuentes/spanlp?style=social"> </p> </p> <hr>

Indice

<hr>

Sobre la librería

spanlp es una librería escrita en Python para detección de groserías, vulgaridades, palabras de odio, racismo, xenofobia y bullying en textos escritos en Español. Puedes usar la librería y aplicarla a palabras de cualquiera de los más de 20 paises de habla hispana.

Incluye:

  1. Argentina 🇦🇷
  2. Bolivia 🇧🇴
  3. Chile 🇨🇱
  4. Colombia 🇨🇴
  5. Costa Rica 🇨🇷
  6. Cuba 🇨🇺
  7. Ecuador 🇪🇨
  8. El Salvador 🇸🇻
  9. España 🇪🇸
  10. Guatemala 🇬🇹
  11. Guinea Ecuatorial 🇬🇶
  12. Honduras 🇭🇳
  13. México 🇲🇽
  14. Nicaragua 🇳🇮
  15. Panamá 🇵🇦
  16. Paraguay 🇵🇾
  17. Perú 🇵🇪
  18. Puerto Rico 🇵🇷
  19. República Dominicana 🇩🇴
  20. Uruguay 🇺🇾
  21. Venezuela 🇻🇪

Casos de uso

Status Desarrollo

FuncionalidadDesarrolloPruebasRelease
Soporte de tokens con númerosv0.0.5
Estrategias de limpieza de datosv0.0.5
Completar datasetv1.0.1
Hammingv1.0.2
Levensteinv1.1.0
Bag distance--
Sorensen-Dice coefficient--
Tversky index--
Overlap index--
Tanimoto distance--
Ampliación datasetsProgreso--
% de palabrotas en el texto---

Instalación

Para instalar la última versión use:

pip install spanlp

Para instalar una versión específica use (por ejemplo):

pip install spanlp==1.1.0

Funcionamiento

Los algoritmos y modulos se personalizan de forma dinámica y muy flexible. Veamos algunos usos.

Uso básico

Validar si una palabra o frase contiene o no una palabrota:

from spanlp.palabrota import Palabrota
palabrota = Palabrota()
print(palabrota.contains_palabrota("Hola huevon cómo está?"))
# salida: True
from spanlp.palabrota import Palabrota
palabrota = Palabrota()
print(palabrota.contains_palabrota("Hola a todos ¿cómo están?"))
# salida: False

Censurar una frase con los parámetros por defecto:

from spanlp.palabrota import Palabrota

palabrota = Palabrota()
print(palabrota.censor("Hola huevon cómo está?"))

# salida: Hola !$%#@! cómo está?

Censurar la misma frase, configurando carácteres propios

from spanlp.palabrota import Palabrota

palabrota = Palabrota(censor_char="*")
print(palabrota.censor("Hola huevon como está?"))

# salida: Hola ****** cómo está?

Censurar otra frase, configurando carácteres propios y país

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country

palabrota = Palabrota(censor_char="@", countries=[Country.COLOMBIA, Country.VENEZUELA])
print(palabrota.censor("Hola huevon marico cómo está?"))

# salida: Hola @@@@@@ @@@@@@ cómo está?

Censuremos la misma frase pero solo con el país de Venezuela

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country

palabrota = Palabrota(censor_char="@", countries=[Country.VENEZUELA])
print(palabrota.censor("Hola huevon marico cómo está?"))

# salida: Hola huevon @@@@@@ cómo está?

Censuremos la misma frase pero incluyendo "huevon" al vocabulario de Venezuela

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country

palabrota = Palabrota(censor_char="@", countries=[Country.VENEZUELA], include=["huevon"])
print(palabrota.censor("Hola huevon marico cómo está?"))

# salida: Hola @@@@@@ @@@@@@ cómo está?

Censuremos la misma frase incluyendo "huevon" al vocabulario de Venezuela y excluyendo "marico"

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country

palabrota = Palabrota(censor_char="@", countries=[Country.VENEZUELA], include=["huevon"], exclude=["marico"])
print(palabrota.censor("Hola huevon marico cómo está?"))

# salida: Hola huevon marico cómo está?

Censuremos la misma frase incluyendo ""Hola" y "huevon" al vocabulario de Venezuela y excluyendo "marico"

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country

palabrota = Palabrota(censor_char="-", countries=[Country.VENEZUELA], include=["huevon"], exclude=["marico"])
print(palabrota.censor("Hola huevon marico cómo está?"))

# salida: ---- ---- marico cómo está?

Uso Avanzado

El uso avanzado incluye usar metricas de distancia y similitud para encontrar, comparar, censurar palabras. Estas son las metricas usadas a la fecha:

  1. ES-Indice de Jaccard (EN-Jaccard Index)
  2. ES-Similitud del coseno (EN-Cosine Similarity)
  3. ES-Levenshtein(EN-Levenshtein)
  4. ES-Damerau-Levenshtein(EN-Damerau-Levenshtein)

Censuremos la frase usando Cosine Similarity

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country
from spanlp.domain.strategies import CosineSimilarity

palabrota = Palabrota(censor_char="*", countries=[Country.VENEZUELA], distance_metric=CosineSimilarity())
print(palabrota.censor("Hola huevo maric cómo está?"))

# salida: Hola huevo ***** cómo está?

A pesar de que "maric" no está en el dataset, al algoritmo la censuró dado que por la métrica de distancia es muy similar.

Censuremos la frase usando Cosine Similarity manipulando los parámetros

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country
from spanlp.domain.strategies import CosineSimilarity

# Indicamos que tenga en cuenta palabras similares en al menos 30% y que normalice los datos (poner en minusculas, remover acentos)
cosine = CosineSimilarity(0.9, normalize=True) 
palabrota = Palabrota(censor_char="*", countries=[Country.VENEZUELA], distance_metric=cosine)
print(palabrota.censor("Hola huevon MARIC cómo está?"))

# salida: hola huevon ***** **** esta? => Censuró "como" porque en el dataset está "cono" y son similares en más del 90%

Censuremos la frase usando JaccardIndex con los parámetros por defecto

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country
from spanlp.domain.strategies import JaccardIndex

palabrota = Palabrota(censor_char="*", countries=[Country.VENEZUELA], distance_metric=JaccardIndex())
print(palabrota.censor("Hola huevo maric cómo está?"))

# salida: Hola huevo ***** cómo está?

El indice de Jaccard usa por defecto los siguientes parámetros:

Censuremos la frase usando JaccardIndex y modifiquemos los parámetros

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country
from spanlp.domain.strategies import JaccardIndex

jaccard = JaccardIndex(threshold=0.9, normalize=True, n_gram=1)
palabrota = Palabrota(censor_char="*", countries=[Country.VENEZUELA], distance_metric=jaccard)
print(palabrota.censor("Hola huevon marica cómo vamos?"))

# salida: hola huevon ****** **** vamos?

Ahora, sin normalizar los datos

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country
from spanlp.domain.strategies import JaccardIndex

jaccard = JaccardIndex(threshold=0.9, normalize=False, n_gram=1)
palabrota = Palabrota(censor_char="*", countries=[Country.VENEZUELA], distance_metric=jaccard)
print(palabrota.censor("Hola huevon marica cómo vamos?"))

# salida: Hola huevon ****** cómo vamos? => "moco" y "cómo" ahora son muy diferentes.

Ahora, bajemos el threshold a 0.6

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country
from spanlp.domain.strategies import JaccardIndex

jaccard = JaccardIndex(threshold=0.6, normalize=False, n_gram=1)
palabrota = Palabrota(censor_char="*", countries=[Country.VENEZUELA], distance_metric=jaccard)
print(palabrota.censor("Hola huevon marica cómo vamos?"))

# salida: Hola ****** ****** cómo vamos? => Ha censurado una palabra más.

Aumentemos la cantidad de n_gram a 3 con el threshold=0.7 y con normalize=False

from spanlp.palabrota import Palabrota
from spanlp.domain.countries import Country
from spanlp.domain.strategies import JaccardIndex

jaccard = JaccardIndex(threshold=0.7, normalize=False, n_gram=3)
palabrota = Palabrota(censor_char="*", countries=[Country.VENEZUELA], distance_metric=jaccard)
print(palabrota.censor("Hola huevon marica cómo vamos?"))

# salida: Hola huevon marica cómo vamos? => No censuró nada.

Preprocesamiento de texto

Siempre será necesario limpiar los datos antes de empezar a trabajar. Aqui te presento la clase Preprocessing y las 28 estratégias de limpieza de datos.

¿Cuantas estrategias hay y cuales son?

Son 28 algoritmos y son:

  1. TextToLower input: "HOLA QUE MÁS?" output: "hola que más?"
  2. TextToUpper input: "hola ¿cómo están?" output: "HOLA ¿CÓMO ESTÁN?"
  3. RemoveUnicodeCharacters input: "hola çcomo ªvan?" output: "hola como van" (Unicode characters)
  4. NumbersToVowelsInLowerCase input: "h0l4 qu3 t4l?" ouput: "hola que tal?"
  5. NumbersToVowelsInUpperCase input: "H0l4 c0m0 v4n?" output: "HOlA cOmO vAn?"
  6. NumbersToConsonantsInLowerCase input: "C0m0 e574n? 8i3n?" output: "Como estan? bien?"
  7. NumbersToConsonantsInUpperCase input: "C0m0 e574n? 8i3n?" output: "COMO ESTAN? BIEN?"
  8. RemoveExtraSpaces input: "Hola como están? " output: "Hola como están?"
  9. RemoveUserMentions input: "Hola @channel como van?" output: "Hola como van?"
  10. RemoveUrls input: "Revisen este recurso https://github.com/jfreddypuentes/spanlp" output: "Revisen este recurso "
  11. RemoveHashtags input: "Hola #equipo bienvenidos" output: "Hola bienvenidos"
  12. RemoveTicks input: "Hola, que' más'" output: "Hola, que más"
  13. RemoveBackTicks input: "Hola, que más" output: "Hola, que más"
  14. RemovePunctuation input: "Mensaje...,con, puntos" output: "Mensaje con puntos"
  15. RemoveNumbers input: "Hay 12 patacones y 20 yucas" output: "Hay patacones y yucas"
  16. RemoveAccents input: "La canción es una sensación" output: "La cancion es una sensacion"
  17. RemoveStopWords input: "La canción y la letra es buena" output: "canción letra buena"
  18. RemoveArticles input: "La canción y la letra es buena" output: "canción y letra es buena"
  19. RemoveEmoticons input: "Hola ;) como estás? XD" output: "Hola como estás?"
  20. RemovePronouns input: "Yo pienso que ella debería ser como él" output: "pienso que debería ser como"
  21. RemoveAdverbs input: "muchos años despues frente al peloton de fusilamiento lentamente recordaba..." output: "muchos años frente al peloton de fusilamiento recordaba..."
  22. RemoveConjunctions input: "y entonces estaba programando aunque con sueño pero concentrado creando esta libreria" output: "entonces estaba programando con sueño concentrado creando esta libreria"
  23. RemovePrepositions input: "ante todo es mejor cuidar a la naturaleza mediante buenas acciones. entre todos podemos." output: "todo es mejor cuidar la naturaleza buenas acciones. todos podemos."
  24. RemoveAdjectives input: "la voz era tenebrosa y la noche estaba fria y oscura hasta que de pronto algo luminoso apareció y" output: "la voz era y la noche estaba y hasta que de pronto algo apareció y"
  25. RemoveHtmlTags input: "Hola <strong>USUARIO</strong> que tal?" output: "Hola USUARIO que tal?"
  26. RemoveEmailAddress input: "Hola Pepito, el correo es contacto@domain.com" output: "Hola Pepito, el correo es "
  27. ExpandAbbreviations input: "pero xq tengo es3 si yo estaba bn en clase, ahora me duelen to2 los musculos" output: "pero por que tengo estres si yo estaba bien en clase, ahora me duelen todos los musculos"
  28. RemoveAbbreviations input: "xfa pongase el tapabocas pq me da es3 verlo sin eso. to2 debemos cuidarnos. chas gracias. salu2" output: "pongase el tapabocas me da verlo sin eso. debemos cuidarnos. gracias."

La nueva clase Preprocessing implementa de manera flexible y dinámica cualquier estrategia de limpieza de una manera muy simple. Se puede aplicar dentro de una metrica de distancia como JaccardIndex o CosineSimilarity para darle más poder a la busqueda, dismunir el riesgo de no encontrar las palabras a censurar y aumentar la posibilidad de censurar las palabras que son por el hecho de estar limpias.

Veamos algunos ejemplos:

from spanlp.domain.strategies import Preprocessing, TextToLower

strategies = [TextToLower()] # Defino mis estrategias de limpieza o pre-procesamiento
data = "ESTARE EN MINUSCULA" # Tengo mis datos
result = Preprocessing(data=data, clean_strategies=strategies).clean() # Invoco a Preprocessing
print(result)

# salida: estare en minuscula

Tambien se puede así:

from spanlp.domain.strategies import Preprocessing, TextToLower

strategies = [TextToLower()]
data = "ESTARE EN MINUSCULA" 
result = Preprocessing().clean(data=data, clean_strategies=strategies) # Envio los datos y las estragias al clean()
print(result)

# salida: estare en minuscula

Y tambien así:

from spanlp.domain.strategies import Preprocessing, TextToLower

strategies = [TextToLower()]
data = "ESTARE EN MINUSCULA"
preprocessor = Preprocessing(data=data, clean_strategies=strategies)
result = preprocessor.clean()
print(result)

# salida: estare en minuscula

Y así:

from spanlp.domain.strategies import Preprocessing, TextToLower

result = Preprocessing(data="ESTARE EN MINUSCULA", clean_strategies=[TextToLower()]).clean()
print(result)

# salida: estare en minuscula

¿Cómo aplico una estrategia para que pre-preprocese mis datos dentro de la métrica de distancia?

Aplicar la metrica de distancia con una estrategia de pre-procesado:

from spanlp.domain.strategies import JaccardIndex, TextToLower

strategies = [TextToLower()]
jaccard_index = JaccardIndex(normalize=True, clean_strategies=strategies)
result = jaccard_index.calculate("HOLA", "hola")

print(result)

# salida: 1

Usar la métrica como interface para ejecutar la estrategia:

from spanlp.domain.strategies import JaccardIndex, TextToLower

strategies = [TextToLower()]
jaccard_index = JaccardIndex(normalize=True, clean_strategies=strategies)
result = jaccard_index.normalize("HOLA ME VOY A NORMALIZAR A MINUSCULA")

print(result)

# salida: hola me voy a normalizar a minuscula

Usar varias estrategias de limpieza de datos Nota: Las estrategias se ejecutan en el orden enviado en la lista. Recomiendo analizar primero como desea que funcione: Si primero elimina espacios extras y despues elimina signos de puntuación y luego stop words etc.. esto dependerá de su necesidad concreta; Por lo que se pueden obtener diferentes resultados si cambia el orden de las estrategias.

Enviando varias estrategias para limpiar mis datos:

from spanlp.domain.strategies import Preprocessing, TextToLower, RemoveUserMentions, RemovePunctuation

strategies = [RemoveUserMentions(), TextToLower(), RemovePunctuation()]
tweet = "Hola @jhon, si viste que @freddy va a lanzar una nueva libreria Python para NLP?"
cleaned = Preprocessing().clean(data=tweet, clean_strategies=strategies)

print(cleaned)

#salida: hola  si viste que  va a lanzar una nueva libreria python para nlp

Limpiemos emoticones, pronombres y pasemos a minuscula

from spanlp.domain.strategies import Preprocessing, RemoveEmoticons, TextToLower, RemovePronouns

strategies = [RemoveEmoticons(), TextToLower(), RemovePronouns()]
message = "Segun ella Los emoticones <3 :) :D ;) son muy usados y esta rosa tambien @}->--"
cleaned = Preprocessing().clean(data=message, clean_strategies=strategies)

print(cleaned)

# salida: segun los emoticones son muy usados y esta rosa tambien

Testing

¿Eres tester? ¿quieres automatizar pruebas? o ¿simplemente aprender del open source y de las pruebas? Aventurate ya y ayudame a mejorar este proyecto! A continuación encontrarás algunas pautas para implementar pruebas exitosas que permitarán encontrar posibles errores y mejoras.

1. ¿Por donde empezar?

2. ¿Qué tipos de pruebas puedo realizar?

Excelente pregunta! no hay limite para la creatividad; Así que haz todas las pruebas que quieras e imagines. Sin embargo, aquí te dejo algunas pautas:

Sigue estos pasos:

  1. Crear una API Rest súper simple. Crea el siguiente script y llámalo server.py
pip install flask
from flask_cors import CORS, cross_origin
from flask import request
from flask import json

from spanlp.palabrota import Palabrota

app = Flask(__name__)
cors = CORS(app)

@app.route('/api/v1/nlp/text/censor', methods = ['POST'])
@cross_origin()
def censor():
    try:
        body = request.json     
        in_message = body['message']
        
        if in_message and len(str(in_message).strip()):
            palabrota = Palabrota()
            censored = palabrota.censor(in_message)
            response = {
                "success": True,
                "message": "OK",
                "error_code": 0,
                "data": {
                    'message': in_message,
                    'censored': censored
                }
            }
            return response
        else:
            return {
                "success": False,
                "message": "Bad Request - message is required",
                "error_code": 400,
                "data": {}
            }
    except Exception as e:
        return {
            "success": False,
            "message": "Internal Server Error - "+str(e),
            "error_code": 500,
            "data": {}
        }


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080, debug=True)
python server.py

y listo!!

  1. Consume la API desde algún cliente http como Postman, Insomnia y/o SOAP UI. Tambien puedes usar alguna herramienta como Apache JMeter; o tambien puedes crear un script python con muchos hilos que consuma la API.

3. ¿quieres escibrir unit tests y ejecutarlo de forma automática?

Clona el repositorio en tu máquina, abre el proyecto en tu editor favorito, ve al archivo /spanlp/tests/test_palabrota.py y empieza a escribir tus propios tests unitarios. Escribe cuantos quieras. Una vez tengas los tests listos, ejecuta el siguiente comando en la raiz del proyecto (/spanlp/):

pytest -ra

o tambien el comando:

python -m pytest

Esto ejecutará de forma automática todas las pruebas programadas e indicará si algún test falló.

Reportar un bug

Si encuentras algún problema (por muy mínimo que sea) reportalo aquí. Solo necesitarás poner título y describir la falla y aportará un montón a que este proyecto mejore su calidad.

Contacto

¿Alguna duda? escribeme por email: jfredypuentes@gmail.com

Cuentame en (@jfreddypuentes) ¿qué te parece está librería? ¿cómo la estás usando? ¿Qué le mejorarías?

<br>

Contribuidorxs

<br>

Hecho con ❤️️ de Colombia para el mundo.