function obj = importDataTable(obj, data_column, axis_val_columns, axis_names, overwriteBool)
%% importDataTable - overwrite object data with tabular data from variable
%
% Purpose:
% Imports a 2D table of data and converts it into a high dimensional
% matrix. The first column in this table specifies the actual data. The
% remaining columns specify where the data is located in
% high-dimensional space. In the output obj, the first column populates
% obj.data. The remainder populate obj.axis.values.
% Usage:
% Note: functionality can be called from a static (ie class) or object method
% As class static method:
% obj = MDD.ImportDataTable(data_column,axis_val_columns) % uppercase method
% obj = MDD.ImportDataTable(data_column,axis_val_columns,axis_names) % uppercase method
%
% As object method:
% obj = MDD();
% obj = obj.importDataTable(data_column,axis_val_columns) % lowercase method
% obj = obj.importDataTable(data_column,axis_val_columns,axis_names) % lowercase method
%
% Inputs:
% data_column - A cell array or matrix specifying the first column of
% the table. This will populate obj.data. If a cell array
% is used, data in the cells can be of any type.
% axis_val_columns - A 1xM cell array specifying remaining M columns
% of the table. These determine the locations of the
% data in M-dimensional space. THERE are specific
% restrictions on what can constitute these columns.
% See NOTE (3) BELOW.
% axis_names - Names associated with each of the M columns of the
% table.
% overwriteBool - if false (default), throw error on duplicate entries.
% if true, use the last duplicate entry.
%
% Notes:
% 1) We assume the full table is of dimensions NxM+1. Therefore,
% data_column, and each of the cells in axis_val_columns, must
% contain N entries.
% 2) data_column is of size Nx1 (or 1xN) and can be either a cell
% array or numeric. If it's a cell array, data in cells can be of
% any type (numeric, cellarrays, strings, tables, handles, etc.).
% 3) Each of the M columns specified in axis_val_columns should be
% of size Nx1. They can be of the following types:
% - numeric array
% - cell arrays of numerics,
% - cell arrays of character vectors.
% They do not need to be all the same (e.g. one column can be
% numerics, the other can be a cell array of chars).
if nargin < 5
overwriteBool = false;
end
% Initialize
X = data_column;
axlinear = axis_val_columns;
lenX = length(X);
Ndims = length(axlinear);
% Error checking - X must be linear
if ~isvector(X); error('data_column must be linear'); end
% Error checking - X must be cell or numeric
[~, XsimpleFormat] = MDD.calcClasses(X,'data');
if strcmp(XsimpleFormat,'unknown'); error('data_column must be a numeric or cell array'); end
% Error checking - each entry in axislinear must be either numeric or
% cell. If it's a cell, all entries must char.
axLinearFormat = cell(1, Ndims);
for k = 1:Ndims
axLinearFormat{k} = MDD.calcClasses(axlinear{k}, 'axis_values');
end
if any(strcmp(axLinearFormat,'unknown')) || isempty(axlinear); error('Cells in axis_val_columns must be a numeric array, cell array of numerics, or cell array of chars'); end
% Error checking - length of each axis_val_column must be equal to the
% length of data_column (e.g. the number of rows in the table must be
% uniform across all columns)
axis_lengths = cellfunu(@length,axis_val_columns);
if ~all(length(data_column) == [axis_lengths{:}]); error('ImportDataTable failed - all cells in axis_vals must have length equal to length(data)'); end
if ~overwriteBool && MDD.isDuplicateAxisValues(axis_val_columns)
error(['Attempting to import overlapping entries. Set overwriteBool=true to',...
' overwrite overlapping entries with the last duplicate entry.'])
end
% Set up xp.axis_pr
sz = zeros(1, Ndims);
for iDim = 1:Ndims
if strcmp(axLinearFormat{iDim}, 'cellnum')
axlinear{iDim} = [axlinear{iDim}{:}]; % convert cellnum to numeric array
% fprintf(' Note: Converting dim %i axis_values to numeric array from cell array of numerics\n', iDim)
end
obj.axis_pr(iDim).values = unique(axlinear{iDim},'stable');
sz(iDim) = length(obj.axis_pr(iDim).values);
if isnumeric(axlinear{iDim}(1))
if any(isnan(axlinear{iDim})) || any(isinf(axlinear{iDim}))
error('Axis cannot contain NaNs or Infs');
end
end
end
if length(sz) == 1; sz(2) = 1; end
% Set up target matrix
switch XsimpleFormat
case 'cell'
obj.data_pr = cell(sz);
% case 'string'
% xp.data_pr = repmat(string(''),sz);
case 'numeric'
obj.data_pr = nan(sz);
otherwise
error('Case not implemented');
end
% Set up xp.data_pr -> Convert linear data into a multi dimensional matrix
for indLinear = 1:lenX
% Get subscripts
subs = cell(1,Ndims);
for iDim = 1:Ndims
if iscellstr(axlinear{iDim})
subs{iDim} = find(strcmp(axlinear{iDim}{indLinear},obj.axis_pr(iDim).values));
else
subs{iDim} = find(axlinear{iDim}(indLinear) == obj.axis_pr(iDim).values);
end
end
% Add data to sparse cell array or matrix based on subscripts
obj.data_pr(subs{:}) = X(indLinear);
end
if nargin > 3 && ~isempty(axis_names)
obj = obj.importAxisNames(axis_names);
end
obj = obj.fixAxes(1);
end