Mostrando entradas con la etiqueta Pygal. Mostrar todas las entradas
Mostrando entradas con la etiqueta Pygal. Mostrar todas las entradas

viernes, 3 de mayo de 2024

Descifrador de códigos.

 Basado en el proyecto de la Raspberripy Org "Codebreakers".

¿Qué es lo que haremos en este post?


Analizaremos un gráfico para intentar descifrar un código, mientras aprendemos sobre listas y funciones en Python.

Que haremos:

- Definir una función que acepte una serie de parámetros y devuelva un valor.
- Usar los bucles "for" y "while" para repetir tareas.
- Crear un gráfico para mostrar la frecuencia de unos datos.

El programa que crearemos puede codificar o descodificar un mensaje secreto desde un archivo *.txt adjunto, o también podremos analizar la frecuencia de las letras en un mensaje. Estos datos se mostrarán en un gráfico de frecuencias, que mostrará con que frecuencia cada letra aparece en el mensaje secreto.

Nos basaremos en el cifrado Atbash. Este cifrado es uno de los más antiguos que se conocen en el mundo. Originariamente desarrollado en el idioma hebreo, utiliza el alfabeto y lo relaciona en orden inverso para crear el mensaje secreto. El nombre deriva de la primera, última, segunda y penúltima letras hebreas: Aleph, Taw, Bet y Shin.

Cifrando el alfabeto.


Para empezar vamos a crear un diccionario para tus letras cifradas.  El programa comenzará con dos estructuras de datos. La primera es una lista de todas las letras que existen en nuestro alfabeto y la segunda el un diccionario para el código. Para ahorrar teclear código, puedes crear la lista desde una cadena de texto usando la función list().

Empieza el programa con el siguiente código:

#Estructura de datos
#Lista creada desde una cadena de texto
ALFABETO = list(" abcdefghijklmnñopqrstuvwxyz ")
codigo = {}
La lista ALFABETO, como puedes ver, contiene un espacio al principio y al final de las letras. Esto lo hemos hecho para preservar los espacios que existan en el texto del mensaje. Sin embargo en una codificación fuerte esto no debería ser así, ya que hace que los mensajes sean más fáciles de decodificar. En este proyecto hemos mantenido los espacios para que que pueda ver más fácilmente el funcionamiento del programa.

Creando una nueva lista con el alfabeto invertido.


Necesitamos crear una nueva lista pero con el alfabeto, pero invertido. Puedes usar la funcion list() de nuevo para ayudarte con esto. Puedes usar la función reversed() para invertir el orden de una lista existente.

Crea la función crear_codigo. A continuación crea una lista que almacene el alfabeto pero al revés en una lista.

# Crear el código Atbash invirtiendo el alfabeto
def crear_codigo():
    inversa = list(reversed(ALFABETO))


Codificando el alfabeto.


"Codificar" es cuando conviertes datos de un formato a otro. Ahora tienes dos listas. Una contiene la lista del alfabeto en su orden normal y la otra en su orden inverso. Vamos a usar ambas para rellenar un diccionario. La llave almacenará almacenará el alfabeto en su orden normal y el valor correspondiente guardará el diccionario al revés.

El diccionario codigo es muy importante porque lo usaremos para hacer coincidir cada letra del mensaje usando las llaves y sus correspondientes valores.

Dentro de la función crear_codigo, rellena el diccionario usando los datos de ambas listas. Usa un bucle "for" para obtener la longitud de la lista del alfabeto y completar el diccionario con los datos. 

La función len() se usa para obtener la longitud de un objeto, como puede ser una lista. Aquí se usar para que el bucle "for" itere tantas veces como elementos tiene la lista "ALFABETO", es decir su longitud. 

def crear_codigo():
    # reversa del ALFABETO
    inversa = list(reversed(ALFABETO))
    
    for i in range(len(ALFABETO)): # Longitud de una lista
        codigo[ALFABETO[i]] = inversa[i]
        # Rellena el alfabeto con una letra y su correspondiente
        # letra códificada.

Ahora tenemos un problema a solventar. En el idioma español existen los acentos. Para usarlos en nuestro código tenemos que pasar las letras acentuadas como letras normales ya que si no no se procesarían al no estar en el diccionario. Para solventarlo añadimos el diccionario caracteres_especiales y luego añadimos sus elementos al diccionario codigo.

# Crear el código Atbash invirtiendo el alfabeto
def crear_codigo():
    # reversa del ALFABETO
    inversa = list(reversed(ALFABETO))
    
    for i in range(len(ALFABETO)): # Longitud de una lista
        codigo[ALFABETO[i]] = inversa[i]
        # Rellena el alfabeto con una letra y su correspondiente
        # letra códificada.
    
    # Lista de caracteres especiales y sus equivalentes en el alfabeto normal
    caracteres_especiales = {'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ú': 'u'}
    
    # Agregar caracteres especiales al diccionario de códigos
    codigo.update(caracteres_especiales)   

Crear una función de entrada "main.py" es útil para llamar a las funciones que se necesitan cuando comienza el programa. Creemos una función main.py que llame a la función crear_codigo. 

