diff --git a/+datop/apply_func_along_dimension.m b/+datop/apply_func_along_dimension.m new file mode 100644 index 0000000..155a5a4 --- /dev/null +++ b/+datop/apply_func_along_dimension.m @@ -0,0 +1,44 @@ +function varargout = apply_func_along_dimension(arr, iDim, func, varargin) +dim_size = size(arr); +num_dims = ndims(arr); + +% --- Input Validation --- +if iDim > num_dims || iDim <= 0 || ~isnumeric(iDim) || fix(iDim) ~= iDim + error('iDim must be a positive integer less than or equal to the number of dimensions of arr.'); +end +if ~isa(func, 'function_handle') + error('func must be a function handle.'); +end + +% --- Determine Output Size (and preallocate) --- +% Apply the function to the *first* slice to determine the output size. +first_slice_indices = repmat({1}, 1, num_dims); +first_slice_indices{iDim} = ':'; +first_slice = squeeze(arr(first_slice_indices{:})); +outputN = cell([1,nargout]); +[outputN{:}] = func(first_slice, varargin{:}); +output_size_iDim = cellfun(@(x) numel(x), outputN); +% Calculate the size of the output array +op_size = repmat(dim_size,nargout,1); +op_size(:,iDim) = output_size_iDim; +% Preallocate the output array. +varargout = arrayfun(@(i) zeros(op_size(i,:), 'like', outputN{i}), 1:nargout, 'UniformOutput',false); + +% --- Prepare for Looping --- +% Create a cell array to index all dimensions *except* iDim. +slice_idx = arrayfun(@(x) 1:x, dim_size, 'UniformOutput',false); +slice_idx{iDim} = ':'; +slice_idx = table2cell(combinations(slice_idx{:})); + +for iSlice = 1:size(slice_idx,1) + + [outputN{:}] = func(squeeze(arr(slice_idx{iSlice,:})), varargin{:}); + + for iOp = 1:nargout + + varargout{iOp}(slice_idx{iSlice,:}) = outputN{iOp}; + + end +end + +end \ No newline at end of file diff --git a/+datop/empty_like.m b/+datop/empty_like.m new file mode 100644 index 0000000..101337f --- /dev/null +++ b/+datop/empty_like.m @@ -0,0 +1,6 @@ +function emp = empty_like(x) +% Creates an empty instance of the class of x + +emp = feval(sprintf("%s.empty",class(x))); + +end \ No newline at end of file diff --git a/+datop/extract_numbers_from_string.m b/+datop/extract_numbers_from_string.m new file mode 100644 index 0000000..0122698 --- /dev/null +++ b/+datop/extract_numbers_from_string.m @@ -0,0 +1,25 @@ +function varargout = extract_numbers_from_string(x) +% Extracts all sequences of digits from a string. +% numericVars = extract_numbers_from_string(x) finds all sequences of one or more +% digits in the input string x and returns them as a numeric array. +% +% [numericVars, isNumeric] = extract_numbers_from_string(x) also returns a logical +% array 'isNumeric' the same size as x, where true indicates that the +% corresponding character in x is a digit. +% +% Args: +% x: The input string. +% +% Returns: +% numericVars: A numeric array containing the numbers found in the string. +% Returns an empty array if no numbers are found. +% isNumeric: (Optional) A logical array indicating the position of digits. + +varargout = cell(1, nargout); + +varargout{1} = str2double(regexp(x, '\d+', 'match')); + +if nargout > 1 + varargout{2} = isstrprop(x,'digit'); +end +end \ No newline at end of file diff --git a/+datop/hstack_struct.m b/+datop/hstack_struct.m new file mode 100644 index 0000000..2fe072c --- /dev/null +++ b/+datop/hstack_struct.m @@ -0,0 +1,66 @@ +function s = force_mergestruct(varargin) + +filler = missing; +isMerge = cellfun(@(x) isstruct(x), varargin); + +idx1_opts = find(~isMerge, 1, 'first'); + +if ~isempty(idx1_opts) + opts = varargin(idx1_opts:end); + + ii = 1; + while ii <= length(opts) + + optN = opts{ii}; + ii = ii + 1; + switch optN + + case {'fill_with'} + + filler = opts{ii}; + ii = ii + 1; + end + + end + + + s = varargin(1:idx1_opts-1); +else + + s = varargin; +end + + +try + + s = [s{:}]; + +catch ME + + switch ME.identifier + + case 'MATLAB:catenate:structFieldBad' + + + + fld_names = cellfun(@(x) string(fieldnames(x))', s, 'UniformOutput', false); + fld_names = unique([fld_names{:}]); + + for ii = 1:length(s) + + nan_fields = setdiff(fld_names, fieldnames(s{ii})); + for fldN = nan_fields + + s{ii}.(fldN) = filler; + + end + + end + + s = [s{:}]; + otherwise + rethrow(ME); + end + + +end \ No newline at end of file diff --git a/+datop/make_column.m b/+datop/make_column.m new file mode 100644 index 0000000..cc5cad9 --- /dev/null +++ b/+datop/make_column.m @@ -0,0 +1,5 @@ +function vec = make_column(vec) + +if isrow(vec), vec = vec'; end + +end \ No newline at end of file diff --git a/+datop/make_row.m b/+datop/make_row.m new file mode 100644 index 0000000..76c241b --- /dev/null +++ b/+datop/make_row.m @@ -0,0 +1,5 @@ +function vec = make_row(vec) + +if iscolumn(vec), vec = vec'; end + +end \ No newline at end of file diff --git a/+datop/struct_to_varargin.m b/+datop/struct_to_varargin.m new file mode 100644 index 0000000..67ddfe6 --- /dev/null +++ b/+datop/struct_to_varargin.m @@ -0,0 +1,22 @@ +function op = struct_to_varargin(s) + +assert(isstruct(s) && isscalar(s), "Input must be a struct array of length 1.") + +op = arrayfun(@(f_name, f_val) {f_name(:), handle_empty_val(f_val{:})}, string(fieldnames(s)), struct2cell(s), 'UniformOutput', false); + +op = horzcat(op{:})'; +end + +function op = handle_empty_val(f_val) + +if isempty(f_val) + + op = gen.empty_like(f_val); + +else + + op = f_val; + +end + +end \ No newline at end of file diff --git a/+num/absargmin.m b/+num/absargmin.m new file mode 100644 index 0000000..42cf77a --- /dev/null +++ b/+num/absargmin.m @@ -0,0 +1,10 @@ +function i = absargmin(varargin) +if nargin == 1 +% varargin = {varargin{1},1,varargin{2:end}}; +% else + varargin{2} = 1; +end +% [~, i] = min(abs(varargin{1}),varargin{2:end}); +[~, i] = mink(abs(varargin{1}),varargin{2:end}); + +end \ No newline at end of file diff --git a/+num/find_closest_integer_divisor_pair.m b/+num/find_closest_integer_divisor_pair.m new file mode 100644 index 0000000..9cffb68 --- /dev/null +++ b/+num/find_closest_integer_divisor_pair.m @@ -0,0 +1,12 @@ +function pairN = get_closest_integer_dividers(num) + +assert(isscalar(num) & isnumeric(num) & rem(num,1)==0, "num must be an integer!"); + +pairs = arrayfun(@(x) [x, num/x],1:sqrt(num),'UniformOutput',false); +pairs = vertcat(pairs{:}); +pairs = sort(pairs,2); +isInt = ~any(rem(pairs,1),2); +pairs = pairs(isInt,:); +pairN = pairs(gen.absargmin(diff(pairs,[],2)),:); + +end \ No newline at end of file diff --git a/+num/ifbetween.m b/+num/ifbetween.m new file mode 100644 index 0000000..800bfa4 --- /dev/null +++ b/+num/ifbetween.m @@ -0,0 +1,4 @@ +function i = ifbetween(a,vec) +i = a> vec(1) & a < vec(2); +end + diff --git a/+num/ifinrange.m b/+num/ifinrange.m new file mode 100644 index 0000000..ac31806 --- /dev/null +++ b/+num/ifinrange.m @@ -0,0 +1,5 @@ +function i = ifinrange(a, vec) + +i = a>=vec(1) & a <= vec(2); + +end \ No newline at end of file diff --git a/+num/ifwithin.m b/+num/ifwithin.m new file mode 100644 index 0000000..9160584 --- /dev/null +++ b/+num/ifwithin.m @@ -0,0 +1,5 @@ +function i = ifwithin(varargin) + +i = gen.ifinrange(varargin{:}); + +end \ No newline at end of file diff --git a/+stats/range.m b/+stats/range.m new file mode 100644 index 0000000..df21ddf --- /dev/null +++ b/+stats/range.m @@ -0,0 +1,15 @@ +function r = range(arr, dim) + +if nargin == 1, dim = 'all'; end + +if isnumeric(dim) + r = cat(dim,min(arr,[],dim), max(arr,[],dim)); +elseif strcmp(dim,"all") + + r = [min(arr,[],dim), max(arr,[],dim)]; + +else + error("dimension needs to be a numeric value or 'all'."); +end + +end \ No newline at end of file diff --git a/+stats/robust_z.m b/+stats/robust_z.m new file mode 100644 index 0000000..1e7b844 --- /dev/null +++ b/+stats/robust_z.m @@ -0,0 +1,63 @@ +function varargout = robust_z(arr, dim, var_method, varargin) + +%{ +Robust Z-score: +Calculated using the median and median absolute deviation (MAD).   +Formula: RobustZ= (x−median)/MAD +​ +The median and MAD are less affected by outliers, making the robust Z-score more reliable when dealing with data that may contain extreme values.   + +Median: The middle value in a sorted dataset.   +Median Absolute Deviation (MAD): The median of the absolute differences between each data point and the median. +   +%} + + +if nargin == 1 || isempty(dim) + + dim = 'all'; + +end + + +m = median(arr, dim, 'omitnan'); + +if nargin < 3 + + var_method = 'mad'; + +end + + +switch var_method + + case 'mad' + + var = median(abs(arr - m), dim, 'omitnan'); + + case 'iqr' + + var = iqr(arr, dim); + + case 'std' + + if isempty(varargin) + scalar = 1/1.349; % iqr calculated from normal standard deviation + else + scalar = varargin{1}; + end + + var = scalar * iqr(arr, dim); + + otherwise + error("Unknown method to compute variance. Pick one of the following: 'mad', 'robust_std', 'iqr'") +end + +varargout = cell(1, nargout); +varargout{1} = (arr - m) ./ var; + +if nargout > 1, varargout{2} = m; end +if nargout > 2, varargout{3} = var; end + +end +