UNPKG

nipals-pca

Version:

A NIPALS implementation of PCA that handles missing values

129 lines (87 loc) 3.52 kB
const { Matrix } = require('ml-matrix'); var fs = require('fs'); var Papa = require('papaparse'); class Dataset { constructor(matrix, transpose = false) { //set zero length string values to NaN to prevent missing values being parsed as zeroes matrix = matrix.map(r => r.map(v => v.length ? v : NaN)) this.matrix = new Matrix(matrix); if (transpose) { this.matrix = this.matrix.transpose(); } let n = this.matrix.rows; let k = this.matrix.columns; this.xmask = new Matrix(n, k) this.means = []; this.stdevs = []; for (let i = 0; i < n; i++) { this.xmask.setRow(i, this.matrix.getRow(i).map(v => isNaN(v) ? 0 : 1)); } this.missing = this.xmask.size != this.xmask.sum(); let mvCutoffPct = 0.5; let mvCutoff = mvCutoffPct * n; let columns = this.xmask.sum("column").map((numVals, i) => numVals > mvCutoff ? i : -1).filter(idx => idx >= 0); let rowMvCutoffPct = 0.2; let rowMvCutoff = rowMvCutoffPct * k; let rows = this.xmask.sum("row").map((numVals, i) => numVals > rowMvCutoff ? i : -1).filter(idx => idx >= 0); this.matrix = this.matrix.selection(rows, columns); this.xmask = this.xmask.selection(rows, columns); n = this.matrix.rows; k = this.matrix.columns; this.setZeroes(); for (let i = 0; i < k; i++) { let dataColumn = this.matrix.getColumn(i); let maskColumn = this.xmask.getColumn(i); let idxs = maskColumn.map((v, i) => { if (v) { return i; } }).filter(idx => idx != undefined); let values = idxs.map(i => dataColumn[i]); let nVals = values.length; let mean = values.reduce((sum, value) => sum + value, 0) / nVals; this.means.push(mean); const std = Math.sqrt(values.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / (nVals - 1)); this.stdevs.push(std); } this.matrix.subRowVector(this.means); this.matrix.divRowVector(this.stdevs); this.setZeroes() } setZeroes() { let n = this.xmask.rows; for (let row = 0; row < n; row++) { let maskRow = this.xmask.getRow(row); let nanIndices = maskRow.map((v, i) => { if (!v) { return i; } }).filter(idx => idx != undefined); nanIndices.map(nanIdx => this.matrix.set(row, nanIdx, 0)); } } static async parse(filePath, numObsIds, numVarIds, transpose = false) { let filePromise = this.parseExampleInput(filePath); let jsonMatrix = await filePromise; jsonMatrix.splice(0, numVarIds); jsonMatrix.map(r => r.splice(0, numObsIds)); return new Dataset(jsonMatrix, transpose); } static parseExampleInput(filePath) { let file = fs.readFileSync(filePath, 'utf8') return new Promise((resolve, reject) => { Papa.parse(file, { header: false, skipEmptyLines: true, complete(results, file) { let data = results.data; resolve(data) }, error(err, file) { reject(err) } }) }) } } module.exports.Dataset = Dataset;