# Comienzo
def main():
    crear_codigo()

main()

Probar y depurar el código.


Para probar que el diccionario se ha creado correctamente, vamos a imprimir el diccionario completo. El programa hasta ahora tiene que verse de esta forma:

# Crear el código Atbash invirtiendo el alfabeto
def crear_codigo():
    # reversa del ALFABETO
    inversa = list(reversed(ALFABETO))
    
    for i in range(len(ALFABETO)): # Longitud de una lista
        codigo[ALFABETO[i]] = inversa[i]
        # Rellena el alfabeto con una letra y su correspondiente
        # letra códificada.

    # Lista de caracteres especiales y sus equivalentes en el alfabeto normal
    caracteres_especiales = {'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ú': 'u'}

    # Agregar caracteres especiales al diccionario de códigos
    codigo.update(caracteres_especiales)
    
    print(codigo)

#Estructura de datos
#Lista creada desde una cadena de texto
ALFABETO = list("abcdefghijklmnñopqrstuvwxyz")
codigo = {}

# Comienzo
def main():
    crear_codigo()

main()
Ejecuta el código para ver si el diccionario se ha creado correctamente. Deberías ver un patrón que empieza con la letra "a" emparejada con la letra "z" y la letra "b" emparejada con la letra "y" y así sucesivamente.

SALIDA:

{' ': ' ', 'a': 'z', 'b': 'y', 'c': 'x', 'd': 'w', 'e': 'v', 'f': 'u', 'g': 't',
 'h': 's', 'i': 'r', 'j': 'q', 'k': 'p', 'l': 'o', 'm': 'ñ', 'n': 'n', 'ñ': 'm',
 'o': 'l', 'p': 'k', 'q': 'j', 'r': 'i', 's': 'h', 't': 'g', 'u': 'f', 'v': 'e',
 'w': 'd', 'x': 'c', 'y': 'b', 'z': 'a', 'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o',
 'ú': 'u'}

Codificar un mensaje.


En este apartado crearemos una función que pueda tomar un texto, lo transforma y te lo devuelva cifrado. 

Empezaremos comentando el print que usamos para ver si funcionaba nuestro código.

def crear_codigo():
    # reversa del ALFABETO
    inversa = list(reversed(ALFABETO))
    
    for i in range(len(ALFABETO)): # Longitud de una lista
        codigo[ALFABETO[i]] = inversa[i]
        # Rellena el alfabeto con una letra y su correspondiente
        # letra códificada.

    # Lista de caracteres especiales y sus equivalentes en el alfabeto normal
    caracteres_especiales = {'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ú': 'u'}

    # Agregar caracteres especiales al diccionario de códigos
    codigo.update(caracteres_especiales)

    #print(codigo) 

Definiendo la función Atbash.

Vamos a crear una función para codificar un texto usando el cifrado Atbash. Empieza definiendo la función Atbash, con un parámetro llamado texto. Los parámetros te permiten pasar valores para que sean usados en las funciones. 

def atbash(texto):

Convirtiendo el texto a minúsculas.

Lo primero que necesita la función es convertir el texto que le pasemos a minúsculas. Necesitamos crear una nueva variable, la podemos llamar salida, para guardar el mensaje codificado.

def atbash(texto):
    """Codifica o decodifica una línea de texto.
    Atbash es símetrico.
    """
    texto = texto.lower() # Convierte el texto en minúsculas.
    salida = ""

Codificando el texto.

Lo próximo que haremos será codificar el texto que habremos pasado a la función. Utilizaremos un bucle for para iterar sobre cada letra del texto y convertirla en una letra codificada usando el diccionario codigo. Finalmente devolveremos el mensaje codificado.

def atbash(texto):
    """Codifica o decodifica una línea de texto.
    Atbash es símetrico.
    """
    texto = texto.lower() # Convierte el texto en minúsculas.
    salida = ""
    
    for letra in texto:
        if letra in codigo:
            salida += codigo[letra]
    
    return salida

Probar y depurar.

Ahora que tenemos la función vamos a probarla codificando algo de texto y ver que funciona. Localiza la función main() y añade una llamada a la función atbash(). Podemos pasar el texto "Codificar es fácil"

Si todo ha ido bien la salida del programa sería:

>>> %Run main.py
xlwrurxzi vh uaxro 

Creando un menú.


Ahora lo que haremos será crear un menú para que el usuario pueda elegir que es lo que quiere hacer. Empezaremos creando una función llamada "menu":

def menu():
    """Crea un menú basado en texto."""
Tu menú necesita un bucle que pregunte al usuario que es lo que quiere hacer hasta que introduzca una opción válida. Tenemos que definir una variable llamada "elección" que de momento no tendrá ningún valor.  Esto permitirá al bucle while ejecutar su primer bucle.

def menu():
    """Crea un menú basado en texto."""
    eleccion = "" # El bucle comienza con una elección no válida.

Usando un bucle while para obtener lo que teclee el usuario.

Lo que queremos es que el bucle se rompa solamente cuando se haya escogido una opción válida. Queremos que el bucle while se siga ejecutando mientras no se escoja una opción válida.

