Revista Informática

Medir la similitud de archivos con Python

Publicado el 30 marzo 2020 por Daniel Rodríguez @analyticslane

Una de las grandes ventajas de los sistemas informáticos es la facilidad con la que se puede copiar y modificar los archivos. Cuando tenemos que repetir un análisis que ya hemos realizado previamente, sea este en una hoja de cálculo, un Jupyter Notebook o con cualquier otra herramienta, podemos partir de este y modificar adecuadamente los datos. Esto que nos no reinventar los procesos cada vez, facilita actividades no deseadas como puede ser la copia. Por eso vamos a ver como crear un método para medir la similitud de archivos con Python. Para lo que nos basaremos en la clase SequenceMatcher que hemos visto recientemente.

Medir la similitud de dos secuencias con SequenceMatcher

La clase SequenceMatcher que hemos visto reciéntemente permite crear objetos que identifican las subsecuencias comunes dentro de secuencias más grandes. Ofreciendo una métrica de la similitud entre ellas. Los archivos no son más que secuencias. Por ejemplo, los archivos Jupyter Notebook no son más que un documento con formato JSON. Lo que se puede comprobar al abrir un archivo de estos en cualquier editor texto.

Así, si importamos dos archivos podemos tener una idea de los similares que son simplemente con la línea

SequenceMatcher(None, file_A, file_A).ratio()

Lo que nos devolverá un valor entre 0 y 1. Indiciado 1 que ambos son el mismo archivo. Un valor que tiene en cuenta posible rotaciones del contenido de la secuencia.

Método para detectar documentos similares

Así podemos crear un método que nos permita identificar archivos que son similares. Método al que le indicaremos una ruta de búsqueda en la que localizara todos los archivos y los con la clase SequenceMatcher. Una vez terminada la comparación puede ordenar los archivos en base a los similares que son.

Una posible implementación es:

from glob import glob
from difflib import SequenceMatcher
from tqdm import tqdm

def get_similarity_in_folder(path='./', ext='*', progress=False):
    # Obtención de los archivos
    files = glob(path + ext)
    num_files = len(files)
    
    results =  dict()
    
    # Barra de progreso
    if progress:
        pbar = tqdm(total= num_files * (num_files - 1) / 2)
    
    for i in range(num_files-1):
        # Primer fichero
        file_i = open(files[i]).read()
        name_i = files[i].split('/')[2]
        
        for j in range(i+1, num_files):
            # Segudo fichero
            file_j = open(files[j]).read()
            name_j = files[j].split('/')[2]
            
            # Obtención de ratio
            results[(name_i, name_j)] = SequenceMatcher(None, file_i, file_j).ratio()
            
            # Actualiza barra de progreso
            if progress:
                pbar.update(1)
                    
    results = sorted(results.items(), key=lambda x: x[1], reverse=True)
    
    # Finaliza barra de progreso
    if progress:
        pbar.close()
    
    return results

En donde se ha utilizado glob para obtener el listado de archivos. Una vez obtenidos estos se puede compara cada par de archivos con dos bucles. Dado que el valor de la ratio es simétrico, es decir, la ratio de A y B es el mismo que el de B y A el segundo bucle no tiene porqué recorrer todos los archivos, solo los que no se han analizado previamente.

Los resultados se guardan en un diccionario, donde la clave es una tupla con los nombres de los dos archivos. Finalmente se ordenan con el método sorted() en orden inverso, de más a menos similares.

Como la tarea puede ser lenta se ha incluido la opción de una barra de progreso, utilizando el módulo tqdm visto anteriormente.

Comparativa

Para ver si este método se puede usar para detectar cuadernos de Jupyter con ligeras modificaciones hemos creado un cuaderno con 3 celdas. Hemos copiado el archivo y ejecutado las celdas. Posteriormente hemos rotado el orden de las celdas, el cual también hemos copiado y ejecutado el archivo. Por lo que tenemos cuatro documentos que son básicamente el mismo con ligeras modificaciones. El método nos ha dado los siguientes resultados:

Podemos ver que ejecutar el Notebook baja el valor de la ratio a 0,84 solo con cuatro celdas. Rotarlo lo baja aún más a 0,76. Rotarlo y ejecutarlo lo baja a 0,63. Hay que tener en cuenta que es un documento pequeño, por lo que en documentos más grandes posiblemente los valores sean mayores.

Conclusiones

Hemos visto un método para medir la similitud de archivos con Python que se puede utilizar para detectar si los cuadernos Jupyter son similares entre sí de una forma automática. Posiblemente el documento proceso pueda ser optimizado eliminado adecuadamente subcadenas que se pueden considerar "basura".

Imagen de quhl en Pixabay


Publicidad


Volver a la Portada de Logo Paperblog