All Downloads are FREE. Search and download functionalities are using the official Maven repository.

prerna.reactor.frame.r.PivotReactor Maven / Gradle / Ivy

The newest version!
package prerna.reactor.frame.r;

import java.util.List;
import java.util.Vector;

import prerna.algorithm.api.SemossDataType;
import prerna.ds.r.RDataTable;
import prerna.ds.r.RSyntaxHelper;
import prerna.sablecc2.om.GenRowStruct;
import prerna.sablecc2.om.PixelDataType;
import prerna.sablecc2.om.PixelOperationType;
import prerna.sablecc2.om.ReactorKeysEnum;
import prerna.sablecc2.om.execptions.SemossPixelException;
import prerna.sablecc2.om.nounmeta.NounMetadata;
import prerna.util.Utility;
import prerna.util.usertracking.AnalyticsTrackerHelper;
import prerna.util.usertracking.UserTrackerFactory;

public class PivotReactor extends AbstractRFrameReactor {

	/**
	 * This reactor pivots a column so that the unique values will be transformed into new headers
	 * The inputs to the reactor are: 
	 * 1) the column to pivot
	 * 2) the column to turn into values for the selected pivot column
	 * 3) the aggregate function
	 * 4) the other columns to maintain
	 * 5) the optional no value replace
	 */
	
	private static final String PIVOT_COLUMN_KEY = "pivotCol";
	private static final String VALUE_COLUMN_KEY = "valueCol";
	private static final String AGGREGATE_FUNCTION_KEY = "function";
	private static final String NA_REPLACE_KEY = "naReplace";
	
	public PivotReactor() {
		this.keysToGet = new String[] { PIVOT_COLUMN_KEY, VALUE_COLUMN_KEY, ReactorKeysEnum.MAINTAIN_COLUMNS.getKey(), AGGREGATE_FUNCTION_KEY, NA_REPLACE_KEY };
	}
	
	@Override
	public NounMetadata execute() {
		organizeKeys();
		// initialize the rJavaTranslator
		init();

		// get frame
		RDataTable frame = (RDataTable) getFrame();
		// get frame name
		String table = frame.getName();
		// get inputs
		// get the column to pivot
		String pivotCol = getColumnToPivot();
		// separate the column name from the frame name
		if (pivotCol.contains("__")) {
			pivotCol = pivotCol.split("__")[1];
		}
		
		//get the column to turn into values for the selected pivot column
		String valuesCol = getValuesCol(); 
		//separate the column name from the frame name
		if (valuesCol.contains("__")) {
			valuesCol = valuesCol.split("__")[1];
		} 
				
		// keep track of the columns to keep
		List colsToKeep = getKeepCols();
		// get the aggregate function if it exists; if it does not exist
		// it will be of length zero
		String aggregateFunction = getAggregateFunction();
		// makes the columns and converts them into rows
		// dcast(molten, formula = subject~ variable)
		// I need columns to keep and columns to pivot
		String newFrame = Utility.getRandomString(8);
		String keepString = "";
		int numColsToKeep = 0;
		if (colsToKeep != null) {
			numColsToKeep = colsToKeep.size();
		}
		boolean dropColumn = false;
		if (numColsToKeep > 0) {
			// with the portion of code to ignore if the user passes in the 
			// col to pivot or value to pivot in the selected columns
			// we need to account for this so we dont end the keepString with " + "
			keepString = ", formula = ";
			for (int colIndex = 0; colIndex < numColsToKeep; colIndex++) {
				String newKeepString = colsToKeep.get(colIndex);
				if(newKeepString.equals(pivotCol) || newKeepString.equals(valuesCol)) {
					continue;
				}
				keepString = keepString + newKeepString;
				if (colIndex + 1 < numColsToKeep) {
					keepString = keepString + " + ";
				}
			}

			// with the portion of code to ignore if the user passes in the 
			// col to pivot or value to pivot in the selected columns
			// we need to account for this so we dont end the keepString with " + "
			if(keepString.endsWith(" + ")) {
				keepString = keepString.substring(0, keepString.length() - 3);
			}
			keepString = keepString + " ~ " + pivotCol + ", value.var=\"" + valuesCol + "\"";
		} else {
			// this creates a new column .
			dropColumn = true;
			keepString = ", formula = .~" + pivotCol + ", value.var=\"" + valuesCol + "\""; ;
		}

		String aggregateString = "";
		if (aggregateFunction != null && aggregateFunction.length() > 0) {
			// check data type of values col 
			SemossDataType dataType = frame.getMetaData().getHeaderTypeAsEnum(table + "__" + valuesCol);
			if (!(dataType == SemossDataType.INT || dataType == SemossDataType.DOUBLE)) {
				NounMetadata noun = new NounMetadata("Unable to aggregate on non-numeric column :" + valuesCol, PixelDataType.CONST_STRING,
						PixelOperationType.ERROR);
				SemossPixelException exception = new SemossPixelException(noun);
				exception.setContinueThreadOfExecution(false);
				throw exception;
			}
			aggregateString = ", fun.aggregate = " + aggregateFunction + " , na.rm = TRUE";
		}
		
		//we need to make sure that the column we are pivoting on does not have values with dashes
        //we should replace any dashes with underscores
        String colScript = table + "$" + pivotCol;
        String cleanScript = colScript + "= gsub(" + "\"-\"" + "," + "\"_\"" + ", " + colScript + ");";
        this.rJavaTranslator.executeEmptyR(cleanScript);
		this.addExecutedCode(cleanScript);
		
		String script = newFrame + " <- dcast(" + table + keepString + aggregateString + ");";
		script += RSyntaxHelper.asDataTable(newFrame, newFrame);
		script += table + " <- " + newFrame + ";";
		if (dropColumn) {
			// drop the . column
			script += table + " <- " + table + "[,.:=NULL];";
		}
		this.rJavaTranslator.runR(script);
		this.addExecutedCode(script);
		frame.recreateMeta();

		// get the optional replace value for na values
		// for the pivoted column new values
		String naReplace = getNaReplace();
		if (naReplace != null) {
			StringBuilder rReplaceScript = new StringBuilder();
			String[] finalHeaders = frame.getColumnHeaders();

			// see if naReplace string is numeric
			if (!isNumeric(naReplace)) {
				naReplace = "\"" + naReplace + "\"";
			}

			rReplaceScript.append(table + "[is.na(" + table + ")] <- " + naReplace + "; ");
			this.rJavaTranslator.runR(rReplaceScript.toString());
			this.addExecutedCode(rReplaceScript.toString());
			rReplaceScript = new StringBuilder();

			for (String possibleHeader : finalHeaders) {
				if (!colsToKeep.contains(possibleHeader)) {
					// r script to replace a value within the new pivoted columns
					// f$New_Col[df$New_Col %like% \"NA\" | df$New_Col %like% \"NaN\"] <- NA;
					rReplaceScript.append(
							table + "$" + possibleHeader + "[" + table + "$" + possibleHeader + " %like% \"NA\" | "
									+ table + "$" + possibleHeader + " %like% \"NaN\"] <- " + naReplace + ";");
				}
			}

			this.rJavaTranslator.runR(rReplaceScript.toString());
			this.addExecutedCode(rReplaceScript.toString());
		}

		//clean up temp r variables
		StringBuilder cleanUpScript = new StringBuilder();
		cleanUpScript.append("rm(" + newFrame + ");");
		cleanUpScript.append("gc();");
		this.rJavaTranslator.runR(cleanUpScript.toString());
		this.addExecutedCode(cleanUpScript.toString());

		// NEW TRACKING
		UserTrackerFactory.getInstance().trackAnalyticsWidget(
				this.insight, 
				frame, 
				"Pivot", 
				AnalyticsTrackerHelper.getHashInputs(this.store, this.keysToGet));
		
		return new NounMetadata(frame, PixelDataType.FRAME, PixelOperationType.FRAME_DATA_CHANGE, PixelOperationType.FRAME_HEADERS_CHANGE);
	}
		