Puedes usar un bucle while para ejecutar un trozo de código mientras una condición se verdadera (True). En nuestro caso vamos a definir que mientras que el usuario no elija la letra "d" o la "f" el bucle se continuará ejecutando. Lo que pretendemos es que si se pulsa una "c" se codifique/decodifique un mensaje que habremos guardado en un archivo de texto y si pulsa una "f" se muestre un gráfico con la frecuencia de las letras en ese mensaje. 

def menu():
    """Crea un menú basado en texto."""
    eleccion = ""
    
    while elección != 'c' and eleccion != 'f':
        eleccion = input('''Por favor, introduce c to codificar/decodificar texto,
o f para realizar un análisis de frecuencias: ''')

Una vez que el usuario ha introducido una elección correcta, el bucle finaliza. A continuación crearemos una sentencia 'if' que ejecutará la función atbash si el usuario introduce una 'c'

Tu decidirás que sucederá cuando el usuario introduzca una 'f' en un paso posterior.

Debajo de la última línea (¡asegurándote de tener todavía una sangría!), escribe:

def menu():
    """Crea un menú basado en texto."""
    eleccion = ""
    
    while eleccion != 'c' and eleccion != 'f':
        eleccion = input('''Por favor, introduce c to codificar/decodificar texto,
o f para realizar un análisis de frecuencias: ''')
        
        if eleccion == 'c':
            print("Pasando tu mensaje a través del cifrado...")
            mensaje = "Mi mensaje secreto"
            codigo = atbash(mensaje)
            print(codigo)

Cambia la cadena que dice "Mi mensaje secreto" por el texto que tu quieras. Este texto será el que se codifique o decodifique.

Al final de la función main() teclea menu() para llamar a la función cuando se ejecute el programa:

# Comienzo
def main():
    crear_codigo()
    #print(atbash("Codificar es fácil"))
    menu()
Ejecuta el código. Teclea 'c' y presiona Enter para codificar tu mensaje.

SALIDA.

>>> %Run main.py
Por favor, introduce c to codificar/decodificar texto,
o f para realizar un análisis de frecuencias: 
c
Pasando tu mensaje a través del cifrado...
ñr ñvnhzqv hvxivgl
En el próximo paso usaremos la función atbash para codificar el contenido de un archivo de texto.

Codificar texto desde un archivo.


Cargar el texto desde un archivo es más eficiente que teclear o copiar un gran texto para pasárselo al programa. Vamos a crear la función obtener_texto(). Esta función tiene un argumento que es el nombre del archivo a cargar. Usamos este argumento para cargar el archivo y pasárselo a la variable texto, que una vez quitados los saltos de línea es devuelto por la función. El código quedaría tal que así:

def obtener_texto(nombre_archivo):
    """Obtiene y devuelve texto desde un archivo."""
    with open(nombre_archivo) as f:
        texto = f.read().replace('\n', " ") # tenemos que eliminar los saltos de línea.
        return texto
La función en el menú necesita codificar el mensaje secreto desde un archivo de texto. Reemplaza tu mensaje secreto con la función obtener_texto(). Así quedaría el código:

def menu():
    """Crea un menú basado en texto."""
    eleccion = ""
    
    while eleccion != 'c' and eleccion != 'f':
        eleccion = input('''Por favor, introduce c to codificar/decodificar texto,
o f para realizar un análisis de frecuencias: \n''')
        
        if eleccion == 'c':
            print("Pasando tu mensaje a través del cifrado...")
            mensaje = obtener_texto('datos.txt')
            codigo = atbash(mensaje)
            print(codigo)
Crea en el mismo directorio un archivo llamado datos.txt, edítalo e introduce en el tu propio mensaje secreto.

Ejecuta tu código para ver si se muestra el mensaje codificado después de pulsar la letra 'c' en el menú.

SALIDA.

>>> %Run main.py
Por favor, introduce c para codificar/decodificar texto,
o f para realizar un análisis de frecuencias: 
c
Pasando tu mensaje a través del cifrado...
vhgv vh fn ñvnhzqv wv kifvyz kziz evi hr vo xlwrurxzwli wv gvcglh ufnxrlnz xliivxgzñvngv

Decodificando el mensaje.


El cifrado atbash codifica el mensaje utilizando el reverso de las letras del alfabeto, como ya hemos visto. Esto significa que exactamente el mismo código que hemos utilizado para codificar el mensaje vale para decodificarlo. Puedes probarlo, usando el código encriptado. Copialo y pégalo en el archivo datos.txt y vuelve a ejecutar el código de nuevo.

>>> %Run main.py
Por favor, introduce c para codificar/decodificar texto,
o f para realizar un análisis de frecuencias: 
c
Pasando tu mensaje a través del cifrado...
este es un mensaje de prueba para ver si el codificador de textos funciona correctamente 

Se mostrará la versión descodificada, es decir tu mensaje original.


Crear un analizador de frecuencias.


En este paso vamos a crear una función que nos muestre la frecuencia con la que las letras del alfabeto aparecen en tu texto.

