2.1 Représenter et manipuler les nombres en R
Pour commencer en douceur, regardons comment nous pouvons utiliser R pour représenter et manipuler des nombres. R permet de travailler facilement avec les nombres, qu'ils soient entiers, négatifs, décimaux ou même complexes. Pour un langage dédié à l'analyse statistique, c'est plutôt rassurant non ? Les nombres sont ainsi le premier type d'objets que nous allons rencontrer.
Les différents types de nombres
R possède en réalité trois types d'objets différents pour représenter les nombres, selon la nature du nombre en question.
- Le type
integer
représente les nombres entiers (integer signifiant entier en anglais). - Le type
double
représente les nombres réels. - Le type
complex
représente les nombres complexe comme son nom l'indique.
Trois types pour représenter les nombres ? Cela peut sembler compliqué au début, mais vous allez rapidement vous y faire. Voyons comment écrire différents nombres dans la console et les manipuler.
Les nombres entiers, ou le type "integer" :
Commençons avec les nombres entiers. Pour écrire un nombre entier en R, il suffit d'écrire un nombre, puis d'écrire la lettre L
collée juste après, comme ceci :
10L #le nombre entier 10
Si vous écrivez un tel nombre dans la console, R va simplement vous l'afficher en retour, comme ceci :
On peut déjà remarquer que la console n'affiche pas le L
après notre nombre, alors que nous entrons pourtant 10L
. En effet, ce L
est uniquement là pour faire comprendre à R que l'on souhaite écrire un nombre entier. R sait bien que 10L
représente le nombre 10
, et nous l'affiche donc en tant que tel. Nous verrons un peu plus tard l'intêret de ce L
.
Vous pouvez essayer d'entrer d'autres nombres de type integer
dans la console, elle vous affichera toujours le nombre en question. Afficher des nombres dans la console c'est pas très intéressant en soi, mais une fois que nous aurrons vu les différents types de nombres, nous pourrons commencer à réaliser des opérations entre eux. Et dans le chapitre suivant, nous apprenderons à stocker les résultats de nos calculs. En attendant, continuer à apprendre les différentes façon de représenter des nombres en R.
Les nombres réels, ou le type "double" :
Pour représenter les nombres réels, R utilise le type double
. Un nombre de type double
s'écrit en R de façon "normale", simplement en utilisant un .
pour écrire les décimales :
3.14 #Le nombre décimal 3.14
Plutôt facile non ? Comme pour les entiers, la console nous affiche simplement le nombre que l'on vient d'écrire :
Le type double permet aussi de représenter les nombres entiers. Vous pouvez simplement écrire un entier comme un nombre dont la partie décimale est nulle, comme ceci :
3.0 #le nombre entier 3 de type double
Comme vous pouvez le voir, la console affiche directement le nombre 3
, sans montrer les zéros inutiles.
Vous pouvez aussi ne pas écrire la partie décimale. R va alors comprendre de lui même que la partie décimale est nulle et vous voulez représenter un nombre entier de type double :
3 #l'entier 3 de type double
Cette forme d'écriture à l'avantage d'être bien plus pratique que d'écrire une partie décimale nulle.
Ainsi, si on écrit des nombres entiers sans ajouter de L
, R les comprendra comme des nombres de type double
avec une partie déciminale nulle, et non comme des entiers de type integer
. C'est la raison pour laquelle les integer s'écrivent avec le L
: pour les différencer des entiers de type double
!
Remarque : Dans la console, les nombres entiers de type integer
s'affichent exactement de la même façon que les nombresdouble
. Ainsi 3L
s'affiche exactement de la même façon que le double
3
. Pour éviter les confusions nous indiqueront toujours le L
aprés un integer
, même si en réalité il ne sera pas affiché.
10L #affiche 10L
#La console affiche "10", mais cette écriture permet de voir qu'on parle d'un integer
Pourquoi ce nom de double ?
Finissons de présenter les nombres de type double
avec un autre petit détour historique pour en expliquer le nom.
Pourquoi appeler double
le type de données représentées par les nombres décimaux ? Ce nom remonte simplement aux orgines de l'informatique, où les ordinateurs avaient une mémoire très faible. Il fallait alors stocker l'information en utilisant le minimum de place possible. Avec l'évolution de la technique, on à pu stocker des nombres plus grand, et certains langages ont décidé d'appeler double
les nombres réels stockés sur 64 octets de mémoire, alors qu'avant il était courant de les stocker sur seulement 32 octets. Ces nombres réels étaient ainsi deux fois plus précis que les anciens, car on pouvait retenir le double de décimal ! D'où le terme de "double", pour nombre réelle avec une précision doublée. Depuis, ce nom de double
est resté dans de nombreux langages pour désigner les nombres réels, quand même bien ils ne seraient pas stockés sur 64 ocets ou qu'il n'existai pas de type "simple" prenant deux fois moins de mémoire d'un double
!
R étant là aussi un langage relativement ancien ayant hérité de S, il à conservé cette appelation désuete de double
pour désigner les nombres réels.
Les nombres complexes, ou le type "complex" :
Le troisiéme et dernier type de nombres que nous pouvons rencontrer est les nombres complexes. Il s'écrivent de façon trés intuitive, en se composant d'une partie entiére et d'une partie imaginaire comme attendu :
5 + 2i #Un nombre complexe
Attention à petit point de détail : si vous voulez représenter le nombre i
seul, vous devez ajouter un 1
devant, pour bien préciser que vous parlez de une fois le nombre imaginaire :
#Il ajouter ajouter un 1 devant le i :
5 + 1i #Un nombre complexe
1i #seulement une partie imaginaire
Pourquoi trois types de nombres ?
Il est facile de comprendre à quoi servent les nombres de type complex
. Mais pourquoi avoir deux autres types de nombres (integer
et double
), alors qu'on peut trés bien représenter les nombres entiers avec le type double
?
La raison est historique. Ecrire un entier sous la forme d'un integer
prend (un peu) moins de place en mémoire que le même entier sous forme de double
. A une époque où les ordinateurs avaient peu de place en mémoire, il pouvait être important de faire la différence entre les types de nombres pour optimiser un programme. Un programme qui utilisait beaucoup de nombres entiers et les comptait comme des doubles
au lieu de integer
gaspillait de précieuses ressources en mémoire.
De nos jours, cette différence d'utilisation de mémoire est totalement negligeable pour nos ordinateurs modernes. Il faudrait manipuler des millions de nombres en même temps pour que votre ordinateur personnel puisse voir la différence ! Mais comme R est un langage assez vieux, il à hérité de cette distinction qui était courante dans les premiers langages informations.
En pratique, nous n'utiliserons pas le type integer
, qui demande l'effort d'ajouter un L
à chaque nombre, et qui n'a plus vraiment d'utilité de nos jours. Nous écrirons uniquement des double
, plus rapide à écrire, et sans que cela n'ai d'impact visible sur nos ordinateurs modernes.
Connaitre le type d'un objet avec la fonction typeof()
:
Nous venons de voir les trois types d'objets qui permettent de représenter des nombres en R : integer
, double
et complex
. Il est parfois utile de pouvoir vérifier le type du nombre ou de la donnée que l'on manipule, pour s'assurer que l'on travaille bien avec le type que l'on souhaite. Il existe pour cela la fonction typeof()
, qui renvois le type la donnée placée entre parenthèses. Voici un exemple d'utilisation pour vérifier le type d'un nombre integer
:
typeof(10L) #affichera "integer" dans la console
La fonction nous affichera alors dans la console le type de la donnée en question, ici un nombre entier de type integer
.
Nous sommes donc bien sur que l'objet que nous manipulons est un objet basique de type integer
.
Vérifions aussi que les autres types de nombres produisent bien le résultat attendu et que typeof()
affiche leur type : double
et complex
:
Remarquez que le nombre 3
est bien un double
, bien qu'il soit un entier ! Les entiers au sens courant du terme peuvent être représentés en R soit par des integer
(comme 1L
, 3L
), soit par des double
(comme 1
ou 3.0
).
Cette fonction typeof()
nous servira tout au long de ce cours pour connaitre la nature des objets représentrée dans nos programmes. En effet, elle fonctionne pour les types de nombres, mais aussi pour tous les autres types de données/objets que nous allons décourir au fur et à mesure de notre progression. Aussi, essayez de vous en souvenir !
Manipuler des nombres et calculer des expressions mathématiques
Maintenant que nous avons vu les trois types de nombres en R, nous allons voir comment les utiliser pour réaliser des calculs mathématiques. R permet de manipuler les nombres facilement avec les opérations de base. Une chose importante à retenir est que pour qu'une opération fonctionne, il faut toujours que les éléments soient de même type : on ajoute les integer
avec les integer
, les double
avec les double
et les complex
avec les complex
. R est un langage qui aime bien l'ordre.
Les opérations usuelles :
Voici les opérations mathématiques uselles que l'on peut réaliser en R. Comme vous pouvez le voir, leur écriture est intuitive. La console affiche directement le résultat de notre opération.
Addition :
10 + 5 #addition (affiche 15)
Soustraction :
10 - 5 #soustraction (affiche 5)
Multiplication :
50 * 2 #multiplication (affiche 100)
Division :
10 / 2 #division (affiche 5)
Puissance :
10^3 #puissance (affiche 1000)
Modulo et division arrondie
R sait également faire deux autres opérations intéressantes, qui peuvent être utiles dans certaintes situations. Il s'agit du modulo
, qui est le reste aprés la division entiére, et de la division avec arrondie à l'entier inférieur.
Modulo :
10 %% 3 #modulo (affiche 1 car il reste 1 aprés la division entiére de 10 par 3)
Attention, l'opérateur pour calculer un modulo est bien composé de deux signes %
, si vous en utilisez un seul R ne comprendra pas le sens de votre calcul et vous indiquera une erreur.
Division arrondie :
10 %/% 4 #division avec arrondi à l'entier inférieur (affiche 2)
La gestion des priorités et des parenthèses
R reconnait automatiquement l'usage des parenthèses dans les calculs et applique la priorité conventionnelle lors des opérations. Vous pouvez ainsi écrire des calculs sophistiqués, R utilisera les bonnes régles de priorité pour donner le résultat attendu.
3^2+2 #donne 11, la priorité de la puissance est bien respectée
3^(2+2)#donne 81, la priorité des parenthèses est bien respectée
(2^3+5)*(2-1) #Donne 13 la priorité des parenthèses est bien respectée
La gestion des infinis et des formes indéterminées :
R est non seulement capable de réaliser les calculs correctement, mais il sait aussi comment gérer les cas problématiques, comme ceux qui produisent un résultat infini (comme la division par 0), ou ceux qui produisent un résultat indéterminé (comme divisé l'infini par l'infini). Regardons ceci de plus prêt !
Les formes infinies :
R est non seulement capable de réaliser les calculs correctement, mais il sait aussi travailler avec l'infini. Il utilise les "nombres" Inf
et -Inf
pour représenter l'infini et moins l'infini.
#Diviser par 0 donne Inf
10/0
#Diviser un nombre négatif par 0 donne -Inf
-10/0
R est également cohérent quand on essaye de manipuler l'infini. Ainsi ajouter $$+ \infty + \infty$$ donnera bien $$+ \infty$$, idem pour moins l'infini.
#On enlève 1000 à l'infini
Inf - 1000
#on ajoute l'infini
Inf + Inf
#On ajoute moins l'infini
-Inf -Inf
Remarquez que Inf
est bien considèré comme un nombre par R, et plus précisément un nombre de type double
:
typeof(Inf) #double
Ceci est aussi vrai pour -Inf
.
Résultat indéterminé et NaN :
Que ce passe-il si on essaye une forme indéterminée, comme par exemple ajouter plus l'infini moins l'infini ? Alors R retournera NaN
pour Not a Number (pas un nombre), indiquant que l'opération demandée de donne pas de résultat déterminable.
#plus l'infini moins l'infini donne une forme indéterminée :
Inf -Inf #donne NaN
# L'infi multpliée par 0
Inf*0 #donne NaN
NaN
est bien un objet de type nombre, plus précisement de type double
, exactement comme ses compères Inf
et -Inf
:
typeof(Inf -Inf) #double
Propagation des NaN :
Toute opération qui implique NaN
donnera également comme résultat NaN
. En effet ajouter un nombre avec quelque chose d'indéterminé donnera aussi une indétermination. On dit alors que NaN
se "propage" dans le calcul car tous les calculs où il sera impliqué donneront également NaN
. Tous ces exemples auront ainsi pour valeur NaN
:
#Addition :
5 + NaN
#Multiplication :
10*NaN
#Puissance :
NaN^10
10^NaN
#Nan est plus fort que l'infini :
NaN + Inf
L'infini, NaN et les complexes :
Remarquez que les nombres complexes peuvent avoir dans des cas d'intermination une partie réelle ou imaginaire infine, et l'autre partie NaN
. Regardons un exemple :
1 + Inf*1i)*5 #On peut multiplier i par l'infini comme les deux sont des nombres
#Cela donne le nombre complexe NaN+Infi
On voit que le résultat est bien Nan + Inf*i
ce qui correspond au calcul correct et est bien un nombre complexe car composé d'une partie réelle et imaginaire (n'oubliez pas que NaN
et Inf
sont des doubles).
Vous pouvez ainsi utiliser R comme une véritable calculatrice de façon trés intuitive, qui gère à la fois différents types de nombres, les régles usuelles de prioriété et de calcul, ainsi que les formesinfinies et indeterminées.
R et la précision des calculs
Comme tous langage informatique, R ne peut représenter de façon exacte des nombres qui ont un nombre infini de décimale, comme le nombre pi
ou racine de 2. En effet, un ordinateur ayant une mémoire finie, il ne peut stocker un nombre infini de nombres après la virgule. Cela est matériellement impossible.
Ces nombres seront donc toujours arrondis avec une plus ou moins grande précision et les résultats obtenus lors de tels manipulations ne pourront par nature jamais être exacts.
Mais ce problème est plus subtile qu'il n'y parrait. R représente les nombres non pas en base décimale comme nous, mais en base 2, c'est à dire comme une suite de 0 et de 1. Ainsi le nombre 100
s'écrit 1100100
en binaire. Hors, certains nombres qui ont une écriture décimale finie comme 0.1
en base décimale, ont une écriture binaire infinie (comme 1/3
peut l'avoir en base décimale). Aussi ce n'est pas par ce que vous manipuler des nombres qui ont l'air d'avoir une écriture finie en écriture décimale, que c'est forcement le cas du point de vue de R !
Sans entrer dans les détails, l'affiche de la console de R se débrouille pour cacher les petites erreurs de calcul que cela peut entrainer. Ainsi la plupart du temps nous pensons obtenir un résultat exact, alors que ce n'est pas le cas ! Par exemple 1/10
s'affichera correctement dans la console de R, mais la représentation interne stockée en mémoire sera elle inexacte, car ce nombre n'a pas d'écriture finie en binaire.
De façon générale, il faut toujours être prudent quand on manipule des nombres réels en informatique : dans de nombreux cas, les résultats ne pourront pas être exacts et entraineront des erreurs d'arrondi, qui seront plus ou moins grave. Il existe des méthodes spécifiques pour réaliser du calcul scientifique avec une grande précision, mais cela n'entre pas dans le cadre de ce cours. Il est important que vous gardiez à l'esprit que les calculs avec des réels sont rarement totalement exact en R, et que parfois cela peut entrainer des résultats inatentus dans nos programmes. Nous y reviendrons sur le chapitre sur les expressions.
A retenir :
Il existe trois types de nombres en R :
integer
pour les entiers,double
pour les réels etcomplexe
pour les nombres complexes.On peut connaitre le type d'un élément de cette façon :
typeof(element)
. Le type s'afficher alors dans la console.Toutes les opérations mathématiques usuelles fonctionnement correctement en R, et doivent se faire entre élèments du même type.
R gère correctement les régles de priorité du calcul mathématique, en particulier les formes infinies qui donnent
Inf
ou-Inf
et les formes indéterminées qui donnentNaN
pour Not a Number. Tous sont de typedouble
et sont donc considérés par R comme des sortes de nombres !Comme tous langage informatique, R ne peut gérer correctement les nombres avec une écriture binaire infinie, comme pi ou 0.1. En pratique cela conduit à des approximations, qui peuvent parfois conduire nos programmes à donner des résultats inatendus.