4
votes

diviser efficacement les données en bacs

Je souhaite diviser ma variable data en différentes variables a b et c , et appliquer signifie aux bacs (1ère dimension). Existe-t-il un moyen d'améliorer considérablement (par exemple, un ordre de grandeur 1x) ce code en termes de vitesse? Commentaires généraux bienvenus

data=rand(20,1000); %generate data
bins=[5 10 5]; %given size of bins
start_bins=cumsum([1 bins(1:end-1)]);
end_bins=cumsum([bins]);
%split the data into 3 cell arrays and apply mean in 1st dimension
binned_data=cellfun(@(x,y) mean(data(x:y,:),1),num2cell(start_bins),num2cell(end_bins),'uni',0);
%data (explicitly) has be stored into different variables
[a,b,c]=deal(binned_data{:});
whos a b c
  Name      Size              Bytes  Class     Attributes

  a         1x1000             8000  double              
  b         1x1000             8000  double              
  c         1x1000             8000  double              


11 commentaires

Y a-t-il une raison pour laquelle vous le souhaitez dans des variables séparées, plutôt qu'une seule matrice avec des lignes différentes ou en le laissant dans le tableau de cellules?


oui ... j'essaie de m'améliorer et d'en apprendre davantage sur une bibliothèque qui nécessite que la matrice soit divisée en un champ de structure. Les superbes extraits de code postés ci-dessous m'illustrent que le choix de conception sur ce point n'était peut-être pas exactement optimal. Le reste de la bibliothèque nécessite ces champs de structure


Une simple boucle for n'est pas une option?


Si vous souhaitez diviser un tableau de cellules en champs d'une structure, vous pouvez utiliser cell2struct et ainsi éviter les variables intermédiaires


@obchardon une boucle est implémentée, mais je veux emprunter le chemin des démons et optimiser en vectorisant @luis_mendo: désolé de ne pas être clair. C'est un tableau de cellules dans un champ struct. Les variables ci-dessus a, b, c représentent de tels champs, je suppose que c'est plus facile à attribuer - soit en boucle - soit en utilisant deal


mise à jour, bon à savoir: l'utilisation de sparse () ne semble pas fonctionner pour les types de variables int16, ce qui pour moi est le cas et exclut certaines réponses


mise à jour supplémentaire / FYI: apparemment l'utilisation de bsxfun (@times) n'est pas implémentée dans matlab2016a avec int16


Vous pouvez envelopper vos données avec double () lors du passage dans la fonction d'accumulation pour éviter les problèmes de type int16 ... étant donné que vous calculez la moyenne que vous allez finir avec des doubles de toute façon?


@Wolfie vous avez absolument raison et c'est un excellent point



@Wolfie J'ai réfléchi à cela, je réduis les données brutes d'origine en extraits de code avant la conversion (vous en inférez correctement que la RAM est la raison pour laquelle le type de données est int16 en premier lieu), cela devrait fonctionner


4 Réponses :


3
votes

Question d'origine: fractionnement et calcul de la moyenne selon des dims différents

La moyenne peut être appliquée avant le fractionnement, ce qui réduit les données à un vecteur, puis accumarray peut être utilisé:

ind_rows = repmat(repelem((1:numel(bins)).', bins), 1, size(data,2));
ind_cols = repmat(1:size(data,2), size(data,1), 1);
binned_data = sparse(ind_rows, ind_cols, data);
binned_data = bsxfun(@rdivide, binned_data, bins(:));
binned_data = num2cell(binned_data, 2).';


3 commentaires

mea culpa, j'ai foiré, j'en avais besoin dans la 1ère dimension, mais si je lis correctement votre code (élégant), c'est facilement adaptable. code d'exemple mis à jour


Je dois partir maintenant. Je jetterai un coup d'oeil plus tard


Veuillez voir modifier. En attendant, vous avez également obtenu de très bonnes réponses :-)



3
votes

Vous pouvez utiliser la multiplication matricielle:

0.386079  seconds

Si vous voulez la sortie sous forme de cellule:

0.0398011 seconds

Pour les grandes matrices, il est préférable de utiliser une matrice creuse:

0.806947 seconds   sparse: 0.2331  seconds

Remarque: dans les versions précédentes de MATLAB, vous devriez utiliser bsxfun:

0.00718904 seconds

Voici le résultat du timing de trois méthodes proposées dans Octave:

Multiplication matricielle:

0.00465298 seconds

Accumarray:

0.00197697 seconds

Cellfun:

result = bsxfun(@times,bsxfun(@eq, r.',repelem(r,bins)) * data , (1./bins(:)))

MODIFIER: Pour une matrice 200 x 100000:

Multiplication de la matrice:

result = sparse(r.' == repelem(r,bins)) * data .* (1./bins(:));

Accumarray:

result = num2cell(result,2);

Cellfun :

r = 1:numel(bins);
result = (r.' == repelem(r,bins)) * data .* (1./bins(:));


5 commentaires

Vous pourriez avoir besoin d'un exemple plus grand, les résultats de synchronisation seront plus représentatifs si le code prend des secondes pour se terminer (je ne doute pas que la multiplication simple soit la plus rapide).


OK, le résultat est différent pour différentes tailles.


c'est encore au moins un indicateur d'effets temporels très importants. Souhaitez-vous exécuter num2cell pour convertir les lignes en tableau de cellules et la sortie dans les différentes variables?


répondu à la question principale, il se trouve que c'est une exigence circonstancielle pour moi


quelqu'un peut-il m'aider à faire ce travail dans matlab2016a? Remplacez simplement tous les opérateurs implicites par bsxfun?



3
votes

Vous pouvez utiliser splitapply (le petit frère un peu plus sympathique de accumarray ):

% Your example
data = rand(20,1000); % generate data
bins = [5 10 5];      % given size of bins

% Calculation
bins = repelem(1:numel(bins), bins).'; % Bin sizes to group labels
binned_data = splitapply( @mean, data, bins ); % splitapply for calculation

Les lignes de binned_data sont votre a , b et c.


4 commentaires

Je ne savais pas que splittapply pouvait fonctionner en ligne (ou en colonne) avec une matrice de données. Bonne trouvaille!


@Luis tant que le vecteur de regroupement (ligne ou colonne en conséquence) correspond à la taille, cela fonctionne en quelque sorte comme votre expansion implicite préférée!


Je vois. J'aime ça :-D


a choisi cette réponse car dans mon cas d'utilisation réel (taille de données 400x50000 et 50x bins) était en fait toujours un peu plus rapide que la solution de multiplication matricielle.



1
votes

Vous pouvez également utiliser une simple boucle for, je ne vois pas comment une autre fonction peut être plus rapide dans ce cas. La fonction mean doit en tout cas lire chaque valeur donc ...

for ii = 1:numel(start_bins)
    res{ii} = mean(data(start_bins(ii):end_bins(ii),:),1);
end

Je ne vais pas diviser la cellule en plusieurs variables puisqu'une cellule est destinée exactement pour ça.


1 commentaires

@LuisMendo Yups en effet j'ai raté la dimension, merci. Je vais comparer les différentes solutions ce soir pour voir, dans ce cas précis, à quel point une boucle for est mauvaise par rapport à une solution vectorisée.