El análisis de frecuencias mide con que frecuencia algo aparece de esta forma podrás buscar patrones de ocurrencia en esos datos. Es posible descifrar cifrados monoalfabeticos (si conoces el idioma en el que se encuentra el mensaje) observando la frecuencia con la que aparece cada letra y comparándolo con las letras más utilizadas en ese idioma. Lo explicaremos en más detalle más adelante.

Empezaremos creando una función que tome tu texto y cuente el número de veces que cada letra del alfabeto aparece en él y luego convierta ese número de apariciones en un porcentaje para que puedas compararlo con la frecuencia de las letras en español.

Vamos al lio. Define una función llamada frecuencia que tome como argumento tu texto, luego convierta el mensaje en minúsculas y lo pase todo a una lista.

def frecuencia(texto):
    """Calcula la frecuencia de las letras en un texto"""
    texto = list(texto.lower())
Crea un diccionario llamado frec donde vamos a almacenar el número de veces que aparece cada letra en el texto. Luego recorreremos el texto a través de un bucle for y añadiremos al diccionario el número de veces que aparece cada letra. Para posteriormente calcular las frecuencias almacenamos el número total de palabras en la variable letras_totales.

def frecuencia(texto):
    """Calcula la frecuencia de las letras en un texto"""
    texto = list(texto.lower())
    
    frec = {}
    
    for letra in texto:
        frec[letra] = frec.get(letra, 0) + 1
Por ejemplo si la primera letra del texto fuese una 'a' el programa busca si existe una clave en el diccionario con la 'a'. Usamos el método get ya que si existe la clave en el diccionario nos devuelve su valor y si no es así, porque fuese la primera aparición, nos devolverá el número cero en vez de un error.
Cada vez que aparece la letra en el texto le sumamos uno a su valor.

Una vez que ya tengamos el número total de veces que cada letra aparece en el mensaje vamos a calcular su frecuencia  a través de un bucle. No queremos que se tengan en cuenta los espacios en blanco para el calculo de las frecuencias, así que añadimos el condicional if para añadir solo las letras. Añade un return a la función para que podamos usar el diccionario con las frecuencias en cualquier parte del código.

def frecuencia(texto):
    """Calcula la frecuencia de las letras en un texto"""
    texto = list(texto.lower())
    
    frec = {}
        
    for letra in texto:
        if letra != " ":
            frec[letra] = frec.get(letra, 0) + 1
    
    letras_totales = sum(frec.values()) #numero total de letras sin espacios en blanco
        
    for letra in frec:
        frec[letra] = frec[letra] / letras_totales * 100  # Convierte números en porcentajes.
    
    return frec

Extendiendo el menú para incluir la opción 'f'.


Ahora que tenemos una función que calcula la frecuencia de las letras en el mensaje, necesitamos un enlace para ejecutarla a través del menú. Hasta ahora el usuario solo podía escoger la opción 'c' para codificar o decodificar un mensaje. Si tecleaba la letra 'f' no ocurría nada. Vamos a añadir esta opción para mostrar un análisis de frecuencias con la función que hemos creado anteriormente.

Debajo del comando if que verifica si el usuario ha pulsado una 'c',  necesitamos añadir el comando elif para que el usuario pueda escoger la opción 'f' para realizar un análisis de frecuencias e imprimirlas.

Deja una línea en blanco después de la sentencia 'if ' y añade el siguiente código:

def menu():
    """Crea un menú basado en texto."""
    eleccion = ""
    
    while eleccion != 'c' and eleccion != 'f':
        eleccion = input('''Por favor, introduce c para codificar/decodificar texto,
o f para realizar un análisis de frecuencias: \n''')
        
        if eleccion == 'c':
            print("Pasando tu mensaje a través del cifrado...")
            # mensaje = "Mi mensaje secreto"
            mensaje = obtener_texto('datos.txt')
            codigo = atbash(mensaje)
            print(codigo)
            
        elif eleccion == 'f':
            mensaje = obtener_texto('datos.txt')
            codigo = atbash(mensaje)
            mensaje_frec = frecuencia(codigo)
            print(mensaje_frec)
Guarda y ejecuta el código. Presiona la tecla 'f' y deberías ver en la consola una relación de las frecuencias con la que las letras aparecen en tu mensaje ya codificado. Los valores que se muestren seguramente serán diferentes de los mostrados aquí.

>>> %Run main.py
Por favor, introduce c para codificar/decodificar texto,
o f para realizar un análisis de frecuencias: 
f
{'v': 18.91891891891892, 'h': 6.756756756756757, 'g': 6.756756756756757,
 'f': 4.054054054054054, 'n': 6.756756756756757, 'ñ': 2.7027027027027026,
 'z': 9.45945945945946, 'q': 1.3513513513513513, 'w': 5.405405405405405,
 'k': 2.7027027027027026, 'i': 8.108108108108109, 'y': 1.3513513513513513,
 'e': 1.3513513513513513, 'r': 5.405405405405405, 'o': 1.3513513513513513,
 'x': 6.756756756756757, 'l': 6.756756756756757, 'u': 2.7027027027027026,
 'c': 1.3513513513513513}


Analizando la frecuencia mediante un gráfico.


Lo primero que tenemos que hacer es instalar la librería Pygal para mostrar el gráfico de las frecuencias. 

