package de.mpicbg.knime.scripting.r.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.knime.core.data.BooleanValue;
import org.knime.core.data.DataCell;
import org.knime.core.data.DataCellFactory;
import org.knime.core.data.DataColumnDomain;
import org.knime.core.data.DataColumnDomainCreator;
import org.knime.core.data.DataType;
import org.knime.core.data.DoubleValue;
import org.knime.core.data.IntValue;
import org.knime.core.data.StringValue;
import org.knime.core.data.def.BooleanCell;
import org.knime.core.data.def.BooleanCell.BooleanCellFactory;
import org.knime.core.data.def.DoubleCell;
import org.knime.core.data.def.DoubleCell.DoubleCellFactory;
import org.knime.core.data.def.IntCell;
import org.knime.core.data.def.IntCell.IntCellFactory;
import org.knime.core.data.def.StringCell.StringCellFactory;
import org.knime.core.data.def.StringCell;
import org.rosuda.REngine.REXP;
import org.rosuda.REngine.REXPDouble;
import org.rosuda.REngine.REXPFactor;
import org.rosuda.REngine.REXPInteger;
import org.rosuda.REngine.REXPLogical;
import org.rosuda.REngine.REXPMismatchException;
import org.rosuda.REngine.REXPString;
import de.mpicbg.knime.scripting.r.AbstractRScriptingNodeModel.RType;
/**
* <p>
* column model for R <-> KNIME transfer
* </p>
* <pre>
* General:
* - constructor, init data vector
* </pre>
* <pre>
* R >>> KNIME:
* - setLevels, add data, get REXP data, clear data
* </pre>
* <pre>
* KNIME >>> R:
* - setLevels, set bounds, add data, get KNIME data type, get KNIME domain, get KNIME cell
* </pre>
*
* @author Antje Janosch
*
*/
public class RDataColumn {
/** column index - zero based */
private int m_idx;
/** column name */
private String m_name;
/** column data type */
private RType m_type;
/** data vector */
private Object[] m_data;
/** list of missing value indicees - zero based*/
private HashSet<Integer> m_missingFlags = new HashSet<Integer>();
/** column levels(R)/domain values(KNIME) with index - zero-based */
private HashMap<Integer, String> m_levels = new HashMap<Integer, String>();
/** column upper and lower bound */
private double[] m_bounds = new double[2];
/**
* constructor
* @param name
* @param type
* @param idx
*/
public RDataColumn(String name, RType type, int idx){
this.m_name = name;
this.m_type = type;
this.m_idx = idx;
}
/**
* assign new vector with a given size and column type
* vector type corresponds to type expected by REXP
* @param size
*/
public void initDataVector(int size) {
switch(m_type) {
case R_LOGICAL:
m_data = new Byte[size];
break;
case R_INT:
m_data = new Integer[size];
break;
case R_DOUBLE:
m_data = new Double[size];
break;
case R_FACTOR:
m_data = new Integer[size];
break;
case R_STRING:
m_data = new String[size];
break;
default:
}
}
// GETTER / SETTER
// ============================================================
/**
* get data type
* @return
*/
public RType getType() {
return m_type;
}
/**
* get column index - zero based
* @return
*/
public int getIndex() {
return m_idx;
}
/**
* get column name
* @return
*/
public String getName() {
return m_name;
}
// KNIME >>> R
// ============================================================
/**
* add KNIME row value to data vector at a given row index
* also adds index to missing value indices if missing cekk
* @param cell
* @param rowIdx
*/
public void addData(DataCell cell, int rowIdx) {
if(cell.isMissing() && m_type == RType.R_STRING)
m_missingFlags.add(rowIdx);
switch(m_type) {
case R_LOGICAL:
((Byte[])m_data)[rowIdx] = getLogicalValue(cell);
break;
case R_INT:
((Integer[])m_data)[rowIdx] = getIntegerValue(cell);
break;
case R_DOUBLE:
((Double[])m_data)[rowIdx] = getDoubleValue(cell);
break;
case R_FACTOR:
((Integer[])m_data)[rowIdx] = getLevelIndex(cell);
break;
case R_STRING:
((String[])m_data)[rowIdx] = getStringValue(cell);
break;
default:
}
}
/**
* @param cell
* @return one-based integer representing the level-based string
*/
private Integer getLevelIndex(DataCell cell) {
if(cell.isMissing())
return REXPInteger.NA;
String value = ((StringValue)cell).getStringValue();
if(m_levels.containsValue(value)) {
for(int i : m_levels.keySet())
if(m_levels.get(i).equals(value))
return (i+1);
}
return -1;
}
/**
* @param cell
* @return string value of KNIME cell or {@link RDataFrameContainer#NA_VAL_FOR_R} if missing cell
*/
private String getStringValue(DataCell cell) {
String val;
if(cell.isMissing())
val = RDataFrameContainer.NA_VAL_FOR_R;
else
val = ((StringValue)cell).getStringValue();
return val;
}
/**
* @param cell
* @return integer value of KNIME cell or {@link REXPInteger#NA} if missing cell
*/
private Integer getIntegerValue(DataCell cell) {
int val;
if(cell.isMissing())
val = REXPInteger.NA;
else
val = ((IntValue)cell).getIntValue();
return val;
}
/**
* @param cell
* @return byte value of KNIME cell or {@link REXPLogical#NA} if missing cell
*/
private byte getLogicalValue(DataCell cell) {
byte val;
if(cell.isMissing())
val = REXPLogical.NA;
else
val = (byte)(((BooleanValue)cell).getBooleanValue()? 1:0);
return val;
}
/**
* @param cell
* @return double value of KNIME cell or {@link REXPDouble#NA} if missing cell
*/
private double getDoubleValue(DataCell cell) {
double val;
if(cell.isMissing())
val = REXPDouble.NA;
else
val = ((DoubleValue)cell).getDoubleValue();
return val;
}
/**
* @return data vector as REXP representation
*/
public REXP getREXPData() {
switch(m_type) {
case R_LOGICAL:
return new REXPLogical(ArrayUtils.toPrimitive((Byte[]) m_data));
case R_INT:
return new REXPInteger(ArrayUtils.toPrimitive((Integer[]) m_data));
case R_DOUBLE:
return new REXPDouble(ArrayUtils.toPrimitive((Double[]) m_data));
case R_FACTOR:
return new REXPFactor(ArrayUtils.toPrimitive((Integer[]) m_data), getLevels());
case R_STRING:
return new REXPString((String[]) m_data);
default:
}
return null;
}
/**
* @return levels as String vector
*/
private String[] getLevels() {
String[] levels = new String[m_levels.size()];
Integer[] indices = m_levels.keySet().toArray(new Integer[m_levels.size()]);
Arrays.sort(indices);
for(int i : indices) {
levels[i] = m_levels.get(i);
}
return levels;
}
/**
* set data vector to null
*/
public void clearData() {
m_data = null;
}
/**
* @return comma separated string with missing value indices
*/
public String getMissingIdx() {
return StringUtils.join(m_missingFlags, ',');
}
/**
* set levels from KNIME-domain values
* @param levels
*/
public void setLevels(Set<DataCell> levels) {
m_levels = new HashMap<Integer, String>();
assert levels!= null;
int i = 0;
for(DataCell cell : levels) {
m_levels.put(i, ((StringValue)cell).getStringValue());
i++;
}
}
// R >>> KNIME
// ============================================================
/**
* set levels from String vector
* @param levels
*/
public void setLevels(String[] levels) {
m_levels = new HashMap<Integer, String>();
for(int i = 1; i <= levels.length; i++)
this.m_levels.put(i, levels[i-1]);
}
/**
* set upper/lower bounds for numeric column
* @param bounds
*/
public void setBounds(double[] bounds) {
this.m_bounds = bounds;
}
/**
* @return KNIME data type
*/
public DataType getKnimeDataType() {
if(m_type.equals(RType.R_DOUBLE)) return DoubleCell.TYPE;
if(m_type.equals(RType.R_INT)) return IntCell.TYPE;
if(m_type.equals(RType.R_LOGICAL)) return BooleanCell.TYPE;
if(m_type.equals(RType.R_FACTOR) || m_type.equals(RType.R_STRING)) return StringCell.TYPE;
return null;
}
/**
* put levels into KNIME domain format (as {@link LinkedHashSet})
* @return
*/
private LinkedHashSet<DataCell> createDomainValueSet() {
LinkedHashSet<DataCell> values = new LinkedHashSet<DataCell>();
Integer[] indices = m_levels.keySet().toArray(new Integer[m_levels.size()]);
Arrays.sort(indices);
for(int i : indices) {
String level = m_levels.get(i);
values.add(new StringCell(level));
}
return values;
}
/**
* @param hasValues if TRUE, set levels
* @return KNIME domain object for this column
*/
public DataColumnDomain getKnimeDomain(boolean hasValues) {
DataColumnDomainCreator dCDC = new DataColumnDomainCreator();
if(hasValues) {
if(m_type.equals(RType.R_DOUBLE)) {
dCDC.setLowerBound(new DoubleCell(m_bounds[0]));
dCDC.setUpperBound(new DoubleCell(m_bounds[1]));
}
if(m_type.equals(RType.R_INT)) {
dCDC.setLowerBound(new IntCell((int) m_bounds[0]));
dCDC.setUpperBound(new IntCell((int) m_bounds[1]));
}
if(m_type.equals(RType.R_LOGICAL)) {
boolean lBound = m_bounds[0] != 0 ? true : false;
boolean uBound = m_bounds[1] != 0 ? true : false;
dCDC.setLowerBound(BooleanCell.get(lBound));
dCDC.setUpperBound(BooleanCell.get(uBound));
Set<BooleanCell> vals = new LinkedHashSet<BooleanCell>();
vals.add(BooleanCell.get(uBound));
vals.add(BooleanCell.get(lBound));
dCDC.setValues(vals);
}
if(m_type.equals(RType.R_FACTOR)) {
dCDC.setValues(createDomainValueSet());
}
}
return dCDC.createDomain();
}
/**
* add REXP-vector values to data vector
* also keeps missing-value indices
* @param data
*/
public void addData(REXP data) {
try {
boolean[] missingVals = data.isNA();
m_missingFlags = new HashSet<Integer>();
for(int i = 0; i < missingVals.length; i++)
if(missingVals[i])
m_missingFlags.add(i);
} catch (REXPMismatchException e) {
e.printStackTrace();
}
switch(m_type) {
case R_LOGICAL:
m_data = ArrayUtils.toObject(((REXPLogical)data).isTRUE());
break;
case R_INT:
m_data = ArrayUtils.toObject(((REXPInteger)data).asIntegers());
break;
case R_DOUBLE:
m_data = ArrayUtils.toObject(((REXPDouble)data).asDoubles());
break;
case R_FACTOR:
m_data = ((REXPFactor)data).asFactor().asStrings();
break;
case R_STRING:
m_data = ((REXPString)data).asStrings();
break;
default:
}
}
/**
* @param rowIdx
* @return KNIME cell for a given row index
*/
public DataCell getKNIMECell(int rowIdx) {
if(m_missingFlags.size() > 0)
if(m_missingFlags.contains(rowIdx))
return DataType.getMissingCell();
switch(m_type) {
case R_LOGICAL:
return BooleanCellFactory.create((boolean)m_data[rowIdx]);
//return BooleanCell.get((boolean) m_data[rowIdx]);
case R_INT:
return IntCellFactory.create((int) m_data[rowIdx]);
//return new IntCell((Integer) m_data[rowIdx]);
case R_DOUBLE:
return DoubleCellFactory.create((double) m_data[rowIdx]);
//return new DoubleCell((Double) m_data[rowIdx]);
case R_FACTOR:
case R_STRING:
return StringCellFactory.create((String) m_data[rowIdx]);
//return new StringCell((String) m_data[rowIdx]);
default:
}
return null;
}
}