Les opérateurs logiques

Informatique — MATLAB, chapitre 9

Voici le premier chapitre de la seconde partie. Il n’introduit pourtant aucune boucle ni aucun test : il montre au contraire comment s’en passer.

L’indexation logique est ce qui distingue un programme MATLAB écrit par un débutant d’un programme écrit par quelqu’un qui a compris le langage. Elle remplace la plupart des boucles, en une ligne et cent fois plus vite.

9.1 Comparer

v = [4 8 15 16 23 42];

v > 15
v == 16
v ~= 8
ans =

  0  0  0  1  1  1

ans =

  0  0  0  1  0  0

ans =

  1  0  1  1  1  1

Une comparaison sur un vecteur renvoie un vecteur de 0 et de 1, de même taille : une réponse par élément. C’est le point de départ de tout le chapitre.

Opérateur Signification
> < >= <= comparaisons d’ordre
== égal — deux signes
~= différent — le tilde, pas le point d’exclamation
Avertissement= et == ne font pas la même chose

x = 5 range 5 dans x. x == 5 demande si x vaut 5.

MATLAB refuse if x = 5 avec une erreur de syntaxe, ce qui limite les dégâts. Mais l’inverse — écrire == là où il fallait affecter — produit un ans inutilisé, et la variable attendue n’est jamais créée.

9.2 Combiner

v = [4 8 15 16 23 42];

(v > 10) & (v < 30)
(v < 10) | (v > 30)
~(v > 15)
ans =

  0  0  1  1  1  0

ans =

  1  1  0  0  0  1

ans =

  1  1  1  0  0  0
Opérateur Sens
& ET, élément par élément
\| OU, élément par élément
~ NON
xor(a, b) OU exclusif
Important& et && ne sont pas interchangeables

& opère élément par élément et renvoie un vecteur : c’est celui de l’indexation.

&& n’accepte que des valeurs uniques et renvoie une seule réponse : c’est celui du if, au chapitre suivant.

Appliqué à un vecteur, && déclenche une erreur. Employé dans un if, & fonctionne mais teste une condition globale surprenante — nous y revenons en 9.5.

Règle simple : & dans des crochets, && dans un if.

Les parenthèses autour de chaque condition ne sont pas obligatoires en MATLAB, mais prenez l’habitude : elles le sont en NumPy, et elles rendent l’intention explicite.

9.3 L’indexation logique

C’est le cœur du chapitre.

v = [4 8 15 16 23 42];

masque = v > 15
v(masque)
v(v > 15)
masque =

  0  0  0  1  1  1

ans =

   16   23   42

ans =

   16   23   42

Un vecteur de 0 et de 1 placé entre parenthèses ne désigne pas des positions : il sélectionne les éléments marqués 1. C’est le même mécanisme qu’en R.

9.3.1 Compter et calculer

v = [4 8 15 16 23 42];

sum(v > 15)
mean(v > 15)
sum(v(v > 15))
mean(v(v > 15))
ans = 3
ans = 0.5000
ans = 81
ans = 27
AstuceLe raccourci le plus rentable de MATLAB

Un 1 logique vaut 1, un 0 vaut 0. Donc :

  • sum(condition) compte les cas vérifiant la condition ;
  • mean(condition) en donne directement la proportion.

mean(rendements < 0) livre la fréquence des séances de baisse en une ligne. C’est le même raccourci qu’en R et en NumPy.

9.3.2 Modifier par condition

v = [4 8 15 16 23 42];

v(v > 20) = 0
v(v == 0) = NaN
v =

    4    8   15   16    0    0

v =

     4     8    15    16   NaN   NaN

Une seule ligne remplace ce qu’une boucle avec test ferait en cinq. C’est ainsi qu’on code les valeurs aberrantes en manquantes : revenu(revenu > 1e6) = NaN.

9.3.3 Sur une matrice

A = [1 -2 3; -4 5 -6];

A > 0
A(A > 0)'
A(A < 0) = 0
ans =

  1  0  1
  0  1  0

ans =

   1   5   3

ans =

   1   0   3
   0   5   0
AvertissementL’extraction aplatit toujours

A(A > 0) renvoie un vecteur colonne, jamais une matrice : les éléments retenus ne formeraient pas un rectangle.

Et l’ordre suit le parcours par colonnes du chapitre 2 : ici 1, puis 5, puis 3.

L’affectation, elle, conserve la forme : A(A < 0) = 0 laisse A en 2×3.

9.4 Localiser

v = [4 8 15 16 23 42];

find(v > 15)
find(v > 15, 1)
[~, imax] = max(v)
ans =

   4   5   6

ans = 4
imax = 6

find convertit un masque logique en positions. Le second argument limite le nombre de résultats : find(cond, 1) donne la première occurrence, ce qui évite de parcourir tout le vecteur.

[~, imax] = max(v) récupère la position du maximum en ignorant sa valeur, grâce au tilde vu au chapitre 7.

AstuceMasque ou find ?

Pour extraire ou modifier, le masque suffit et va plus vite : v(v > 15).

Pour connaître les positions — les afficher, les recouper avec un autre tableau, s’arrêter à la première —, il faut find.

Écrire v(find(v > 15)) fonctionne mais fait un détour inutile.