pip3 install pygal

En todos los idiomas, cada letra de su alfabeto tiene una "personalidad" o un conjunto de rasgos cuando se usa en ese idioma. Una de las características que tiene una letra en cualquier idioma es la frecuencia con la que aparece. El análisis de frecuencia es el método para descifrar códigos observando la frecuencia con la que se utilizan las letras en el idioma del código y comparándolo con la frecuencia con la que aparecen letras codificadas en un mensaje. En español, la letra e es la letra más utilizada (aparece el 13,68% de las veces), seguida de la 'a' (el 12,53 %). La letra menos utilizada es la w.

En el menú no necesitaremos más la línea print(mensaje_frec) así que lo comentaremos para que se ignore cuando ejecutemos el código. 

 elif eleccion == 'f':
            mensaje = obtener_texto('datos.txt')
            codigo = atbash(mensaje)
            mensaje_frec = frecuencia(codigo)
            # print(mensaje_frec)

Crear el gráfico con las frecuencias.

Vamos a definir una nueva función llamada crear_grafico(). Esta función tendrá dos parámetros, texto y lenguaje. El gráfico será un gráfico de barras que tendrá por título "análisis de frecuencias" y las etiquetas para el eje-x serán las claves del diccionario. (a,b,c...y,z)

El diccionario de frecuencias lo pasaremos posteriormente a la función cuando se la llame a través del parámetro texto.

Primeramente después de haberlo instalado importamos al principio del programa el tipo de gráfico que vamos a crear.

from pygal import Bar

#...
def crear_grafico(texto, lenguaje):
    """Crea el gráfico de frecuencias"""
    grafico = Bar(width=800, height=400, title="Análisis de Frecuencias",
                  x_labels=list(lenguaje.keys()))

Etiquetaremos el gráfico usando la frecuencia con la que las letras aparecen en el mensaje codificado y las frecuencias conocidas de las letras en el idioma español. Estos datos los pasaremos a través de los parámetros texto y lenguaje. 

Para pasar la frecuencia con la que las letras aparecen en un texto genérico en el idioma español, crearemos una variable llamada "spanish" y a través de un diccionario pasaremos las frecuencias.

Tenemos que solucionar un problema. A partir de la versión 3.7 de Python los datos en un diccionario se guardan en el orden en el que se han introducido y aunque no fuera así para poder representar correctamente el gráfico tenemos que emparejar las frecuencias de las letras en el mensaje codificado, con las frecuencias de las letras en el idioma. Es decir si la primera letra de nuestro texto codificado es una 'e' y las segunda una "s" pues tendremos que ordenar todas alfabéticamente y hacer coincidir sus valores con los del diccionario del lenguaje, que están ordenados alfabéticamente.

Para ello creamos una lista llamada "valores" que recogerá las frecuencias de las letras del mensaje codificado pero ordenadas alfabéticamente desde la 'a' a la 'z'. A través de un bucle for recorremos las letras "a,b,c...z" y si la letra existe pasamos su valor a la lista y sino pues sustituimos su valor con un cero.

def crear_grafico(texto, lenguaje):
    """Crea el gráfico de frecuencias"""
    grafico = Bar(width=800, height=400, title="Análisis de Frecuencias",
                  x_labels=list(lenguaje.keys()))
    # Etiquetas de las frecuencias para el mensaje codificado.
    valores = []
    for i in lenguaje:
        valores.append(texto.get(i,0))
    grafico.add('Mensaje objetivo', valores)
    # Etiquetas de las frecuencias de las letras en el idioma español
    grafico.add('Lenguaje', list(lenguaje.values()))
    
    grafico.render_to_file('grafico.svg')


# Estructura de datos
# Lista creada desde una cadena de texto
ALFABETO = list(" abcdefghijklmnñopqrstuvwxyz ")
codigo = {}
spanish = {'a': 12.53, 'b': 1.42, 'c': 4.68, 'd': 5.86, 'e': 13.68, 'f': 0.69, 'g': 1.01,
           'h': 0.70, 'i': 6.25, 'j': 0.44, 'k': 0.02, 'l': 4.97, 'm': 3.15, 'n': 6.71,
           'ñ': 0.31, 'o': 8.68, 'p': 2.51, 'q': 0.88, 'r': 6.97, 's': 7.98, 't': 4.63,
           'u': 3.93, 'v': 0.90, 'w': 0.01, 'x': 0.22, 'y': 0.90, 'z': 0.52}

LLamando a la función que representa las frecuencias.

Busca la secuencia elif dentro de la función menu(). Añade una línea de código para utilizar las frecuencias del idioma español y otra para crear el gráfico.

elif eleccion == 'f':
            mensaje = obtener_texto('datos.txt')
            codigo = atbash(mensaje)
            mensaje_frec = frecuencia(codigo)
            # print(mensaje_frec)
            lang_freq = spanish  # Importamos la frecuencia de la letras en español
            # LLama a la función que crea el gráfico
            crear_grafico(mensaje_frec, lang_freq)

Ejecuta el código y puedes ver el archivo del gráfico usando cualquier navegador para abrirlo:

