Comment puis-je appliquer une fonction à chaque ligne / colonne d'une matrice dans MATLAB?

106

Vous pouvez appliquer une fonction à chaque élément d'un vecteur en disant, par exemple v + 1, ou vous pouvez utiliser la fonction arrayfun. Comment puis-je le faire pour chaque ligne / colonne d'une matrice sans utiliser une boucle for?

FurtifFelon
la source

Réponses:

73

De nombreuses opérations intégrées telles que sumet prodsont déjà capables de fonctionner sur des lignes ou des colonnes, vous pourrez donc peut-être refactoriser la fonction que vous appliquez pour en tirer parti.

Si ce n'est pas une option viable, une façon de le faire est de collecter les lignes ou les colonnes dans des cellules à l'aide de mat2cellou num2cell, puis de l'utiliser cellfunpour opérer sur le tableau de cellules résultant.

À titre d'exemple, disons que vous souhaitez additionner les colonnes d'une matrice M. Vous pouvez le faire simplement en utilisant sum:

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

Et voici comment procéder en utilisant l' option num2cell/ plus compliquée 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
novice
la source
17
Je testerais les performances de cette approche pour n'importe quel cas particulier par rapport à une simple boucle for, qui pourrait être plus rapide que la conversion d'une matrice en tableau de cellules. Utilisez un enveloppement tic / tac pour tester.
yuk
5
@yuk: Je pense que vous vouliez dire "tic / toc". ;)
gnovice
4
@gnovice, peut-être que yuk a fait de la magie et a assigné tak = toc. Dans une langue où true = falseest une déclaration valide, je suis sûr qu'il y a un moyen de le faire (:
chessofnerd
1
@Argyll: Déterminer quelle approche est la plus efficace dépendra du type de fonction que vous vouliez appliquer, de la taille de la matrice, etc. En bref, cela dépend probablement du problème. En fait, une bonne vieille boucle for peut parfois être le choix le plus rapide.
gnovice
2
@gnovice, je suggère une modification à sum(M, 1). Les débutants pourraient penser que cela sumpeut être utilisé de cette façon pour des matrices de taille arbitraire, puis être perplexes lorsque la matrice l'est un jour 1-by-n.
Stewie Griffin
24

Vous voudrez peut-être la fonction plus obscure de Matlab bsxfun . D'après la documentation Matlab, bsxfun "applique l'opération binaire élément par élément spécifiée par le descripteur de fonction fun aux tableaux A et B, avec l'expansion des singleton activée."

@gnovice a déclaré ci-dessus que la somme et d'autres fonctions de base fonctionnent déjà sur la première dimension non singleton (c'est-à-dire, des lignes s'il y a plus d'une ligne, des colonnes s'il n'y a qu'une seule ligne, ou des dimensions supérieures si les dimensions inférieures ont toutes une taille == 1 ). Cependant, bsxfun fonctionne pour n'importe quelle fonction, y compris (et surtout) les fonctions définies par l'utilisateur.

Par exemple, disons que vous avez une matrice A et un vecteur ligne BEg, disons:

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

Vous voulez une fonction power_by_col qui renvoie dans un vecteur C tous les éléments de A à la puissance de la colonne correspondante de B.

D'après l'exemple ci-dessus, C est une matrice 3x3:

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

c'est à dire,

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

Vous pouvez le faire à la manière de la force brute en utilisant repmat:

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

Ou vous pouvez le faire de manière élégante en utilisant bsxfun, qui s'occupe en interne de l'étape repmat:

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

