Заставляя столбцы матрицы в разных пределах

У меня есть матрица с именем l размером 20X3. Я хотел сделать следующее: предположим, у меня есть следующие ограничения:

l1_max=20; l1_min=0.5;
l2_max=20; l2_min=0.5;
mu_max=20; mu_min=0.5;

Я хотел заставить все элементы матрицы l в определенных пределах. Значения 1-го столбца в пределах l1_max & l1_min. Значения 2-го столбца в пределах l2_max & l2_min. Значения 3-го столбца в пределах mu_max и mu_min.

То, что я сделал, было так:

for k=1:20
    if l(k,1)>l1_max 
        l(k,1) = l1_max;
    elseif l(k,1)<l1_min
        l(k,1) = l1_min;
    end

    if l(k,2)>l2_max 
        l(k,2) = l2_max;
    elseif l(k,2)<l2_min
        l(k,2) = l2_min;
    end

    if l(k,3)>mu_max 
        l(k,3) = mu_max;
    elseif l(k,3)<mu_min
        l(k,3) = mu_min;
    end
end

Можно ли сделать это лучше?

3 ответа

Решение

Вам не нужно перебирать строки, используйте векторизованные операции над целыми столбцами:

l(l(:, 1) > l1_max, 1) = l1_max;
l(l(:, 1) < l1_min, 1) = l1_min;

, подобным образом:

l(l(:, 2) > l2_max, 2) = l2_max;
l(l(:, 2) < l2_min, 2) = l2_min;
l(l(:, 3) > l2_max, 3) = mu_max;
l(l(:, 3) < l2_min, 3) = mu_min;

Альтернативный метод, который напоминает идею Баса, заключается в применении min а также max следующее:

l(:, 1) = max(min(l(:, 1), l1_max), l1_min);
l(:, 2) = max(min(l(:, 2), l2_max), l2_min);
l(:, 3) = max(min(l(:, 3), mu_max), mu_min);

Похоже, что оба подхода имеют сопоставимую производительность.

Вам даже не нужно перебирать все столбцы, операцию над всей матрицей можно выполнить за 2 вызова bsxfunнезависимо от количества столбцов:

column_max = [l1_max, l2_max, mu_max];
column_min = [l1_min, l2_min, mu_min];

M = bsxfun(@min, M, column_max); %clip to maximum
M = bsxfun(@max, M, column_min); %clip to minimum

Это использует два трюка: чтобы обрезать значение между min_val и max_val, вы можете сделать clipped_x = min(max(x, min_val), max_val), Другой трюк состоит в том, чтобы использовать несколько непонятный bsxfun, которая применяет функцию после выполнения одноэлементного расширения. Когда вы используете его на двух матрицах, перед применением функции он "выдавливает" наименьший размер, равный наибольшему, поэтому приведенный выше пример эквивалентен M = min(M, repmat(column_max, size(M, 1), 1)), но, надеюсь, рассчитывается более эффективным способом.

Ниже приведен тест для тестирования различных методов, которые обсуждались до сих пор. Я использую функцию TIMEIT, найденную на файловой бирже.

function [t,v] = testClampColumns()
    % data and limits ranges for each column
    r = 10000; c = 500;
    M = randn(r,c);
    mn = -1.1 * ones(1,c);
    mx = +1.1 * ones(1,c);

    % functions
    f = { ...
        @() clamp1(M,mn,mx) ;
        @() clamp2(M,mn,mx) ;
        @() clamp3(M,mn,mx) ;
        @() clamp4(M,mn,mx) ;
        @() clamp5(M,mn,mx) ;
    };

    % timeit and check results
    t = cellfun(@timeit, f, 'UniformOutput',true);
    v = cellfun(@feval, f, 'UniformOutput',false);
    assert(isequal(v{:}))
end

Даны следующие реализации:

1) Зациклите все значения и сравните их с минимальным / максимальным

function M = clamp1(M, mn, mx)
    for j=1:size(M,2)
        for i=1:size(M,1)
            if M(i,j) > mx(j)
                M(i,j) = mx(j);
            elseif M(i,j) < mn(j)
                M(i,j) = mn(j);
            end
        end
    end
end

2) сравнить каждый столбец с мин / макс

function M = clamp2(M, mn, mx)
    for j=1:size(M,2)
        M(M(:,j) < mn(j), j) = mn(j);
        M(M(:,j) > mx(j), j) = mx(j);
    end
end

3) обрезать каждый столбец до предела

function M = clamp3(M, mn, mx)
    for j=1:size(M,2)
        M(:,j) = min(max(M(:,j), mn(j)), mx(j));
    end
end

4) векторизованная версия усечения в (3)

function M = clamp4(M, mn, mx)
    M = bsxfun(@min, bsxfun(@max, M, mn), mx);
end

5) сравнение абсолютных значений: -a |x| <а

(Примечание: это не применимо к вашему случаю, так как требует симметричного диапазона пределов. Я включил это только для полноты. Кроме того, это оказывается самый медленный метод.)

function M = clamp5(M, mn, mx)
    assert(isequal(-mn,mx), 'Only works when -mn==mx')
    idx = bsxfun(@gt, abs(M), mx);
    v = bsxfun(@times, sign(M), mx);
    M(idx) = v(idx);
end

Время, которое я получаю на моей машине с входной матрицей размером 10000x500:

>> t = testClampColumns
t =
    0.2424
    0.1267
    0.0569
    0.0409
    0.2868

Я бы сказал, что все вышеперечисленные методы достаточно быстрые, с bsxfun решение является самым быстрым:)

Другие вопросы по тегам