8

我正在寻找有关如何优雅地解决以下问题的建议。尽管在我的具体情况下性能不是问题,但我会感谢有关良好实践的评论。

提前致谢!

简短版本:

我试图根据一些逻辑对矩阵行进行平均,同时忽略 NaN 值。我目前拥有的代码没有按照我想要的方式处理 NaN 值。

长版:

我的数据是按以下方式构建的:

  • 单个(第一)列“箱”。每个 bin 的行数不是恒定的。箱不必是整数。行是预先排序的。
  • 可变数量的数据列,可能包括 NaN。

这是一个例子:

DATA = [...
180     NaN     NaN     1.733
180     NaN     NaN     1.703
200     0.720   2.117   1.738
200     0.706   2.073   1.722
200     0.693   2.025   1.723
200     NaN     NaN     1.729
210     NaN     NaN     1.820
210     NaN     NaN     1.813
210     NaN     NaN     1.805
240     NaN     NaN     1.951
240     NaN     NaN     1.946
240     NaN     NaN     1.946
270     NaN     NaN     2.061
270     NaN     NaN     2.052
300     0.754   2.356   2.103
300     0.758   2.342   2.057
300     NaN     NaN     2.066
300     NaN     NaN     2.066 ];

所需的结果是一个矩阵,其中包含第一列中唯一的“bins”,其余部分表示“未被 NaNs 破坏”,例如:

  • 如果对于特定列+bin,只有 NaN(在上面的示例中:第一个数据列+bin 210) - 结果将是 NaN。
  • 如果对于特定的列+bin,存在 NaN 和数字的混合,则结果将是有效数字的平均值。在上面的例子中:第一个数据列+bin 200 应该给出(0.720+0.706+0.693)/3=0.7063——注意这个列+bin 除以 3(而不是 4)。

这是上述示例的预期结果:

RES = [...
180     NaN     NaN     1.718
200     0.7063  2.072   1.728
210     NaN     NaN     1.812
240     NaN     NaN     1.948
270     NaN     NaN     2.056
300     0.756   2.349   2.074 ];

到目前为止我尝试了什么:

这是我设法从多个来源编译的一些代码。它适用于仅包含 NaN 或数字的 column+bin。

nDataCols=size(DATA,2)-1;
[u,m,n] = unique(DATA(:,1));
sz = size(m);
N=accumarray(n,1,sz);

RES(length(u),nDataCols) = 0; %Preallocation

for ind1 = 1:nDataCols
    RES(:,ind1)=accumarray(n,DATA(:,ind1+1),sz)./N;
end

RES= [u,RES];

这是我目前得到的:

RES = [...
180     NaN     NaN     1.718
200     NaN     NaN     1.728
210     NaN     NaN     1.812
240     NaN     NaN     1.948
270     NaN     NaN     2.056
300     NaN     NaN     2.074 ];

ps

  1. 如果有任何机会使用电子表格软件(如 MS Excel)更容易做到这一点 - 我很想听听想法。
  2. 在每列的基础上进行计算是我目前关于如何处理这个问题的想法。我只是想知道是否有一种方法可以概括它以立即获取完整的矩阵。
4

2 回答 2

5

一种可能的方法:在第一列中查找更改(利用它已预先排序的事实)并应用于nanmean每个行块:

ind = find(diff([-inf; (DATA(:,1)); inf])~=0); %// value changed: start of block
r = arrayfun(@(n) nanmean(DATA(ind(n):ind(n+1)-1,:)), 1:numel(ind)-1, 'uni', 0);
RES = vertcat(r{:});

您可以用arrayfun显式循环替换。这可能更快,并且避免了单元格引入的开销:

ind = find(diff([-inf; (DATA(:,1)); inf])~=0); %// value changed: start of block
RES = zeros(numel(ind)-1, size(DATA,2)); %// preallocate
for n = 1:numel(ind)-1 %// loop over blocks
    RES(n,:) = nanmean(DATA(ind(n):ind(n+1)-1,:));
end

您的方法也可以使用。您只需要accumarray使用函数的句柄进行nanmean调用。这不需要对第一列进行预排序。

nDataCols = size(DATA,2)-1;
[u, ~, n] = unique(DATA(:,1));
RES = zeros(length(u), nDataCols); %// Preallocation
for ind1 = 1:nDataCols
    RES(:,ind1) = accumarray(n, DATA(:,ind1+1), [], @nanmean);
end
RES = [u, RES];
于 2014-07-13T15:38:17.850 回答
0

这是另一种解决方案,尽管效率极低。此外,输出数组会将所有NaN值设置为 0。假设这对学术研究有好处。以下是我执行的步骤:

  1. 对于第一列中的每个 ID,找到一个唯一列表。
  2. 对于其他列,将每一列拆分为一个元胞数组。
  3. 创建一个新的元胞数组,其中每列附加此元胞数组中每个元素的第一列
  4. 为每个包含NaN值的元胞数组过滤掉那些行
  5. 对于过滤结果的每一列,accumarray作为mean函数句柄运行。
  6. 使用步骤 #1 中的 ID,索引每个accumarray结果并转换回矩阵

%// Step #1
num = unique(DATA(:,1));

%// Step #2
cells = mat2cell(DATA, size(DATA,1), ones(size(DATA,2),1));

%// Step #3
cellsAppend = cellfun(@(x) [DATA(:,1) x], cells(2:end), 'uni', false);

%// Step #4
cellsNonNaN = cellfun(@(x) x(~isnan(x(:,2)),:), cellsAppend , 'uni', false);

%// Step #5
cellsMean = cellfun(@(x) accumarray(x(:,1), x(:,2), [], @mean), cellsNonNaN, 'uni', false);

%// Step #6
selectCells = cellfun(@(x) x(num), append3, 'uni', false);
RES = [num cell2mat(selectCells)];

结果是:

RES = 

180.0000         0         0    1.7180
200.0000    0.7063    2.0717    1.7280
210.0000         0         0    1.8127
240.0000         0         0    1.9477
270.0000         0         0    2.0565
300.0000    0.7560    2.3490    2.0730

正如你所看到的,效率很低——尤其是cellfun我打了这么多电话,但我想这仍然是一个学术例子!

于 2014-07-13T17:33:19.140 回答