grafico de frecuencias



Analiza el gráfico de frecuencias. 

El gráfico que se ha producido muestra la frecuencia de letras en el idioma español, etiquetado como "Lenguaje". Puedes ver que la letra "e" es la letra más utilizada en el idioma español porque tiene la barra más alta para todos los valores de idioma.

El gráfico de frecuencias también lista la frecuencia de letras en tu mensaje codificado, etiquetado como "Mensaje objetivo". Para averiguar qué codificación se ha utilizado para este mensaje, puedes comparar las barras que muestran el idioma español con las barras en el mensaje codificado. La barra más alta en el texto del mensaje codificado probablemente será una "e". La segunda letra más alta probablemente será una "a", ya que es la siguiente letra más popular.

Los descifradores pueden utilizar la frecuencia de letras para determinar el tipo de codificación que se ha utilizado en el mensaje. Pueden utilizar el método de ensayo y error para predecir qué letra podría representar usando el gráfico como guía.

No obstante ten en cuenta que tu mensaje es muy pequeño lo que dificulta el descifrarlo mediante el análisis de frecuencias. Sin embargo todo se normaliza si codificas un texto lo suficientemente largo.

Puedes encontrar el código de este post en este enlace de Github.

sábado, 27 de abril de 2024

Campeones de Gráficos.

 Basado en el proyecto de la RapsberryPi Org "Charting Champions".

¿Qué es lo que haremos?

Descubriremos el poder de las listas en Python creando un gráfico interactivo de la medallas olímpicas conseguidas por los diferentes países.

Los juegos olímpicos empezaron en el año 1896: miles de atletas representaron a centenares de naciones de todo el mundo. Los juegos olímpicos modernos se inspiraron en las olimpiadas que se realizaban en Grecia en la antigüedad.

Lo que haremos:

- Usar listas para almacenar los datos.

- Crear un gráfico usando la librería Pygal.

- Cargar los datos haciendo que tu programa los lea desde un archivo.


Creando un gráfico.

Lo primero que haremos será instalar la librería que vamos a utilizar para crear el gráfico, la librería Pygal. Para presentar los gráficos por defecto se utiliza el formato SVG (escalable vector graphics), que incluso nos servirá para incluirlo en una página web.

Información: INSTALANDO PYGAL

En WINDOWS:

En la consola de comandos teclea lo siguiente y pulsa la tecla Enter.

pip install pygal

Espera que la instalación se complete y luego continua con el proyecto.

En un MAC:

En una ventana de terminal teclea lo siguiente y pulsa la tecla Enter.

pip3 install pygal

Espera que la instalación se complete y luego continua con el proyecto.

En LINUX, incluyendo el sistema operativo de la Rapsberry Pi.

En una ventana de terminal teclea lo siguiente y pulsa la tecla Enter.

pip3 install pygal

Espera que la instalación se complete y luego continua con el proyecto.
Para comenzar el proyecto importaremos algo de código de la librería pygal que utilizaremos para dibujar el gráfico. Empieza creando un nuevo archivo, por ejemplo main.py, y teclea el siguiente código:

from pygal import Bar

Después vamos a crear un gráfico de barras para mostrar los resultados. Añade el siguiente código y luego ejecútalo:

from pygal import Bar
import webbrowser

# Creando un gráfico
grafico = Bar(title="Medallas Olímpicas")
grafico.render_to_file('prueba.svg')
webbrowser.open('prueba.svg')

Si todo ha ido bien se abrirá tu navegador predeterminado y te mostrará la siguiente imagen:


grafico creado sin datos


Vamos a pararnos un momento para explicar el código. Aunque en teoría para renderizar el gráfico simplemente se podría usar grafico.render()  a la fecha de este post no lo he conseguido en la versión que uso de Debian o Ubuntu. No obtengo ningún error, pero tampoco se renderiza el gráfico. Así que he optado por pasar el gráfico renderizado a un archivo con grafico.renter_to_file('prueba.svg') y luego abrirlo con el navegador para visualizarlo. Para ello he importado la librería webbrowser y luego he utilizado el método open para abrir el grafico creado en el navegador. No obstante también se permite abrir directamente el grafico en el navegador usando grafico.render_in_browser(), si prefieres pero seguramente tendrás que instalar una librería dependiente (lxlm).

Otro inciso antes de seguir es que además de diagramas de barras también podemos usar muchos más tipos de gráficos como líneas, gráficos circulares, etc. Para más información consulta la documentación


Añadiendo algunos datos.


Para almacenar los datos vamos a utilizar las listas de Python. Puedes crearlas poniendo los datos separados por comas entre corchetes [ ]. 

Vamos a crear cuatro listas con datos para mostrar en nuestro gráfico.

Cada lista guardará el nombre de la nación y el número de medallas que ha ganado.

# Añadiendo datos
us = ['Estados Unidos', 2655]
gb = ['Gran Bretaña', 931]
fr = ['Francia', 772]
sp = ['España', 169]
Cuando guardas algo en una lista, cada uno de los elementos tiene un índice. Un índice es un número que nos dice la posición del elemento en la lista. Los índices comienzan en el 0 en vez de en el 1.

