Como posso aplicar uma função a cada linha / coluna de uma matriz no MATLAB?

106

Você pode aplicar uma função a cada item em um vetor dizendo, por exemplo v + 1, ou pode usar a função arrayfun. Como posso fazer isso para cada linha / coluna de uma matriz sem usar um loop for?

FurtiveFelon
fonte

Respostas:

73

Muitas operações integradas gostam sume prodjá são capazes de operar em linhas ou colunas, portanto, você pode refatorar a função que está aplicando para tirar vantagem disso.

Se essa não for uma opção viável, uma maneira de fazer isso é coletar as linhas ou colunas em células usando mat2cellou e num2cell, em seguida, use cellfunpara operar na matriz de células resultante.

Como exemplo, digamos que você deseja somar as colunas de uma matriz M. Você pode fazer isso simplesmente usando sum:

M = magic(10);           %# A 10-by-10 matrix
columnSums = sum(M, 1);  %# A 1-by-10 vector of sums for each column

E aqui está como você faria isso usando a opção num2cell/ mais complicada cellfun:

M = magic(10);                  %# A 10-by-10 matrix
C = num2cell(M, 1);             %# Collect the columns into cells
columnSums = cellfun(@sum, C);  %# A 1-by-10 vector of sums for each cell
gnovice
fonte
17
Eu testaria o desempenho dessa abordagem para qualquer caso particular em relação ao loop for simples, que pode ser mais rápido do que converter uma matriz em array de células. Use tic / tac wrap para testar.
yuk
5
@yuk: Acho que você quis dizer "tic / toc". ;)
gnovice
4
@gnovice, talvez yuk tenha feito alguma mágica e atribuído tak = toc. Em um idioma em que true = falseé uma declaração válida, tenho certeza de que há uma maneira de fazer isso (:
chessofnerd
1
@Argyll: Determinar qual abordagem é mais eficiente vai depender do tipo de função que você deseja aplicar, do tamanho da matriz, etc. Em suma, é provável que dependa do problema. Na verdade, às vezes um bom e velho loop for pode ser a escolha mais rápida.
gnovice
2
@gnovice, sugiro uma edição para sum(M, 1). Iniciantes podem pensar que sumpodem ser usados ​​dessa forma para matrizes de tamanhos arbitrários e então ficar perplexos quando a matriz um dia for usada 1-by-n.
Stewie Griffin de
24

Você pode querer a função mais obscura do Matlab, bsxfun . A partir da documentação do Matlab, bsxfun "aplica a operação binária elemento a elemento especificada pelo identificador de função divertido para os arrays A e B, com a expansão singleton habilitada."

@gnovice afirmou acima que soma e outras funções básicas já operam na primeira dimensão não única (ou seja, linhas se houver mais de uma linha, colunas se houver apenas uma linha, ou dimensões superiores se as dimensões inferiores tiverem tamanho == 1 ) No entanto, bsxfun funciona para qualquer função, incluindo (e especialmente) funções definidas pelo usuário.

Por exemplo, digamos que você tenha uma matriz A e um vetor linha BEg, digamos:

A = [1 2 3;
     4 5 6;
     7 8 9]
B = [0 1 2]

Você quer uma função power_by_col que retorna em um vetor C todos os elementos em A à potência da coluna correspondente de B.

No exemplo acima, C é uma matriz 3x3:

C = [1^0 2^1 3^2;
     4^0 5^1 6^2;
     7^0 8^1 9^2]

ie,

C = [1 2 9;
     1 5 36;
     1 8 81]

Você poderia fazer isso usando a força bruta usando repmat:

C = A.^repmat(B, size(A, 1), 1)

Ou você pode fazer isso da maneira clássica usando bsxfun, que cuida internamente da etapa repmat:

C = bsxfun(@(x,y) x.^y, A, B)

Portanto, o bsxfun economiza algumas etapas (você não precisa calcular explicitamente as dimensões de A). No entanto, em alguns testes informais meus, descobri que repmat é quase duas vezes mais rápido se a função a ser aplicada (como minha função de potência, acima) for simples. Portanto, você precisará escolher se deseja simplicidade ou velocidade.

Daniel Golden
fonte
21

Não posso comentar o quão eficiente isso é, mas aqui está uma solução:

applyToGivenRow = @(func, matrix) @(row) func(matrix(row, :))
applyToRows = @(func, matrix) arrayfun(applyToGivenRow(func, matrix), 1:size(matrix,1))'

% Example
myMx = [1 2 3; 4 5 6; 7 8 9];
myFunc = @sum;

applyToRows(myFunc, myMx)
Alex
fonte
Uma resposta mais genérica é fornecida aqui .
Wok de
11

Com base na resposta de Alex , aqui está uma função mais genérica:

applyToGivenRow = @(func, matrix) @(row) func(matrix(row, :));
newApplyToRows = @(func, matrix) arrayfun(applyToGivenRow(func, matrix), 1:size(matrix,1), 'UniformOutput', false)';
takeAll = @(x) reshape([x{:}], size(x{1},2), size(x,1))';
genericApplyToRows = @(func, matrix) takeAll(newApplyToRows(func, matrix));

Aqui está uma comparação entre as duas funções:

>> % Example
myMx = [1 2 3; 4 5 6; 7 8 9];
myFunc = @(x) [mean(x), std(x), sum(x), length(x)];
>> genericApplyToRows(myFunc, myMx)

ans =

     2     1     6     3
     5     1    15     3
     8     1    24     3

>> applyToRows(myFunc, myMx)
??? Error using ==> arrayfun
Non-scalar in Uniform output, at index 1, output 1.
Set 'UniformOutput' to false.

Error in ==> @(func,matrix)arrayfun(applyToGivenRow(func,matrix),1:size(matrix,1))'
Wok
fonte
4

Somando-se à natureza evolutiva da resposta a esta pergunta, começando com r2016b, o MATLAB expandirá implicitamente as dimensões do singleton, removendo a necessidade de bsxfunem muitos casos.

Das notas de lançamento do r2016b :

Expansão implícita: Aplicar operações e funções de elemento a matrizes com expansão automática de dimensões de comprimento 1

A expansão implícita é uma generalização da expansão escalar. Com a expansão escalar, um escalar se expande para ter o mesmo tamanho de outra matriz para facilitar as operações em elementos. Com a expansão implícita, os operadores e funções element-wise listados aqui podem expandir implicitamente suas entradas para que tenham o mesmo tamanho, desde que os arrays tenham tamanhos compatíveis. Dois arrays têm tamanhos compatíveis se, para cada dimensão, os tamanhos das dimensões das entradas forem iguais ou um deles for 1. Consulte Tamanhos de array compatíveis para operações básicas e Array vs. Operações de matriz para obter mais informações.

Element-wise arithmetic operators+, -, .*, .^, ./, .\

Relational operators<, <=, >, >=, ==, ~=

Logical operators&, |, xor

Bit-wise functionsbitand, bitor, bitxor

Elementary math functionsmax, min, mod, rem, hypot, atan2, atan2d

Por exemplo, você pode calcular a média de cada coluna em uma matriz A e, em seguida, subtrair o vetor de valores médios de cada coluna com A - média (A).

Anteriormente, essa funcionalidade estava disponível por meio da função bsxfun. Agora, é recomendado que você substitua a maioria dos usos de bsxfun por chamadas diretas para as funções e operadores que suportam a expansão implícita. Em comparação com o uso de bsxfun, a expansão implícita oferece velocidade mais rápida, melhor uso de memória e legibilidade de código aprimorada.

Craigim
fonte
2

Nenhuma das respostas acima funcionou "fora da caixa" para mim, no entanto, a seguinte função, obtida copiando as ideias das outras respostas funciona:

apply_func_2_cols = @(f,M) cell2mat(cellfun(f,num2cell(M,1), 'UniformOutput',0));

Ele pega uma função fe a aplica a todas as colunas da matrizM .

Então, por exemplo:

f = @(v) [0 1;1 0]*v + [0 0.1]';
apply_func_2_cols(f,[0 0 1 1;0 1 0 1])

 ans =

   0.00000   1.00000   0.00000   1.00000
   0.10000   0.10000   1.10000   1.10000
patapouf_ai
fonte
1

Com as versões recentes do Matlab, você pode usar a estrutura de dados da tabela a seu favor. Há até uma operação 'rowfun', mas achei mais fácil fazer isso:

a = magic(6);
incrementRow = cell2mat(cellfun(@(x) x+1,table2cell(table(a)),'UniformOutput',0))

ou aqui está um mais antigo que eu tinha que não requer tabelas, para versões mais antigas do Matlab.

dataBinner = cell2mat(arrayfun(@(x) Binner(a(x,:),2)',1:size(a,1),'UniformOutput',0)')
Jordânia
fonte
1

A resposta aceita parece ser primeiro converter em células e depois usar cellfunpara operar em todas as células. Não conheço a aplicação específica, mas no geral acho que usar bsxfunpara operar sobre a matriz seria mais eficiente. Basicamente, bsxfunaplica uma operação elemento por elemento em duas matrizes. Portanto, se você quiser multiplicar cada item de um n x 1vetor por cada item de um m x 1vetor para obter uma n x mmatriz, poderá usar:

vec1 = [ stuff ];    % n x 1 vector
vec2 = [ stuff ];    % m x 1 vector
result = bsxfun('times', vec1.', vec2);

Isso lhe dará uma matriz chamada resultem que a entrada (i, j) será o iésimo elemento de vec1multiplicado pelo j-ésimo elemento de vec2.

Você pode usar bsxfunpara todos os tipos de funções internas e pode declarar as suas próprias. A documentação tem uma lista de muitas funções embutidas, mas basicamente você pode nomear qualquer função que aceite dois arrays (vetor ou matriz) como argumentos e fazê-la funcionar.

Engenheiro
fonte
-1

Tropecei nesta pergunta / resposta enquanto procurava como calcular as somas das linhas de uma matriz.

Eu gostaria apenas de acrescentar que a função SUM do Matlab, na verdade, tem suporte para somar para uma dada dimensão, ou seja, uma matriz padrão com duas dimensões.

Portanto, para calcular as somas das colunas:

colsum = sum(M) % or sum(M, 1)

e para as somas de linha, basta fazer

rowsum = sum(M, 2)

Minha aposta é que isso é mais rápido do que programar um loop for e converter para células :)

Tudo isso pode ser encontrado na ajuda do matlab para SUM.

nover
fonte
7
a capacidade de aplicar SUM ao longo de uma dada dimensão foi mencionada na primeira frase da resposta original a esta pergunta. A resposta então passou a abordar o caso em que a capacidade de escolher uma dimensão ainda não está embutida na função. Você está certo, entretanto, que usar as opções de seleção de dimensão incorporadas - quando elas estão disponíveis - é quase sempre mais rápido do que um loop for ou a conversão em células.
cjh
É verdade que, porém, a resposta acima me remeteu de volta à documentação do matlab, pois não precisava de tanta fantasia, então só queria compartilhar e salvar outras pessoas, que precisavam de uma solução simples, de pesquisar.
nover
-2

se você sabe o comprimento de suas linhas, pode fazer algo assim:

a=rand(9,3);
b=rand(9,3); 
arrayfun(@(x1,x2,y1,y2,z1,z2) line([x1,x2],[y1,y2],[z1,z2]) , a(:,1),b(:,1),a(:,2),b(:,2),a(:,3),b(:,3) )
Stefan
fonte
2
Para quem vir esta resposta: Esta não é a maneira de fazer isso! Esta não é a maneira de fazer nada no MATLAB!
Stewie Griffin de