Source: naiveBayes.js

/*
 * @fileoverview Clase para realizar clasificación utilizando el algoritmo Naive Bayes.
 *
 * Generado como parte del proyecto CAIM+BAYES+CV K-FOLDS.
 * 
 * Basado en:
 * NBcClasser.php 
 * Tarea 4. Aprendizaje y clasificación de documentos
 * Métodos Probabilísticos para la Inteligencia Artificial
 * 
 * @author Fernando MM
 * @version 1.0
 * @date 2024-04-11
 * 
 * Dependencias:
 * - arffDataSet.js
 * - caimDiscretizer.js
 * 
 * Historial de cambios:
 * - 1.0 (2024-04-11): Creación de la librería.
 * 
 */
/**
* @class La clase NaiveBayes crea un modelo de predicción usando las probilidades del teorema de Bayes tradicional.
*/
class NaiveBayes {
    constructor() {
        /**
        * @type {Array}
        */
        this.classProbabilities = {}; // Almacena las probabilidades de cada clase.
        /**
        * @type {Array}
        */
        this.featureProbabilities = {}; // Almacenar las probabilidades de cada característica dada una clase.
    }
    /**
     * @description Regresa el nombre del modelo usado.
     * @returns {string}
    */
    getClassifierName(){
        return "Naive Bayes";
    }
    /**
     * @description Entrena el modelo con un conjunto de datos de características (X) y etiquetas de clase (y).
     * @param {Array} X X representa las características de las instancias.
     * @param {Array} y y representa las etiquetas de clase correspondientes.
    */
    train(X, y) {
        // Separar las instancias de entrenamiento por clase para calcular las probabilidades.
        const separated = y.reduce((acc, classValue, index) => {
            // Si no existe un arreglo para la clase actual, crearlo.
            if (!acc[classValue]) {
                acc[classValue] = [];
            }
            // Agregar la instancia actual al arreglo de la clase correspondiente.
            acc[classValue].push(X[index]);
            return acc;
        }, {});

        // Calcular la probabilidad de cada clase y la probabilidad de cada característica dado una clase.
        Object.keys(separated).forEach((classValue) => {
            // Probabilidad de la clase = número de instancias de la clase / número total de instancias.
            this.classProbabilities[classValue] = separated[classValue].length / y.length;
            // Reducir las instancias de cada clase para calcular la frecuencia absoluta de cada característica.
            this.featureProbabilities[classValue] = separated[classValue].reduce((acc, instance) => {
                instance.forEach((value, featureIndex) => {
                    if (!acc[featureIndex]) {
                        acc[featureIndex] = {};
                    }
                    if (!acc[featureIndex][value]) {
                        acc[featureIndex][value] = 1;
                    } else {
                        acc[featureIndex][value] += 1;
                    }
                });
                return acc;
            }, {});

            // Convertir conteos a probabilidades para cada característica.
            Object.keys(this.featureProbabilities[classValue]).forEach((featureIndex) => {
                const total = Object.values(this.featureProbabilities[classValue][featureIndex]).reduce((sum, count) => sum + count, 0);
                Object.keys(this.featureProbabilities[classValue][featureIndex]).forEach((value) => {
                    this.featureProbabilities[classValue][featureIndex][value] /= total;
                });
            });
        });
    }
    /**
     * @description Predice las clases de un nuevo conjunto de datos de características (X).
     * @param {Array} X Datos para predicción.
     * @returns {Array}
    */
    predict(X) {
        return X.map((instance) => this.predictInstance(instance));
    }
    /**
     * @description Método auxiliar para predecir la clase de una única instancia.
     * @param {Array} instance Datos para predicción.
     * @returns {Array}
    */
    predictInstance(instance) {
        const probabilities = Object.keys(this.classProbabilities).reduce((acc, classValue) => {
            let probability = this.classProbabilities[classValue];
            instance.forEach((value, featureIndex) => {
                if (this.featureProbabilities[classValue][featureIndex] && this.featureProbabilities[classValue][featureIndex][value]) {
                    probability *= this.featureProbabilities[classValue][featureIndex][value];
                } else {
                    // Manejar caso en que el valor de la característica no se haya visto durante el entrenamiento
                    probability *= 1e-6; // Pequeño valor para evitar multiplicar por cero
                }
            });
            acc[classValue] = probability;
            return acc;
        }, {});

        // Seleccionar la clase con la mayor probabilidad acumulada.
        return Object.keys(probabilities).reduce((acc, classValue) => {
            return probabilities[classValue] > probabilities[acc] ? classValue : acc;
        });
    }
}

/* EJEMPLO EN JAVASCRIPT
const nb = new NaiveBayes();

// X representa las características de las instancias
const X = [
    [1, 1],
    [1, 0],
    [0, 1],
    [0, 0]
];

// y representa las etiquetas de clase correspondientes
const y = [
    'yes',
    'yes',
    'no',
    'no'
];

// Entrenar el modelo con los datos proporcionados
nb.train(X, y);

// Ejemplo de datos para predicción:
const newInstances = [
    [1, 1],
    [0, 0]
];

// Realizar predicciones
const predictions = nb.predict(newInstances);
console.log("Predicciones:", predictions);
*/