/*
* @fileoverview Clase para discretizar todos los atributos numéricos y modificar los datos
* correspondientes de un conjunto de datos ARFF almacenados en una instancia de ARFFDataSet.
* Genera una copia de la instancia ARFFDataSet de entrada por lo que no modifica la instancia original.
*
* Generado como parte del proyecto CAIM+BAYES+CV K-FOLDS.
*
* @author Fernando MM
* @version 1.0
* @date 2024-04-10
*
* Dependencias:
* - arffDataSet.js
* - caimDiscretizer.js
*
* Historial de cambios:
* - 1.0 (2024-04-10): Creación de la librería.
*
*/
/**
* @class La clase DiscreteARFF discretiza los datos no discretizados del archivo ARFF original basádose en los rangos calculados con el algoritmo CAIM
*/
class DiscreteARFF {
constructor(arffData) {
this.arffData = arffData;
}
/**
* @description Genera un nuevo archivo ARFF con los nombres de los nuevos intervalos de los valores discretizados al crear la instancia del discretizador correspondiente a cada atributo
* @returns {Blob} Nuevo ARFF discretizado
*/
discretize() {
let discretizedData = new ARFFDataSet();
// Copia los datos de los atributos a la nueva instancia ARFFDataSet
discretizedData.attributes = this.arffData.attributes.map(attr => ({
...attr,
type: (attr.type === 'REAL' || attr.type === 'NUMERIC' || attr.type === 'INTEGER') ? 'NOMINAL' : attr.type,
values: (attr.type === 'REAL' || attr.type === 'NUMERIC' || attr.type === 'INTEGER') ? [] : attr.values
}));
discretizedData.data = this.arffData.data.map(row => [...row]);
discretizedData.labels = [...this.arffData.labels];
// Para cada columna (atributo) ...
this.arffData.attributes.forEach((attr, index) => {
if (attr.type === 'REAL' || attr.type === 'NUMERIC' || attr.type === 'INTEGER') {
// Crear la instancia del discretizador para cada atributo
let attributeData = this.arffData.data.map(row => parseFloat(row[index]));
let discretizer = new CAIMDiscretizer(attributeData, this.arffData.labels);
let cuts = discretizer.discretize();
// Genera los nombres de intervalos y almacena los cortes en la estructura de los atributos
let intervalNames = this.generateIntervalNames(cuts);
discretizedData.attributes[index].values = intervalNames;
discretizedData.attributes[index].cuts = cuts;
// Actualizar los datos con los nombres de intervalo
discretizedData.data.forEach(row => {
let value = row[index];
let intervalIndex = this.findIntervalIndex(cuts, value);
row[index] = intervalNames[intervalIndex];
});
}
});
return discretizedData;
}
/**
*
* @param {Array} cuts Arreglo de cortes que se realizarán a los datos para crear los nuevos intervalos según CAIM
* @returns {string} Lista de nombres de los intervalos
*/
generateIntervalNames(cuts) {
let names = [];
// Agregar el intervalo para valores menores que el primer corte
names.push(`x < ${cuts[0].toFixed(2)}`);
// Generar intervalos intermedios
for (let i = 0; i < cuts.length - 1; i++) {
const lowerBound = cuts[i];
const upperBound = cuts[i + 1];
if (i === cuts.length - 2) {
names.push(`${lowerBound.toFixed(2)} <= x <= ${upperBound.toFixed(2)}`);
} else {
names.push(`${lowerBound.toFixed(2)} <= x < ${upperBound.toFixed(2)}`);
}
}
// Agregar el intervalo para valores mayores que el último corte
names.push(`x > ${cuts[cuts.length - 1].toFixed(2)}`);
return names;
}
/**
*
* @param {Array} cuts Arreglo de cortes que se realizarán a los datos para crear los nuevos intervalos según CAIM
* @param {Number/String} value Valores de los atributos a discretizar
* @returns {number} La cantidad de cortes realizados
*/
findIntervalIndex(cuts, value) {
// Los valores podrían ser strings, deben convertirse a float
value = parseFloat(value);
// Si el valor es menor al índice, regresa el índice
for (let i = 0; i < cuts.length; i++) {
if (value < cuts[i]) {
return i;
}
}
// Si no, se asume que el valor es mayor que el máximo.
return cuts.length;
}
}
/* EJEMPLO JAVASCRIPT
var arffDataSet = new ARFFDataSet();
arffDataSet.readARFF(file, function (attributes, data, labels) {
let discretizer = new DiscreteARFF(arffDataSet);
let discretizedData = discretizer.discretize();
processData(discretizedData);
});
*/