/// <summary> /// Tests if the data in <paramref name="data"/> can be used for the DecomposeByColumnContent action. /// </summary> /// <param name="data">The data to test.</param> /// <param name="throwIfNonCoherent">If true, an exception is thrown if any problems are detected. If false, it is tried to rectify the problem by making some assumtions.</param> public static void EnsureCoherence(DataTableMultipleColumnProxy data, bool throwIfNonCoherent) { if (null == data.DataTable) // this is mandatory, thus an exception is always thrown { throw new ArgumentNullException("SourceTable is null, it must be set before"); } data.EnsureExistenceOfIdentifier(ColumnX, 1); data.EnsureExistenceOfIdentifier(ColumnY, 1); data.EnsureExistenceOfIdentifier(ColumnV, 1); if (data.GetDataColumns(ColumnV).Count == 0) { if (throwIfNonCoherent) { throw new ArgumentException(!data.ContainsIdentifier(ColumnV) ? "ColumnsToProcess is not set" : "ColumnsToProcess is empty"); } } if (data.GetDataColumnOrNull(ColumnX) == null) { if (throwIfNonCoherent) { throw new ArgumentException("X column not included in columnsToProcess"); } else { var col = data.GetDataColumns(ColumnV).FirstOrDefault(); if (null != col) { data.SetDataColumn(ColumnX, col); } } } if (data.GetDataColumnOrNull(ColumnY) == null) { if (throwIfNonCoherent) { throw new ArgumentException("Y column not included in columnsToProcess"); } else { var col = data.GetDataColumns(ColumnV).FirstOrDefault(); if (null != col) { data.SetDataColumn(ColumnY, col); } } } }
/// <summary> /// Tests if the data in <paramref name="data"/> can be used for the ExpandCyclingVariable action. /// </summary> /// <param name="data">The data to test.</param> /// <param name="throwIfNonCoherent">If true, an exception is thrown if any problems are detected. If false, it is tried to rectify the problem by making some assumtions.</param> public static void EnsureCoherence(DataTableMultipleColumnProxy data, bool throwIfNonCoherent) { if (null == data.DataTable) // this is mandatory, thus an exception is always thrown { throw new ArgumentNullException("SourceTable is null, it must be set before"); } data.EnsureExistenceOfIdentifier(ColumnsParticipatingIdentifier); data.EnsureExistenceOfIdentifier(ColumnWithCyclingVariableIdentifier, 1); data.EnsureExistenceOfIdentifier(ColumnsToAverageIdentifier); if (data.GetDataColumns(ColumnsParticipatingIdentifier).Count == 0) { if (throwIfNonCoherent) { throw new ArgumentException(!data.ContainsIdentifier(ColumnsParticipatingIdentifier) ? "ColumnsToProcess is not set" : "ColumnsToProcess is empty"); } } if (data.GetDataColumnOrNull(ColumnWithCyclingVariableIdentifier) == null) { if (throwIfNonCoherent) { throw new ArgumentException("Column with cycling variable was not included in columnsToProcess"); } else { var col = data.GetDataColumns(ColumnsParticipatingIdentifier).FirstOrDefault(); if (null != col) { data.SetDataColumn(ColumnWithCyclingVariableIdentifier, col); } } } if (!data.ContainsIdentifier(ColumnsToAverageIdentifier)) { if (throwIfNonCoherent) { throw new ArgumentException("ColumnsToAverage collection is not included"); } } }
/// <summary> /// Decompose the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable. /// The provided destination table is cleared from all data and property values before. /// </summary> /// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param> /// <param name="options">The settings for decomposing.</param> /// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param> /// <returns>Null if the method finishes successfully, or an error information.</returns> public static string DecomposeByColumnContent(DataTableMultipleColumnProxy inputData, DecomposeByColumnContentOptions options, DataTable destTable) { var srcTable = inputData.DataTable; try { DecomposeByColumnContentDataAndOptions.EnsureCoherence(inputData, true); } catch (Exception ex) { return ex.Message; } destTable.DataColumns.RemoveColumnsAll(); destTable.PropCols.RemoveColumnsAll(); DataColumn srcCycCol = inputData.GetDataColumnOrNull(DecomposeByColumnContentDataAndOptions.ColumnWithCyclingVariableIdentifier); var decomposedValues = Decompose(srcCycCol); // the decomposedValues are not sorted yes if (options.DestinationColumnSorting == DecomposeByColumnContentOptions.OutputSorting.Ascending) { decomposedValues.Sort(); } else if (options.DestinationColumnSorting == DecomposeByColumnContentOptions.OutputSorting.Descending) { decomposedValues.Sort(); decomposedValues.Reverse(); } // get the other columns to process var srcColumnsToProcess = new List<DataColumn>(inputData.GetDataColumns(DecomposeByColumnContentDataAndOptions.ColumnsParticipatingIdentifier)); // subtract the column containing the decompose values srcColumnsToProcess.Remove(srcCycCol); // the only property column that is now usefull is that with the repeated values var destPropCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCycCol), srcTable.DataColumns.GetColumnGroup(srcCycCol)); if (options.DestinationOutput == DecomposeByColumnContentOptions.OutputFormat.GroupOneColumn) { // columns originating from the same column but with different property are grouped together, but they will get different group numbers foreach (var srcCol in srcColumnsToProcess) { int nCreatedCol = -1; int nCreatedProp = 0; foreach (var prop in decomposedValues) { ++nCreatedCol; ++nCreatedProp; var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), nCreatedProp); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); for (int i = 0, j = 0; i < srcCycCol.Count; ++i) { if (prop == srcCycCol[i]) { destCol[j] = srcCol[i]; ++j; } } // fill also property column destPropCol[nDestCol] = prop; } } } else if (options.DestinationOutput == DecomposeByColumnContentOptions.OutputFormat.GroupAllColumns) { // all columns with the same property are grouped together, and those columns will share the same group number int nCreatedCol = -1; // running number of processed range for column creation (Naming) int nCreatedProp = -1; foreach (var prop in decomposedValues) { ++nCreatedProp; ++nCreatedCol; foreach (var srcCol in srcColumnsToProcess) { var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), nCreatedProp); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); for (int i = 0, j = 0; i < srcCycCol.Count; ++i) { if (prop == srcCycCol[i]) { destCol[j] = srcCol[i]; ++j; } } // fill also property column destPropCol[nDestCol] = prop; } } } else { throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString()); } return null; }
/// <summary> /// Tests if the data in <paramref name="data"/> can be used for the DecomposeByColumnContent action. /// </summary> /// <param name="data">The data to test.</param> /// <param name="throwIfNonCoherent">If true, an exception is thrown if any problems are detected. If false, it is tried to rectify the problem by making some assumtions.</param> public static void EnsureCoherence(DataTableMultipleColumnProxy data, bool throwIfNonCoherent) { if (null == data.DataTable) // this is mandatory, thus an exception is always thrown { throw new ArgumentNullException("SourceTable is null, it must be set before"); } data.EnsureExistenceOfIdentifier(ColumnsParticipatingIdentifier); data.EnsureExistenceOfIdentifier(ColumnWithCyclingVariableIdentifier, 1); if (data.GetDataColumns(ColumnsParticipatingIdentifier).Count == 0) { if (throwIfNonCoherent) throw new ArgumentException(!data.ContainsIdentifier(ColumnsParticipatingIdentifier) ? "ColumnsToProcess is not set" : "ColumnsToProcess is empty"); } if (data.GetDataColumnOrNull(ColumnWithCyclingVariableIdentifier) == null) { if (throwIfNonCoherent) throw new ArgumentException("Column with cycling variable was not included in columnsToProcess"); else { var col = data.GetDataColumns(ColumnsParticipatingIdentifier).FirstOrDefault(); if (null != col) data.SetDataColumn(ColumnWithCyclingVariableIdentifier, col); } } }
/// <summary> /// Decompose the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable. /// The provided destination table is cleared from all data and property values before. /// </summary> /// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param> /// <param name="options">The settings for decomposing.</param> /// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param> /// <returns>Null if the method finishes successfully, or an error information.</returns> public static string DecomposeByColumnContent(DataTableMultipleColumnProxy inputData, DecomposeByColumnContentOptions options, DataTable destTable) { var srcTable = inputData.DataTable; try { DecomposeByColumnContentDataAndOptions.EnsureCoherence(inputData, true); } catch (Exception ex) { return(ex.Message); } destTable.DataColumns.RemoveColumnsAll(); destTable.PropCols.RemoveColumnsAll(); DataColumn srcCycCol = inputData.GetDataColumnOrNull(DecomposeByColumnContentDataAndOptions.ColumnWithCyclingVariableIdentifier); var decomposedValues = Decompose(srcCycCol); // the decomposedValues are not sorted yes if (options.DestinationColumnSorting == DecomposeByColumnContentOptions.OutputSorting.Ascending) { decomposedValues.Sort(); } else if (options.DestinationColumnSorting == DecomposeByColumnContentOptions.OutputSorting.Descending) { decomposedValues.Sort(); decomposedValues.Reverse(); } // get the other columns to process var srcColumnsToProcess = new List <DataColumn>(inputData.GetDataColumns(DecomposeByColumnContentDataAndOptions.ColumnsParticipatingIdentifier)); // subtract the column containing the decompose values srcColumnsToProcess.Remove(srcCycCol); // the only property column that is now usefull is that with the repeated values var destPropCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCycCol), srcTable.DataColumns.GetColumnGroup(srcCycCol)); if (options.DestinationOutput == DecomposeByColumnContentOptions.OutputFormat.GroupOneColumn) { // columns originating from the same column but with different property are grouped together, but they will get different group numbers foreach (var srcCol in srcColumnsToProcess) { int nCreatedCol = -1; int nCreatedProp = 0; foreach (var prop in decomposedValues) { ++nCreatedCol; ++nCreatedProp; var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), nCreatedProp); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); for (int i = 0, j = 0; i < srcCycCol.Count; ++i) { if (prop == srcCycCol[i]) { destCol[j] = srcCol[i]; ++j; } } // fill also property column destPropCol[nDestCol] = prop; } } } else if (options.DestinationOutput == DecomposeByColumnContentOptions.OutputFormat.GroupAllColumns) { // all columns with the same property are grouped together, and those columns will share the same group number int nCreatedCol = -1; // running number of processed range for column creation (Naming) int nCreatedProp = -1; foreach (var prop in decomposedValues) { ++nCreatedProp; ++nCreatedCol; foreach (var srcCol in srcColumnsToProcess) { var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), nCreatedProp); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); for (int i = 0, j = 0; i < srcCycCol.Count; ++i) { if (prop == srcCycCol[i]) { destCol[j] = srcCol[i]; ++j; } } // fill also property column destPropCol[nDestCol] = prop; } } } else { throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString()); } return(null); }
/// <summary> /// Decompose the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable. /// The provided destination table is cleared from all data and property values before. /// </summary> /// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param> /// <param name="options">The settings for decomposing.</param> /// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param> /// <returns>Null if the method finishes successfully, or an error information.</returns> public static string ConvertXYVToMatrix(DataTableMultipleColumnProxy inputData, ConvertXYVToMatrixOptions options, DataTable destTable) { var srcTable = inputData.DataTable; try { ConvertXYVToMatrixDataAndOptions.EnsureCoherence(inputData, true); } catch (Exception ex) { return(ex.Message); } destTable.DataColumns.RemoveColumnsAll(); destTable.PropCols.RemoveColumnsAll(); DataColumn srcXCol = inputData.GetDataColumnOrNull(ConvertXYVToMatrixDataAndOptions.ColumnX); DataColumn srcYCol = inputData.GetDataColumnOrNull(ConvertXYVToMatrixDataAndOptions.ColumnY); // X-Values IReadOnlyList <AltaxoVariant> clusterValuesX; IReadOnlyList <int> clusterIndicesX; IReadOnlyList <double> clusterStdDevX = null; if (options.UseClusteringForX && options.NumberOfClustersX.HasValue && srcXCol is DoubleColumn srcXDbl) { (clusterValuesX, clusterStdDevX, clusterIndicesX) = ClusterValuesByKMeans(srcXDbl, options.NumberOfClustersX.Value, options.DestinationXColumnSorting, options.CreateStdDevX); } else { (clusterValuesX, clusterIndicesX) = ClusterValuesByEquality(srcXCol, options.DestinationXColumnSorting); } // Y-Values IReadOnlyList <AltaxoVariant> clusterValuesY; IReadOnlyList <int> clusterIndicesY; IReadOnlyList <double> clusterStdDevY = null; if (options.UseClusteringForY && options.NumberOfClustersY.HasValue && srcYCol is DoubleColumn srcYDbl) { (clusterValuesY, clusterStdDevY, clusterIndicesY) = ClusterValuesByKMeans(srcYDbl, options.NumberOfClustersY.Value, options.DestinationYColumnSorting, options.CreateStdDevY); } else { (clusterValuesY, clusterIndicesY) = ClusterValuesByEquality(srcYCol, options.DestinationYColumnSorting); } // get the other columns to process var srcColumnsToProcess = new List <DataColumn>(inputData.GetDataColumns(ConvertXYVToMatrixDataAndOptions.ColumnV)); // subtract the column containing the decompose values srcColumnsToProcess.Remove(srcXCol); srcColumnsToProcess.Remove(srcYCol); int xOffset = 1 + (clusterStdDevY != null ? 1 : 0); // the only property column that is now useful is that with the repeated values var destXCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcXCol), srcXCol.GetType(), ColumnKind.X, 0); for (int i = 0; i < xOffset; ++i) { destXCol[0] = double.NaN; } for (int i = 0; i < clusterValuesX.Count; ++i) { destXCol[i + xOffset] = clusterValuesX[i]; // leave index 0 and maybe 1for the y-column } if (clusterStdDevX != null) { var stdXCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcXCol) + "_StdDev", srcXCol.GetType(), ColumnKind.Err, 0); for (int i = 0; i < xOffset; ++i) { stdXCol[0] = double.NaN; } for (int i = 0; i < clusterStdDevX.Count; ++i) { stdXCol[i + xOffset] = clusterStdDevX[i]; // leave index 0 and maybe 1 for the y-column } } var destYCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcYCol), srcYCol.GetType(), ColumnKind.X, 0); for (int i = 0; i < clusterValuesY.Count; ++i) { destYCol[i] = clusterValuesY[i]; // leave index 0 for the y-column } if (clusterStdDevY != null) { var stdYCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcYCol) + "_StdDev", srcYCol.GetType(), ColumnKind.Err, 0); for (int i = 0; i < clusterStdDevY.Count; ++i) { stdYCol[i] = clusterStdDevY[i]; // leave index 0 for the y-column } } var srcVColumn = srcColumnsToProcess[0]; // Create as many columns as there are values in the destXColumn for (int i = 0; i < clusterValuesX.Count; ++i) { if (options.ColumnNaming == ConvertXYVToMatrixOptions.OutputNaming.ColAndIndex || string.IsNullOrEmpty(options.ColumnNameFormatString)) { destTable.DataColumns.EnsureExistence("Col" + i.ToString(), srcVColumn.GetType(), ColumnKind.V, 0); } else { destTable.DataColumns.EnsureExistence(string.Format(options.ColumnNameFormatString, clusterValuesX[i], i), srcVColumn.GetType(), ColumnKind.V, 0); } } var dict = new Dictionary <(int iX, int iY), (AltaxoVariant sum, int count)>(); for (int i = 0; i < srcVColumn.Count; ++i) { var iX = xOffset + clusterIndicesX[i]; var iY = clusterIndicesY[i]; if (destTable[iX].IsElementEmpty(iY)) { destTable[iX][iY] = srcVColumn[i]; } else { switch (options.ValueAveraging) { case ConvertXYVToMatrixOptions.OutputAveraging.NoneIgnoreUseLastValue: destTable[iX][iY] = srcVColumn[i]; break; case ConvertXYVToMatrixOptions.OutputAveraging.NoneThrowException: throw new Exception(string.Format("Multiple data present for X={0}, Y={1}, and average options has been set to throw an exception in this case!", srcXCol[i], srcYCol[i])); case ConvertXYVToMatrixOptions.OutputAveraging.AverageLinear: { if (!dict.ContainsKey((iX, iY))) { dict.Add((iX, iY), (srcVColumn[i], 1)); // if not found in the dictionary, then add the first value that was already in the table } var(sum, count) = dict[(iX, iY)];
/// <summary> /// Expand the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable. /// The provided destination table is cleared from all data and property values before. /// </summary> /// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param> /// <param name="options">The settings for expanding.</param> /// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param> /// <returns>Null if the method finishes successfully, or an error information.</returns> public static string ExpandCyclingVariableColumn(DataTableMultipleColumnProxy inputData, ExpandCyclingVariableColumnOptions options, DataTable destTable) { var srcTable = inputData.DataTable; try { ExpandCyclingVariableColumnDataAndOptions.EnsureCoherence(inputData, true); } catch (Exception ex) { return ex.Message; } destTable.DataColumns.RemoveColumnsAll(); destTable.PropCols.RemoveColumnsAll(); DataColumn srcCycCol = inputData.GetDataColumnOrNull(ExpandCyclingVariableColumnDataAndOptions.ColumnWithCyclingVariableIdentifier); var repeatRanges = DecomposeIntoRepeatUnits(srcCycCol); // check if there is at least one averaged column var columnsToAverageOverRepeatPeriod = inputData.GetDataColumns(ExpandCyclingVariableColumnDataAndOptions.ColumnsToAverageIdentifier); if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.FirstAveragedColumn && columnsToAverageOverRepeatPeriod.Count == 0) throw new ArgumentException("In order to let the first averaged column being the x-column, a column to average is needed, but the options didn't provide such column!"); // get the other columns to process var srcColumnsToProcess = new List<DataColumn>(inputData.GetDataColumns(ExpandCyclingVariableColumnDataAndOptions.ColumnsParticipatingIdentifier)); // subtract cyclic variable column and average columns srcColumnsToProcess.Remove(srcCycCol); foreach (var col in columnsToAverageOverRepeatPeriod) srcColumnsToProcess.Remove(col); // --- Create and calculate the averaged columns, for now only temporarily --- var propColsTemp = AverageColumns(srcTable, columnsToAverageOverRepeatPeriod, options, repeatRanges); // --- avgValueOrder designates the ordering of the first averaged column and therefore of the sorting of the ranges and of the first averaged column int[] avgValueOrder = Sorting.CreateIdentityIndices(repeatRanges.Count); // --- prepare the sorting of columns by first averaged column --- var rangeOrderSorting = options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable ? options.DestinationColumnSorting : options.DestinationRowSorting; if (propColsTemp.Length > 0 && rangeOrderSorting != ExpandCyclingVariableColumnOptions.OutputSorting.None) { avgValueOrder = Sorting.HeapSortVirtually(propColsTemp[0], avgValueOrder); if (rangeOrderSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending) Sorting.ReverseArray(avgValueOrder); } // prepare the sorting of the cycling values var cycValueSorting = options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable ? options.DestinationRowSorting : options.DestinationColumnSorting; // create a dictionary with the cycling values (unique) and the corresponding ordering index var cycValueOrder = GetUniqueValues(srcCycCol, cycValueSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending); if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable) { int[] propColsIdx = CreatePropColsForAveragedColumns(srcTable, columnsToAverageOverRepeatPeriod, options, destTable); // --- Fill the x column, take the row sorting into account --- var destXCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), ColumnKind.X, srcTable.DataColumns.GetColumnGroup(srcCycCol)); foreach (var entry in cycValueOrder) destXCol[entry.Value] = entry.Key; if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupOneColumn) { // foreach sourceColumnToProcess create as many destination columns as there are cycling ranges available foreach (var srcCol in srcColumnsToProcess) { int nCreatedCol = -1; var destColumnsToSort = new AscendingIntegerCollection(); foreach (int rangeIndex in avgValueOrder) { var range = repeatRanges[rangeIndex]; ++nCreatedCol; var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol)); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); destColumnsToSort.Add(nDestCol); foreach (var nSrcRow in range) { int nDestRow = cycValueOrder[srcCycCol[nSrcRow]]; destCol[nDestRow] = srcCol[nSrcRow]; } // fill also property columns for (int nPropCol = 0; nPropCol < propColsTemp.Length; nPropCol++) { destTable.PropCols[propColsIdx[nPropCol]][nDestCol] = propColsTemp[nPropCol][rangeIndex]; } } } // repeat for each source colum to process } else if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupAllColumns) { int nCreatedCol = -1; // running number of processed range for column creation (Naming) foreach (int rangeIndex in avgValueOrder) { var range = repeatRanges[rangeIndex]; ++nCreatedCol; foreach (var srcCol in srcColumnsToProcess) { var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol)); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); foreach (var nSrcRow in range) { int nDestRow = cycValueOrder[srcCycCol[nSrcRow]]; destCol[nDestRow] = srcCol[nSrcRow]; } // fill also property columns for (int nPropCol = 0; nPropCol < propColsTemp.Length; nPropCol++) { destTable.PropCols[propColsIdx[nPropCol]][nDestCol] = propColsTemp[nPropCol][rangeIndex]; } } } } else { throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString()); } } else if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.FirstAveragedColumn) { // now the first x column contains the values of the averaged column // the rest of the data columns is repeated as many times as there are members in each repeat range DataColumn srcXCol = columnsToAverageOverRepeatPeriod[0]; var destXCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcXCol), srcXCol.GetType(), ColumnKind.X, srcTable.DataColumns.GetColumnGroup(srcXCol)); // Fill with destination X for (int nDestRow = 0; nDestRow < repeatRanges.Count; nDestRow++) destXCol[nDestRow] = propColsTemp[0][avgValueOrder[nDestRow]]; // the only property column that is now usefull is that with the repeated values var destPropCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCycCol), srcTable.DataColumns.GetColumnGroup(srcCycCol)); if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupOneColumn) { foreach (var srcCol in srcColumnsToProcess) { int nCurrNumber = -1; IEnumerable<AltaxoVariant> cycValues = cycValueOrder.Keys; if (options.DestinationColumnSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending) cycValues = cycValueOrder.Keys.Reverse(); foreach (var cycValue in cycValues) { ++nCurrNumber; var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCurrNumber.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol)); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); int nDestRow = -1; foreach (int rangeIndex in avgValueOrder) { var range = repeatRanges[rangeIndex]; ++nDestRow; int nSrcRow = FindSrcXRow(srcCycCol, cycValue, range); if (nSrcRow >= 0) destCol[nDestRow] = srcCol[nSrcRow]; } // fill also property column destPropCol[nDestCol] = cycValue; } } } else if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupAllColumns) { IEnumerable<AltaxoVariant> positionsKeys = cycValueOrder.Keys; if (options.DestinationColumnSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending) positionsKeys = cycValueOrder.Keys.Reverse(); int nCurrNumber = -1; foreach (var xVal in positionsKeys) { ++nCurrNumber; foreach (var srcCol in srcColumnsToProcess) { var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCurrNumber.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol)); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); int nDestRow = -1; foreach (int rangeIndex in avgValueOrder) { var range = repeatRanges[rangeIndex]; ++nDestRow; int nSrcRow = FindSrcXRow(srcCycCol, xVal, range); if (nSrcRow >= 0) destCol[nDestRow] = srcCol[nSrcRow]; } // fill also property column destPropCol[nDestCol] = xVal; } } } else { throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString()); } } return null; }
/// <summary> /// Expand the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable. /// The provided destination table is cleared from all data and property values before. /// </summary> /// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param> /// <param name="options">The settings for expanding.</param> /// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param> /// <returns>Null if the method finishes successfully, or an error information.</returns> public static string ExpandCyclingVariableColumn(DataTableMultipleColumnProxy inputData, ExpandCyclingVariableColumnOptions options, DataTable destTable) { var srcTable = inputData.DataTable; try { ExpandCyclingVariableColumnDataAndOptions.EnsureCoherence(inputData, true); } catch (Exception ex) { return(ex.Message); } destTable.DataColumns.RemoveColumnsAll(); destTable.PropCols.RemoveColumnsAll(); DataColumn srcCycCol = inputData.GetDataColumnOrNull(ExpandCyclingVariableColumnDataAndOptions.ColumnWithCyclingVariableIdentifier); var repeatRanges = DecomposeIntoRepeatUnits(srcCycCol); // check if there is at least one averaged column var columnsToAverageOverRepeatPeriod = inputData.GetDataColumns(ExpandCyclingVariableColumnDataAndOptions.ColumnsToAverageIdentifier); if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.FirstAveragedColumn && columnsToAverageOverRepeatPeriod.Count == 0) { throw new ArgumentException("In order to let the first averaged column being the x-column, a column to average is needed, but the options didn't provide such column!"); } // get the other columns to process var srcColumnsToProcess = new List <DataColumn>(inputData.GetDataColumns(ExpandCyclingVariableColumnDataAndOptions.ColumnsParticipatingIdentifier)); // subtract cyclic variable column and average columns srcColumnsToProcess.Remove(srcCycCol); foreach (var col in columnsToAverageOverRepeatPeriod) { srcColumnsToProcess.Remove(col); } // --- Create and calculate the averaged columns, for now only temporarily --- var propColsTemp = AverageColumns(srcTable, columnsToAverageOverRepeatPeriod, options, repeatRanges); // --- avgValueOrder designates the ordering of the first averaged column and therefore of the sorting of the ranges and of the first averaged column int[] avgValueOrder = Sorting.CreateIdentityIndices(repeatRanges.Count); // --- prepare the sorting of columns by first averaged column --- var rangeOrderSorting = options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable ? options.DestinationColumnSorting : options.DestinationRowSorting; if (propColsTemp.Length > 0 && rangeOrderSorting != ExpandCyclingVariableColumnOptions.OutputSorting.None) { avgValueOrder = Sorting.HeapSortVirtually(propColsTemp[0], avgValueOrder); if (rangeOrderSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending) { Sorting.ReverseArray(avgValueOrder); } } // prepare the sorting of the cycling values var cycValueSorting = options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable ? options.DestinationRowSorting : options.DestinationColumnSorting; // create a dictionary with the cycling values (unique) and the corresponding ordering index var cycValueOrder = GetUniqueValues(srcCycCol, cycValueSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending); if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable) { int[] propColsIdx = CreatePropColsForAveragedColumns(srcTable, columnsToAverageOverRepeatPeriod, options, destTable); // --- Fill the x column, take the row sorting into account --- var destXCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), ColumnKind.X, srcTable.DataColumns.GetColumnGroup(srcCycCol)); foreach (var entry in cycValueOrder) { destXCol[entry.Value] = entry.Key; } if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupOneColumn) { // foreach sourceColumnToProcess create as many destination columns as there are cycling ranges available foreach (var srcCol in srcColumnsToProcess) { int nCreatedCol = -1; var destColumnsToSort = new AscendingIntegerCollection(); foreach (int rangeIndex in avgValueOrder) { var range = repeatRanges[rangeIndex]; ++nCreatedCol; var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol)); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); destColumnsToSort.Add(nDestCol); foreach (var nSrcRow in range) { int nDestRow = cycValueOrder[srcCycCol[nSrcRow]]; destCol[nDestRow] = srcCol[nSrcRow]; } // fill also property columns for (int nPropCol = 0; nPropCol < propColsTemp.Length; nPropCol++) { destTable.PropCols[propColsIdx[nPropCol]][nDestCol] = propColsTemp[nPropCol][rangeIndex]; } } } // repeat for each source colum to process } else if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupAllColumns) { int nCreatedCol = -1; // running number of processed range for column creation (Naming) foreach (int rangeIndex in avgValueOrder) { var range = repeatRanges[rangeIndex]; ++nCreatedCol; foreach (var srcCol in srcColumnsToProcess) { var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol)); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); foreach (var nSrcRow in range) { int nDestRow = cycValueOrder[srcCycCol[nSrcRow]]; destCol[nDestRow] = srcCol[nSrcRow]; } // fill also property columns for (int nPropCol = 0; nPropCol < propColsTemp.Length; nPropCol++) { destTable.PropCols[propColsIdx[nPropCol]][nDestCol] = propColsTemp[nPropCol][rangeIndex]; } } } } else { throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString()); } } else if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.FirstAveragedColumn) { // now the first x column contains the values of the averaged column // the rest of the data columns is repeated as many times as there are members in each repeat range DataColumn srcXCol = columnsToAverageOverRepeatPeriod[0]; var destXCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcXCol), srcXCol.GetType(), ColumnKind.X, srcTable.DataColumns.GetColumnGroup(srcXCol)); // Fill with destination X for (int nDestRow = 0; nDestRow < repeatRanges.Count; nDestRow++) { destXCol[nDestRow] = propColsTemp[0][avgValueOrder[nDestRow]]; } // the only property column that is now usefull is that with the repeated values var destPropCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCycCol), srcTable.DataColumns.GetColumnGroup(srcCycCol)); if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupOneColumn) { foreach (var srcCol in srcColumnsToProcess) { int nCurrNumber = -1; IEnumerable <AltaxoVariant> cycValues = cycValueOrder.Keys; if (options.DestinationColumnSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending) { cycValues = cycValueOrder.Keys.Reverse(); } foreach (var cycValue in cycValues) { ++nCurrNumber; var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCurrNumber.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol)); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); int nDestRow = -1; foreach (int rangeIndex in avgValueOrder) { var range = repeatRanges[rangeIndex]; ++nDestRow; int nSrcRow = FindSrcXRow(srcCycCol, cycValue, range); if (nSrcRow >= 0) { destCol[nDestRow] = srcCol[nSrcRow]; } } // fill also property column destPropCol[nDestCol] = cycValue; } } } else if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupAllColumns) { IEnumerable <AltaxoVariant> positionsKeys = cycValueOrder.Keys; if (options.DestinationColumnSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending) { positionsKeys = cycValueOrder.Keys.Reverse(); } int nCurrNumber = -1; foreach (var xVal in positionsKeys) { ++nCurrNumber; foreach (var srcCol in srcColumnsToProcess) { var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCurrNumber.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol)); var nDestCol = destTable.DataColumns.GetColumnNumber(destCol); int nDestRow = -1; foreach (int rangeIndex in avgValueOrder) { var range = repeatRanges[rangeIndex]; ++nDestRow; int nSrcRow = FindSrcXRow(srcCycCol, xVal, range); if (nSrcRow >= 0) { destCol[nDestRow] = srcCol[nSrcRow]; } } // fill also property column destPropCol[nDestCol] = xVal; } } } else { throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString()); } } return(null); }