Ainsi, bsxfun vous évite quelques étapes (vous n'avez pas besoin de calculer explicitement les dimensions de A). Cependant, dans certains de mes tests informels, il s'avère que repmat est environ deux fois plus rapide si la fonction à appliquer (comme ma fonction power, ci-dessus) est simple. Vous devrez donc choisir si vous voulez la simplicité ou la rapidité.

Daniel Golden
la source
21

Je ne peux pas dire à quel point c'est efficace, mais voici une solution:

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
la source
Une réponse plus générique est donnée ici .
Wok
11

Sur la base de la réponse d'Alex , voici une fonction plus générique:

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

Voici une comparaison entre les deux fonctions:

>> % 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
la source
4

Ajoutant à la nature évolutive de la réponse à cette question, en commençant par r2016b, MATLAB élargira implicitement les dimensions singleton, supprimant le besoin bsxfundans de nombreux cas.

À partir des notes de version r2016b :

Expansion implicite: appliquez des opérations et des fonctions élémentaires aux tableaux avec expansion automatique des dimensions de longueur 1

L'expansion implicite est une généralisation de l'expansion scalaire. Avec l'expansion scalaire, un scalaire se développe pour avoir la même taille qu'un autre tableau pour faciliter les opérations élément par élément. Avec l'expansion implicite, les opérateurs et les fonctions élément-élément répertoriés ici peuvent implicitement étendre leurs entrées pour avoir la même taille, tant que les tableaux ont des tailles compatibles. Deux tableaux ont des tailles compatibles si, pour chaque dimension, les tailles de dimension des entrées sont identiques ou si l'une d'elles est 1. Pour plus d'informations, reportez-vous aux sections Tailles de matrice compatibles pour les opérations de base et Opérations sur les tableaux et les matrices.

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

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

Logical operators&, |, xor

Bit-wise functionsbitand, bitor, bitxor

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

Par exemple, vous pouvez calculer la moyenne de chaque colonne dans une matrice A, puis soustraire le vecteur des valeurs moyennes de chaque colonne avec A - moyenne (A).

Auparavant, cette fonctionnalité était disponible via la fonction bsxfun. Il est maintenant recommandé de remplacer la plupart des utilisations de bsxfun par des appels directs aux fonctions et aux opérateurs prenant en charge l'expansion implicite. Par rapport à l'utilisation de bsxfun, l'expansion implicite offre une vitesse plus rapide, une meilleure utilisation de la mémoire et une meilleure lisibilité du code.

craigim
la source
2

Aucune des réponses ci-dessus n'a fonctionné "hors de la boîte" pour moi, cependant, la fonction suivante, obtenue en copiant les idées des autres réponses fonctionne:

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

Il prend une fonction fet l'applique à chaque colonne de la matrice M.

Donc par exemple:

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
la source
1

Avec les versions récentes de Matlab, vous pouvez utiliser la structure de données Table à votre avantage. Il y a même une opération `` rowfun '' mais j'ai trouvé plus simple de le faire:

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

ou voici un ancien que j'avais qui ne nécessite pas de tables, pour les anciennes versions de Matlab.

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

La réponse acceptée semble être de convertir d'abord en cellules, puis de l'utiliser cellfunpour fonctionner sur toutes les cellules. Je ne connais pas l'application spécifique, mais en général, je pense qu'utiliser bsxfunpour opérer sur la matrice serait plus efficace. Applique essentiellement bsxfunune opération élément par élément sur deux tableaux. Donc, si vous souhaitez multiplier chaque élément d'un n x 1vecteur par chaque élément d'un m x 1vecteur pour obtenir un n x mtableau, vous pouvez utiliser:

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

Cela vous donnera une matrice appelée resultdans laquelle l'entrée (i, j) sera le ième élément de vec1multiplié par le jème élément de vec2.

Vous pouvez utiliser bsxfunpour toutes sortes de fonctions intégrées, et vous pouvez déclarer les vôtres. La documentation contient une liste de nombreuses fonctions intégrées, mais en gros, vous pouvez nommer n'importe quelle fonction qui accepte deux tableaux (vecteur ou matrice) comme arguments et la faire fonctionner.

Engineero
la source
-1

Je suis tombé sur cette question / réponse en cherchant à calculer les sommes de ligne d'une matrice.

Je voudrais juste ajouter que la fonction SUM de Matlab prend en charge la sommation pour une dimension donnée, c'est-à-dire une matrice standard à deux dimensions.

Donc, pour calculer les sommes des colonnes, faites:

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

et pour les sommes en ligne, faites simplement

rowsum = sum(M, 2)

Mon pari est que c'est plus rapide que de programmer une boucle for et de convertir en cellules :)

Tout cela peut être trouvé dans l'aide de matlab pour SUM.

nover
la source
7
la possibilité d'appliquer SUM le long d'une dimension donnée a été mentionnée dans la première phrase de la réponse originale à cette question. La réponse a ensuite abordé le cas où la possibilité de choisir une dimension n'est pas déjà intégrée à la fonction. Vous avez raison, cependant, que l'utilisation des options de sélection de dimension intégrées - lorsqu'elles sont disponibles - est presque toujours plus rapide qu'une boucle for ou une conversion en cellules.
cjh
Il est vrai que, cependant, la réponse ci-dessus m'a renvoyé à la documentation de matlab, car je n'avais pas besoin de toute cette fantaisie, donc je voulais juste partager et sauver les autres, ayant besoin d'une solution simple, de la recherche.
nover
-2

si vous connaissez la longueur de vos lignes, vous pouvez faire quelque chose comme ceci:

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
la source
2
À tous ceux qui voient cette réponse: ce n'est pas la façon de procéder! Ce n'est pas la façon de faire quoi que ce soit dans MATLAB!
Stewie Griffin