	//////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////
	///////////////////////// GET PIXEL INPUT ////////////////////////////
	//////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////
	
	//get column to pivot based on key "PIVOT_COLUMN_KEY"
	private String getColumnToPivot() {
		GenRowStruct pivotColInput = this.store.getNoun(PIVOT_COLUMN_KEY);
		if (pivotColInput != null) {
			String pivotCol = pivotColInput.getNoun(0).getValue().toString();
			return pivotCol;
		}
		throw new IllegalArgumentException("Need to define column to pivot");
	}
	
	//get column to turn into values based on key "VALUE_COLUMN_KEY"
	private String getValuesCol() {
		GenRowStruct valueColInput = this.store.getNoun(VALUE_COLUMN_KEY);
		if (valueColInput != null) {
			String valueCol = valueColInput.getNoun(0).getValue().toString();
			return valueCol;
		}
		throw new IllegalArgumentException("Need to define column to turn into values for the selected pivot column");
	}
	
	//get any additional columns to keep based on the key "MAINTAIN_COLUMNS_KEY"
	private List getKeepCols() {
		List colInputs = new Vector();
		GenRowStruct colGRS = this.store.getNoun(ReactorKeysEnum.MAINTAIN_COLUMNS.getKey());
		if (colGRS != null) {
			int size = colGRS.size();
			if (size > 0) {
				for (int i = 0; i < size; i++) {
					//get each individual column entry and clean 
					String column = colGRS.get(i).toString();
					if (column.contains("__")) {
						column = column.split("__")[1];
					}
					colInputs.add(column);
				}
				return colInputs;
			}
		}
		return null;
	}
	
	//aggregate function is optional, uses key "AGGREGATE_FUNCTION_KEY"
	private String getAggregateFunction() {
		GenRowStruct functionInput = this.store.getNoun(AGGREGATE_FUNCTION_KEY);
		if (functionInput != null) {
			String function = functionInput.getNoun(0).getValue().toString();
			return function;
		}
		//don't throw an error because this input is optional
		return "";
	}
	
	// NA Replace is optional, uses key "NA_REPLACE_KEY"
	private String getNaReplace() {
		GenRowStruct naReplaceInput = this.store.getNoun(NA_REPLACE_KEY);
		if (naReplaceInput != null && !naReplaceInput.isEmpty()) {
			String naReplace = naReplaceInput.getNoun(0).getValue().toString();
			return naReplace;
		}
		//don't throw an error because this input is optional
		return null;
	}
	
	///////////////////////// KEYS /////////////////////////////////////
	
	@Override
	protected String getDescriptionForKey(String key) {
		if(key.equals(PIVOT_COLUMN_KEY)) {
			return "The column to pivot on";
		} else if(key.equals(VALUE_COLUMN_KEY)) {
			return "The column to turn into values for the selected pivot column";
		} else if (key.equals(AGGREGATE_FUNCTION_KEY)) {
			return "The function used to aggregate columns";
		} else if (key.equals(NA_REPLACE_KEY)) {
			return "The value used to replace n/a in a column";
		} else {
			return super.getDescriptionForKey(key);
		}
	}
	
	////////////////////// HELPER METHODS  //////////////////////////////////
	
	public static boolean isNumeric(String strNum) {
	    if (strNum == null) {
	        return false;
	    }
	    try {
	        Double.parseDouble(strNum);
	    } catch (NumberFormatException nfe) {
	        return false;
	    }
	    return true;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy