Come posso applicare una funzione a ogni riga / colonna di una matrice in MATLAB?


106

Puoi applicare una funzione a ogni elemento in un vettore dicendo, ad esempio v + 1, oppure puoi usare la funzione arrayfun. Come posso farlo per ogni riga / colonna di una matrice senza utilizzare un ciclo for?

Risposte:


73

Molte operazioni incorporate come sume prodsono già in grado di operare su righe o colonne, quindi potresti essere in grado di refactoring della funzione che stai applicando per trarne vantaggio.

Se questa non è un'opzione praticabile, un modo per farlo è raccogliere le righe o le colonne in celle utilizzando mat2cello num2cell, quindi utilizzare cellfunper operare sulla matrice di celle risultante.

Ad esempio, supponiamo che tu voglia sommare le colonne di una matrice M. Puoi farlo semplicemente usando sum:

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

Ed ecco come lo faresti usando l' opzione più complicata num2cell/ 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

17
Vorrei testare le prestazioni di questo approccio per qualsiasi caso particolare rispetto al semplice ciclo for, che potrebbe essere più veloce della conversione di una matrice in un array di celle. Usa l'involucro tic / tac per testare.
yuk

5
@yuk: Penso che intendessi "tic / toc". ;)
gnovice

4
@gnovice, forse yuk ha fatto un po 'di magia e ha assegnato tak = toc. In una lingua in cui true = falseè un'affermazione valida, sono sicuro che c'è un modo per farlo (:
chessofnerd

1
@Argyll: determinare quale approccio è più efficiente dipenderà dal tipo di funzione che si desidera applicare, dalla dimensione della matrice, ecc. In breve, è probabile che dipenda dal problema. In effetti, a volte un buon vecchio ciclo for può essere la scelta più veloce.
gnovice

2
@gnovice, suggerisco una modifica a sum(M, 1). I principianti potrebbero pensare di sumpoter essere usati in questo modo per matrici di dimensioni arbitrarie e poi rimanere perplessi quando la matrice un giorno lo è 1-by-n.
Stewie Griffin

24

Potresti volere la più oscura funzione Matlab bsxfun . Dalla documentazione di Matlab, bsxfun "applica l'operazione binaria elemento per elemento specificata dalla funzione handle fun agli array A e B, con l'espansione singleton abilitata."

@gnovice ha affermato sopra che la somma e altre funzioni di base operano già sulla prima dimensione non singleton (cioè, righe se c'è più di una riga, colonne se c'è solo una riga, o dimensioni superiori se le dimensioni inferiori hanno tutte dimensione == 1 ). Tuttavia, bsxfun funziona per qualsiasi funzione, comprese (e soprattutto) le funzioni definite dall'utente.

Ad esempio, supponiamo di avere una matrice A e un vettore riga BEg, diciamo:

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

Vuoi una funzione power_by_col che restituisca in un vettore C tutti gli elementi in A alla potenza della corrispondente colonna di B.

Dall'esempio sopra, C è una matrice 3x3:

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

vale a dire,

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

Puoi farlo in modo brute force usando repmat:

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

Oppure potresti farlo in modo classico usando bsxfun, che internamente si occupa del passaggio repmat:

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

Quindi bsxfun ti fa risparmiare alcuni passaggi (non è necessario calcolare esplicitamente le dimensioni di A). Tuttavia, in alcuni miei test informali, risulta che repmat è circa il doppio più veloce se la funzione da applicare (come la mia funzione di alimentazione, sopra) è semplice. Quindi dovrai scegliere se vuoi semplicità o velocità.


21

Non posso commentare quanto sia efficiente, ma ecco una soluzione:

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)

Una risposta più generico è data qui .
Wok

11

Basandosi sulla risposta di Alex , ecco una funzione più generica:

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));

Ecco un confronto tra le due funzioni:

>> % 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))'


4

Aggiungendo alla natura in evoluzione della risposta a questa domanda, a partire da r2016b, MATLAB espanderà implicitamente le dimensioni singleton, eliminando la necessità bsxfunin molti casi.

Dalle note sulla versione r2016b :

Espansione implicita: applica operazioni e funzioni basate sugli elementi agli array con espansione automatica delle dimensioni di lunghezza 1