Puedes obtener cualquier elemento de una lista a partir de su índice. Por ejemplo, mi_lista[3] nos devolverá el cuarto elemento de esa lista, porque recuerda que el índice comienza en el cero. 

Utiliza los índices de tu lista y grafico.add() para mostrar los datos. El nombre del país que está en el primer elemento de la lista (indice 0) se utilizará como etiqueta de la categoría y la cantidad de medallas que es el segundo elemento de la lista (índice 1) determinará la altura de la barra.

# Añadiendo datos
us = ['Estados Unidos', 2655]
gb = ['Gran Bretaña', 931]
fr = ['Francia', 772]
sp = ['España', 169]

grafico.add(us[0], us[1])
grafico.add(gb[0], gb[1])
grafico.add(fr[0], fr[1])
grafico.add(sp[0], sp[1])

Ejecuta el código para ver el gráfico. 


gráfico creado con algunos datos

Si obtienes un mensaje de ERROR del tipo IndexError, es que tu código esta intentando obtener un valor desde un indice que no existe en la lista (por ejemplo us[2]). Para solucionarlo:

- comprueba cada línea grafico.add() y comprueba que solo estás utilizando los indices 0 y 1.

- comprueba las líneas donde creaste las listas. Verifica que cada lista tenga solo dos elementos, separados por una coma.

Ahora, introduciremos dos nuevos paises, añadiendo nuevas listas y luego las cargaremos en el gráfico usando grafico.add().

# Añadiendo datos
us = ['Estados Unidos', 2655]
gb = ['Gran Bretaña', 931]
fr = ['Francia', 772]
sp = ['España', 169]
ch = ['China', 634]
al = ['Alemania', 797]

grafico.add(us[0], us[1])
grafico.add(gb[0], gb[1])
grafico.add(fr[0], fr[1])
grafico.add(sp[0], sp[1])
grafico.add(ch[0], ch[1])
grafico.add(al[0], al[1])

Vuelve a ejecutar el código para actualizar el gráfico.


gráfico con más datos añadidos


Cargando los datos desde un archivo.


¡Nuestro gráfico tiene buena pinta! Pero, casi 150 naciones han competido en los juegos olimpico de verano. Para hacer un gráfico con todos, vamos a cargar sus datos desde un archivo lo que nos hará ahorrar un montón de tiempo en teclear datos.

Si vas a la carpeta del proyecto en Github encontrarás un archivo csv llamado medals.csv. Ábrelo y echa un vistazo a los datos que contiene.


imagen del archivo csv

Como ves en cada línea aparece el nombre del equipo, y el número de medallas que ha ganado hasta los últimos juegos olímpicos de verano de Tokio 2020. (Oro, plata, bronce y total de medallas)

Información: Archivos CSV
Los archivos CSV (Comma Separated Values) o archivos con valores separados
por comas, contienen datos en filas y columnas como en una tabla. Cada lí-
nea es una fila, en la que los datos (columnas) están separados por comas.

Pais, Abreviatura, Oro, Plata, Bronce, Total

United States,USA,1070,841,744,2655
Soviet Union,URS,395,319,296,1010
Great Britain,GBR,292,324,315,931
Germany,GER,239,267,291,797

Para hacer el gráfico tenemos que volcar la información de cada línea al programa que estamos haciendo, al igual que hicimos con las listas anteriormente.

Para ello vuelve al archivo donde tienes el código del programa y añade el siguiente código que nos permitirá cargar los datos del archivo en una variable, para ello utilizaremos "with open() as". Después mediante un bucle for imprimiremos los datos de cada línea.

El usar el bucle for hace que se repita el código. Así que cargaremos cientos de líneas con los equipos participantes y sus datos ¡con unas pocas líneas de código!

Añade el siguiente código.

with open('medals.csv', 'r') as lineas:
    for linea in lineas:
        print(linea)


¿Cómo se lee un archivo con Python?

Para leer un archivo de texto en Python debes abrir el archivo y leer su contenido. 

Cuando abras un archivo, utiliza los comandos with con as. Con esto te asegurarás de que una vez ejecutado el código que contiene, el archivo se cerrará automáticamente. El argumento dentro de open() en este caso 'medals.csv' es el nombre del archivo de texto que queremos abrir. 

Una vez que hemos cargado el archivo, tenemos dos opciones. La primera es volcarlo íntegramente en una variable y una segunda que es usar un bucle for para recorrer el archivo línea por línea.

La primera opción no la hemos usado en este caso pero sería algo como esto:

with open(nombre_archivo) as f:
  archivo_texto = f.read()
  # Hacer algo con el texto

La segunda opción es la que hemos usado nosotros para iterar sobre cada línea del código:

with open(nombre_archivo) as f:
  for línea_archivo in f:
    # hacer algo con la línea.

Si ejecutas ahora el código verás como cada línea tiene seis valores separados por comas.

United States,USA,1070,841,744,2655

Soviet Union,URS,395,319,296,1010

Great Britain,GBR,292,324,315,931

Germany,GER,239,267,291,797

France,FRA,231,256,285,772

People's Republic,of China,CHN,263,199,174,636

