/*
 * Copyright (C) 2008-2015 by Holger Arndt
 *
 * This file is part of the Universal Java Matrix Package (UJMP).
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * UJMP is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * UJMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with UJMP; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package org.ujmp.core.doublematrix.calculation.general.missingvalues;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.ujmp.core.Matrix;
import org.ujmp.core.doublematrix.DenseDoubleMatrix2D;
import org.ujmp.core.doublematrix.calculation.AbstractDoubleCalculation;
import org.ujmp.core.doublematrix.calculation.general.missingvalues.Impute.ImputationMethod;
import org.ujmp.core.util.MathUtil;
import org.ujmp.core.util.UJMPSettings;

public class ImputeEM extends AbstractDoubleCalculation {
	private static final long serialVersionUID = -1272010036598212696L;

	private Matrix bestGuess = null;

	private Matrix imputed = null;

	private double delta = 1e-6;

	private final double decay = 0.66;

	private File tempFile;

	public ImputeEM(Matrix matrix) throws IOException {
		this(matrix, null);
	}

	public ImputeEM(Matrix matrix, Matrix firstGuess) throws IOException {
		this(matrix, firstGuess, 1e-6, File.createTempFile(
				"ujmp-impute-em-" + System.currentTimeMillis(), ".csv"));
	}

	public ImputeEM(Matrix matrix, Matrix firstGuess, double delta, File tempFile) {
		super(matrix);
		this.bestGuess = firstGuess;
		this.delta = delta;
		this.tempFile = tempFile;
	}

	public double getDouble(long... coordinates) {
		if (imputed == null) {
			createMatrix();
		}
		double v = getSource().getAsDouble(coordinates);
		if (MathUtil.isNaNOrInfinite(v)) {
			return imputed.getAsDouble(coordinates);
		} else {
			return v;
		}
	}

	private void createMatrix() {
		try {
			ExecutorService executor = Executors.newFixedThreadPool(UJMPSettings.getInstance()
					.getNumberOfThreads());

			Matrix x = getSource();

			double valueCount = x.getValueCount();
			long missingCount = (long) x.countMissing(Ret.NEW, Matrix.ALL).getEuklideanValue();
			double percent = ((int) Math.round((missingCount * 1000.0 / valueCount))) / 10.0;
			System.out.println("missing values: " + missingCount + " (" + percent + "%)");
			System.out.println("============================================");

			if (bestGuess == null) {
				bestGuess = getSource().impute(Ret.NEW, ImputationMethod.RowMean);
			}

			int run = 0;
			double d;
			do {
				System.out.println("Iteration " + run++);

				List> futures = new ArrayList>();

				imputed = Matrix.Factory.zeros(x.getSize());

				long t0 = System.currentTimeMillis();

				for (long c = 0; c < x.getColumnCount(); c++) {
					if (containsMissingValues(c)) {
						futures.add(executor.submit(new PredictColumn(c)));
					}
				}

				for (Future f : futures) {
					Long completedCols = f.get();
					long elapsedTime = System.currentTimeMillis() - t0;
					long remainingCols = x.getColumnCount() - completedCols;
					double colsPerMillisecond = (double) (completedCols + 1) / (double) elapsedTime;
					long remainingTime = (long) (remainingCols / colsPerMillisecond / 1000.0);
					System.out.println((completedCols * 1000 / x.getColumnCount() / 10.0)
							+ "% completed (" + remainingTime + " seconds remaining)");
				}

				Matrix newBestGuess = bestGuess.times(decay).plus(imputed.times(1 - decay));

				for (int r = 0; r < getSource().getRowCount(); r++) {
					for (int c = 0; c < getSource().getColumnCount(); c++) {
						double value = getSource().getAsDouble(r, c);
						if (!MathUtil.isNaNOrInfinite(value)) {
							newBestGuess.setAsDouble(value, r, c);
						}
					}
				}

				d = newBestGuess.euklideanDistanceTo(bestGuess, true) / missingCount;
				System.out.println("delta: " + d);
				System.out.println("============================================");

				bestGuess = newBestGuess;
				bestGuess.exportTo().file(tempFile).asDenseCSV();

			} while (delta < d);

			executor.shutdown();

			imputed = bestGuess;

			if (imputed.containsMissingValues()) {
				throw new RuntimeException("Matrix has still missing values after imputation");
			}

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private boolean containsMissingValues(long c) {
		for (int r = 0; r < getSource().getRowCount(); r++) {
			if (MathUtil.isNaNOrInfinite(getSource().getAsDouble(r, c))) {
				return true;
			}
		}
		return false;
	}

	class PredictColumn implements Callable {

		long column = 0;

		public PredictColumn(long column) {
			this.column = column;
		}

		public Long call() throws Exception {
			Matrix newColumn = replaceInColumn(getSource(), bestGuess, column);
			synchronized (imputed) {
				for (int r = 0; r < newColumn.getRowCount(); r++) {
					imputed.setAsDouble(newColumn.getAsDouble(r, 0), r, column);
				}
			}
			return column;
		}

	}

	private static Matrix replaceInColumn(Matrix original, Matrix firstGuess, long column) {

		Matrix x = firstGuess.deleteColumns(Ret.NEW, column);
		Matrix y = original.selectColumns(Ret.NEW, column);

		List missingRows = new ArrayList();
		for (long i = y.getRowCount(); --i >= 0;) {
			double v = y.getAsDouble(i, 0);
			if (MathUtil.isNaNOrInfinite(v)) {
				missingRows.add(i);
			}
		}

		if (missingRows.isEmpty()) {
			return y;
		}

		Matrix xdel = x.deleteRows(Ret.NEW, missingRows);
		DenseDoubleMatrix2D bias1 = DenseDoubleMatrix2D.Factory.ones(xdel.getRowCount(), 1);
		Matrix xtrain = Matrix.Factory.horCat(xdel, bias1);
		Matrix ytrain = y.deleteRows(Ret.NEW, missingRows);

		Matrix xinv = xtrain.pinv();
		Matrix b = xinv.mtimes(ytrain);
		DenseDoubleMatrix2D bias2 = DenseDoubleMatrix2D.Factory.ones(x.getRowCount(), 1);
		Matrix yPredicted = Matrix.Factory.horCat(x, bias2).mtimes(b);

		// set non-missing values back to original values
		for (int row = 0; row < y.getRowCount(); row++) {
			double v = y.getAsDouble(row, 0);
			if (!Double.isNaN(v)) {
				yPredicted.setAsDouble(v, row, 0);
			}
		}

		return yPredicted;
	}

}

 

Ads help maintain this website.