以完全矢量化的方式将矩阵中的零(或 NAN)替换为前一个元素逐行或按列



我需要用前一个元素逐行替换矩阵中的零(或 NaN),所以基本上我需要这个矩阵 X

[0,1,2,2,1,0;  
5,6,3,0,0,2;  
0,0,1,1,0,1]  

要变成这样:

[0,1,2,2,1,1;  
5,6,3,3,3,2;  
0,0,1,1,1,1],  

请注意,如果第一行元素为零,它将保持原样。

我知道这已经以矢量化的方式解决了单行或列向量的问题,这是最好的方法之一:

id = find(X);         
X(id(2:end)) = diff(X(id));       
Y = cumsum(X)  

问题在于 Matlab/Octave 中矩阵的索引是连续的并且按列递增,因此它适用于单行或单列,但不能应用相同的确切概念,但需要用多行修改,因为每个原始/列都重新开始,必须被视为独立。我已经尽力了,用谷歌搜索了整个谷歌,但找不到出路。如果我在循环中应用同样的想法,它会变得太慢,因为我的矩阵至少包含 3000 行。谁能帮我解决这个问题?

每行中隔离零的特殊情况

您可以使用find的双输出版本来定位除第一列之外的所有列中的零和 NaN,然后使用线性索引用这些条目的行前值填充这些条目:

[ii jj] = find( (X(:,2:end)==0) | isnan(X(:,2:end)) );
X(ii+jj*size(X,1)) = X(ii+(jj-1)*size(X,1));

一般情况(每行允许连续零)

X(isnan(X)) = 0; %// handle NaN's and zeros in a unified way
aux = repmat(2.^(1:size(X,2)), size(X,1), 1) .* ...
[ones(size(X,1),1) logical(X(:,2:end))]; %// positive powers of 2 or 0
col = floor(log2(cumsum(aux,2))); %// col index
ind = bsxfun(@plus, (col-1)*size(X,1), (1:size(X,1)).'); %'// linear index
Y = X(ind);

诀窍是利用矩阵aux,如果X的相应条目为 0 并且其列号大于 1,则包含 0;否则包含 2 提高到列号。因此,将cumsum逐行应用于此矩阵,取log2并向下舍入(矩阵col)给出最右边的非零条目的列索引,直到当前条目,对于每一行(所以这是一种逐行的"累积最大值"函数)。只需要从列号转换为线性索引(使用bsxfun;也可以使用sub2ind完成)并使用它来索引X

这仅适用于中等大小的X。对于大型,代码使用的 2 的幂会很快接近realmax,并产生不正确的索引。

例:

X =
0     1     2     2     1     0     0
5     6     3     0     0     2     3
1     1     1     1     0     1     1

>> Y
Y =
0     1     2     2     1     1     1
5     6     3     3     3     2     3
1     1     1     1     1     1     1

您可以按如下方式概括自己的解决方案:

Y = X.';                                       %'// Make a transposed copy of X
Y(isnan(Y)) = 0;
idx = find([ones(1, size(X, 1)); Y(2:end, :)]);
Y(idx(2:end)) = diff(Y(idx));
Y = reshape(cumsum(Y(:)), [], size(X, 1)).';   %'// Reshape back into a matrix

其工作原理是将输入数据视为长向量,应用原始解决方案,然后将结果重新塑造回矩阵。第一列始终被视为非零列,以便值不会在各行中传播。另请注意,原始矩阵被转置,以便按行主顺序转换为向量。

Eitan答案的修改版本,以避免跨行传播值:

Y = X'; %'
tf = Y > 0;
tf(1,:) = true;
idx = find(tf);
Y(idx(2:end)) = diff(Y(idx));
Y = reshape(cumsum(Y(:)),fliplr(size(X)))';
x=[0,1,2,2,1,0;
5,6,3,0,1,2;
1,1,1,1,0,1];
%Do it column by column is easier
x=x';
rm=0;
while 1
%fields to replace
l=(x==0);
%do nothing for the first row/column
l(1,:)=0;
rm2=sum(sum(l));
if rm2==rm
%nothing to do
break;
else
rm=rm2;
end
%replace zeros
x(l) = x(find(l)-1);
end
x=x';

我有一个函数用于填充 NaN 的类似问题。这可能可以进一步削减或加速 - 它是从预先存在的代码中提取的,这些代码具有更多功能(向前/向后填充,最大距离等)。

X = [
0 1 2 2 1 0
5 6 3 0 0 2
1 1 1 1 0 1
0 0 4 5 3 9
];
X(X == 0) = NaN;
Y = nanfill(X,2);
Y(isnan(Y)) = 0
function y = nanfill(x,dim)
if nargin < 2, dim = 1; end
if dim == 2, y = nanfill(x',1)'; return; end
i = find(~isnan(x(:)));
j = 1:size(x,1):numel(x);
j = j(ones(size(x,1),1),:);
ix = max(rep([1; i],diff([1; i; numel(x) + 1])),j(:));
y = reshape(x(ix),size(x));
function y = rep(x,times)
i = find(times);
if length(i) < length(times), x = x(i); times = times(i); end
i = cumsum([1; times(:)]);
j = zeros(i(end)-1,1);
j(i(1:end-1)) = 1;
y = x(cumsum(j));

相关内容

  • 没有找到相关文章

最新更新