Italy,ITA,222,195,215,632

Australia,AUS,162,170,209,541

Hungary,HUN,182,156,177,515

Sweden,SWE,149,177,181,507

Japan,JPN,169,150,180,499

Cada cadena que imprime el bucle se compone de seis valores separados por comas. La función grafico.add() necesita cada una de esas piezas pero como entradas separadas. 

La función split() divide una cadena en una lista, al igual que las listas que hicimos al principio del post. Si usamos split(',') se creara una lista poniendo un nuevo elemento cada vez que encuentre una coma, es decir separa el texto en diferentes valores usando para ello la coma. 

Consejo: split() puede dividir una cadena en una lista usando como separador cualquier texto que se desee, no tiene porque ser una coma, puede ser un punto, una letra e incluso una palabra.

Modifica un poco el código para que recoja esto que hemos comentado:

with open('medals.csv', 'r') as lineas:
    for linea in lineas:
        dato = linea.split(',')
        print(dato)

Si lo ejecutas verás lo siguiente:

>>> %Run main.py
['United States', 'USA', '1070', '841', '744', '2655\n']
['Soviet Union', 'URS', '395', '319', '296', '1010\n']
['Great Britain', 'GBR', '292', '324', '315', '931\n']
['Germany', 'GER', '239', '267', '291', '797\n']
['France', 'FRA', '231', '256', '285', '772\n']
["People's Republic", 'of China', 'CHN', '263', '199', '174', '636\n']
['Italy', 'ITA', '222', '195', '215', '632\n']
['Australia', 'AUS', '162', '170', '209', '541\n']
['Hungary', 'HUN', '182', '156', '177', '515\n']
...

Como te habrás dado cuenta cada línea finaliza con "\n" al final. "\n" es normalmente invisible cuando se imprime un texto, por eso no lo has visto antes. Es un salto de línea, le dice al ordenador cuando imprime un texto, que se ha alcanzado el final de la línea.

Sin embargo como para hacer nuestro gráfico necesitamos el primer valor, el nombre del equipo, y el último, el número total de medallas, tenemos que quitar el salto de línea "\n". Para ello usaremos el método strip() que utilizado sin argumentos elimina los espacios en blanco y saltos de línea que pudiera haber tanto al principio como al final de la cadena.

El código sería de momento este:

with open('medals.csv', 'r') as lineas:
    for linea in lineas:
        linea = linea.strip()
        dato = linea.split(",")
        print(dato)

Salida:

>>> %Run main.py
['United States', 'USA', '1070', '841', '744', '2655']
['Soviet Union', 'URS', '395', '319', '296', '1010']
['Great Britain', 'GBR', '292', '324', '315', '931']
['Germany', 'GER', '239', '267', '291', '797']
['France', 'FRA', '231', '256', '285', '772']
["People's Republic", 'of China', 'CHN', '263', '199', '174', '636']
['Italy', 'ITA', '222', '195', '215', '632']
['Australia', 'AUS', '162', '170', '209', '541']

Antes de cargar los datos en el gráfico nos queda una cuestión. Todos los valores que nos pasa la cadena son de tipo string. Eso no nos importa para el nombre del equipo puesto que el primer argumento que le pasamos a grafico.add() es un texto para que lo use como etiqueta. Sin embargo para el número total de medallas necesitamos que el dato se un número entero. Podemos usar la función int() para pasar ese dato de tipo texto a número. 

Ten además en cuenta que el nombre del equipo es el primer elemento de la lista (indice 0)  y que el número total de medallas es el elemento seis (indice 5). Con esto ya podemos construir el grafico. El programa completo quedaría de la siguiente forma:

from pygal import Bar
import webbrowser

# Creando un gráfico
grafico = Bar(title="Medallas Olímpicas")

# Añadiendo datos
# us = ['Estados Unidos', 2655]
# gb = ['Gran Bretaña', 931]
# ch = ['China', 634]
# al = ['Alemania', 797]
# fr = ['Francia', 772]
# sp = ['España', 169]

# grafico.add(us[0], us[1])
# grafico.add(gb[0], gb[1])
# grafico.add(fr[0], fr[1])
# grafico.add(sp[0], sp[1])
# grafico.add(ch[0], ch[1])
# grafico.add(al[0], al[1])

with open('medals.csv', 'r') as lineas:
    for linea in lineas:
        linea = linea.strip()
        dato = linea.split(',')
        grafico.add(dato[0], int(dato[5]))
        
        
grafico.render_to_file('prueba.svg')
webbrowser.open('prueba.svg')

Ejecuta el código y mira como se crea el gráfico:


gráfico final del proyecto

¿Qué podemos hacer a mayores?

Para profundizar algo más en la forma de hacer gráficos, podemos cambiar la forma de representar los datos. 

Podríamos por ejemplo:

Crear un gráfico circular -  Para crear un gráfico circular en vez de un gráfico de barras solo tenemos que cambiar el código de importación, para usar Pie en vez de Bar. Una vez importada la creación o instancia del gráfico la haremos usando Pie.

gráfico circular




Puedes encontrar los enlaces a este proyecto en esta dirección de Github.