L'espansione implicita è una generalizzazione dell'espansione scalare. Con l'espansione scalare, uno scalare si espande per avere le stesse dimensioni di un altro array per facilitare le operazioni a livello di elemento. Con l'espansione implicita, gli operatori e le funzioni degli elementi elencati qui possono espandere implicitamente i loro input per avere la stessa dimensione, purché gli array abbiano dimensioni compatibili. Due array hanno dimensioni compatibili se, per ogni dimensione, le dimensioni delle dimensioni degli input sono uguali o una di esse è 1. Per ulteriori informazioni, vedere Dimensioni array compatibili per operazioni di base e Operazioni array vs.

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

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

Logical operators&, |, xor

Bit-wise functionsbitand, bitor, bitxor

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

Ad esempio, è possibile calcolare la media di ogni colonna in una matrice A, quindi sottrarre il vettore dei valori medi da ciascuna colonna con A - media (A).

In precedenza, questa funzionalità era disponibile tramite la funzione bsxfun. Si consiglia ora di sostituire la maggior parte degli usi di bsxfun con chiamate dirette alle funzioni e agli operatori che supportano l'espansione implicita. Rispetto all'utilizzo di bsxfun, l'espansione implicita offre una maggiore velocità, un migliore utilizzo della memoria e una migliore leggibilità del codice.


2

Nessuna delle risposte precedenti ha funzionato "fuori dagli schemi" per me, tuttavia, la seguente funzione, ottenuta copiando le idee delle altre risposte, funziona:

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

Prende una funzione fe la applica a ogni colonna della matrice M.

Quindi per esempio:

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

1

Con le versioni recenti di Matlab, puoi utilizzare la struttura dei dati della tabella a tuo vantaggio. C'è anche un'operazione 'rowfun' ma ho trovato più semplice farlo:

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

oppure eccone uno più vecchio che avevo che non richiede tabelle, per le versioni precedenti di Matlab.

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

1

La risposta accettata sembra essere quella di convertire prima in celle e quindi utilizzare cellfunper operare su tutte le celle. Non conosco l'applicazione specifica, ma in generale penso che utilizzare bsxfunper operare sulla matrice sarebbe più efficiente. Fondamentalmente bsxfunapplica un'operazione elemento per elemento su due array. Quindi, se volessi moltiplicare ogni elemento in un n x 1vettore per ogni elemento in un m x 1vettore per ottenere un n x marray, potresti usare:

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

Questo ti darà una matrice chiamata in resultcui la voce (i, j) sarà l'ennesimo elemento di vec1moltiplicato per il jesimo elemento di vec2.

Puoi usarlo bsxfunper tutti i tipi di funzioni integrate e puoi dichiarare il tuo. La documentazione ha un elenco di molte funzioni incorporate, ma fondamentalmente puoi nominare qualsiasi funzione che accetta due array (vettore o matrice) come argomenti e farlo funzionare.


-1

Sono incappato in questa domanda / risposta mentre cercavo come calcolare le somme di riga di una matrice.

Vorrei solo aggiungere che la funzione SUM di Matlab ha effettivamente il supporto per la somma per una data dimensione, cioè una matrice standard con due dimensioni.

Quindi per calcolare le somme delle colonne fai:

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

e per le somme di riga, fallo semplicemente

rowsum = sum(M, 2)

La mia scommessa è che questo è più veloce sia della programmazione di un ciclo for che della conversione in celle :)

Tutto questo può essere trovato nell'help di matlab per SUM.


7
la capacità di applicare la SOMMA lungo una data dimensione è stata menzionata nella prima frase della risposta originale a questa domanda. La risposta ha poi affrontato il caso in cui la capacità di scegliere una dimensione non è già incorporata nella funzione. Hai ragione, però, che l'uso delle opzioni di selezione delle dimensioni integrate - quando sono disponibili - è quasi sempre più veloce di un ciclo for o della conversione in celle.
cjh

È vero che, tuttavia, la risposta sopra mi ha rimandato alla documentazione di matlab, poiché non avevo bisogno di tutta quella fantasia, quindi volevo solo condividere e salvare altri, che avevano bisogno della soluzione semplice, dalla ricerca.
nover

-2

se conosci la lunghezza delle tue righe puoi fare qualcosa del genere:

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) )

2
A chiunque veda questa risposta: non è questo il modo per farlo! Questo non è il modo per fare nulla in MATLAB!
Stewie Griffin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.