A = [1 -2 3; -4 5 -6];

[lig, col] = find(A < 0)
lig =

   2
   1
   2

col =

   1
   2
   3

Avec deux sorties, find renvoie les indices de ligne et de colonne — la façon de localiser précisément les valeurs problématiques d’un tableau importé.

9.5 any et all

v = [4 8 15 16 23 42];

any(v > 40)
all(v > 0)
any(isnan(v))
ans = 1
ans = 1
ans = 0

any répond « au moins un », all répond « tous ». Ces deux fonctions réduisent un vecteur logique à une seule valeur — ce qu’un if exige.

A = [1 NaN 3; 4 5 6];

any(isnan(A))
any(isnan(A), 2)
any(isnan(A(:)))
A(~any(isnan(A), 2), :)
ans =

  0  1  0

ans =

  1
  0

ans = 1

ans =

   4   5   6

Comme toutes les fonctions d’agrégation du chapitre 4, any travaille par colonne par défaut ; any(..., 2) travaille par ligne.

La dernière expression est celle rencontrée au chapitre 7 : elle ne garde que les lignes sans valeur manquante. Vous pouvez maintenant la lire entièrement.

ImportantPourquoi if v > 0 est piégeux
v = [1 -2 3];
if v > 0
    disp('tous positifs')
end

Rien ne s’affiche — et c’est heureux. Mais avec v = [1 2 -3], rien non plus, alors qu’avec v = [1 2 3] le message apparaît.

MATLAB considère un if comme vrai si tous les éléments le sont : if v > 0 équivaut à if all(v > 0). Cette règle implicite est une source de bugs classiques.

Écrivez toujours explicitement if all(v > 0) ou if any(v > 0). L’intention devient lisible, et le comportement certain.

9.6 Comparer des nombres à virgule

a = 0.1 + 0.2;

a == 0.3
abs(a - 0.3) < 1e-12
ans = 0
ans = 1

Le chapitre 1 l’avait annoncé ; c’est dans une condition que le problème se manifeste. Ne testez jamais l’égalité de deux nombres à virgule par == : comparez leur écart à une tolérance.

Sur des vecteurs, all(abs(a - b) < 1e-12) fait le même travail.

9.7 Un exemple complet

rand('seed', 17);
rendements = 0.0004 + 0.012 * randn(1, 250);

nb_baisses = sum(rendements < 0);
freq = mean(rendements < 0);

perte_max = min(rendements);
[~, jour] = min(rendements);

seuil = -0.02;
chocs = find(rendements < seuil);

fprintf('seances de baisse : %d (%.1f %%)\n', nb_baisses, 100*freq);
fprintf('pire seance : %.2f %% au jour %d\n', 100*perte_max, jour);
fprintf('chocs sous %.0f %% : %d\n', 100*seuil, numel(chocs));

VaR95 = -quantile(rendements, 0.05);
fprintf('VaR 95 %% : %.2f %%\n', 100*VaR95);
seances de baisse : 122 (48.8 %)
pire seance : -3.24 % au jour 68
chocs sous -2 % : 6
VaR 95 % : 1.90 %

Tout le chapitre est ici : comptages par sum, proportion par mean, localisation par find et par la seconde sortie de min. Aucune boucle, et une lecture directe de ce que le code calcule.

À vous

Sur une série de notes, comptez les reçus, extrayez les mentions, localisez la meilleure copie, et remplacez les notes aberrantes par NaN.

notes = [12 8 15 6 17 11 25 14 -3 9];

% a completer
valides = (notes >= 0) & (notes <= 20);
notes(~valides) = NaN

nb_recus = sum(notes >= 10)
taux = mean(notes >= 10, 'omitnan')

mentions = notes(notes >= 14)
[meilleure, rang] = max(notes)

any(isnan(notes))
sum(isnan(notes))
notes =

    12     8    15     6    17    11   NaN    14   NaN     9

nb_recus = 5
taux = 0.6250
mentions =

   15   17   14

meilleure = 17
rang = 5
ans = 1
ans = 2

Le masque valides combine deux conditions, et ~valides désigne les aberrantes : 25 et −3 deviennent NaN.

Attention à nb_recus : sum compte 5 parce que NaN >= 10 vaut faux — heureusement. Mais mean sans 'omitnan' aurait renvoyé NaN. La différence de traitement entre les deux fonctions est exactement le genre de détail qui fausse un résultat sans prévenir.

Ce qu’il faut retenir

Écriture Effet
v > 15, v == 16, v ~= 8 comparer — un résultat par élément
&, \|, ~ combiner élément par élément
&&, \|\| pour un if seulement, sur une valeur unique
v(v > 15) indexation logique — extraire
v(v > 20) = 0 modifier par condition
sum(cond), mean(cond) compter, calculer une proportion
find(cond), find(cond, 1) positions, première position
[lig, col] = find(A < 0) localiser dans une matrice
any, all au moins un, tous — réduisent à une valeur
if all(...), if any(...) toujours expliciter dans un if
A(~any(isnan(A), 2), :) ne garder que les lignes complètes
abs(a - b) < 1e-12 comparer des nombres à virgule

Le chapitre suivant introduit enfin les boucles et les tests — en montrant que la moitié d’entre eux étaient évitables.