Pour ceux qui programment par ailleurs, parmi les langages que vous maitrisez vous en connaissez peut-être qui sont typés/non-typés, de façon statique/dynamique.
Par exemple en C, toute variable doit d’abord être définie :
int multiplie(int a, int b)
{
int c ;
c = a * b ;
return c;
}
Dans cet exemple, a, b, c sont obligatoirement des nombre entiers tels que définis par le langage (compris entre -32767 et 32767 en C) et la valeur de retour de fonction sera aussi un entier et devra être traitée comme tel. Le non respect de cette règle aboutira à une erreur de compilation et vous ne pourrez pas disposer de votre exécutable (car le C est aussi un langage compilé et non interprété…).
De l’autre côté, nous avons les langages faiblement typés, comme Python. C’est à dire qu’il n’est pas nécessaire de dire par avance ce que contiendra la valeur et on peut à la volée modifier le contenu :
a = 1
# a = 1 (entier)
b = "coucou"
# a = 1 (entier) et b = "coucou" (chaine de caractères)
a = b
# a = "coucou" (chaine de caractères)
Dans le cadre de R, nous avons affaire à un langage faiblement typé. Cela veut dire que le langage n’interdira pas de passer des arguments de type inadapté et ne vérifiera pas lui-même la pertinence ou la capacité de conversion :
Un langage faiblement typé ne veut donc pas dire qu’il n’y a qu’un type universel et interopérable mais que les variables n’ont pas à être typées dans le marbre. Car comme tout langage, il sera nécessaire de respecter les types attendus, imposés ou si besoin se mettre en conformité.
Enfin, il existe des langages dits non-typés, qui n’ont en fait qu’un seul type (généralement la chaine de caractères pouvant stocker des données texte +/- binaires) et tout traitement nécessite une interprétation (cachée) des données. C’est le cas de la plupart des langages de scripts systèmes du type sh, bash,… (sous unix-like), ou cmd.exe (sous feu ms-dos ou windows qui sert à exécuter les fichier .BAT).
Si nous reprenons notre fonction de démonstration habituelle (Les fonctions (premier contact))
multiplie("x",2)
n’est pas interdit par la définition de la fonction mais va générer une erreur à l’exécution car *
(la multiplication arithmétique utilisée dans multiplie()
) ne fonctionne qu’avec un contenu numérique à gauche et à droite. Dans certains cas, des essais de modification automatique seront tentés par exemple entre les différents types numériques.
Les types standards.
R est un peu troublant dans son vocabulaire car il existe 3 termes désignant « quasiment-la-même-chose-mais-pas-exactement » : type, mode et classe. En pratique type et mode sont substituables, tandis que classe est destiné à un usage orienté objet (qui nous verrons un autre jour). A titre personnel, je n’utilise pas « mode » mais « type ».
En R, il existe 6 types standards (on dit aussi primitifs). Les types numériques en sont les principaux représentants (normal, R sert surtout à manipuler des nombres ou réaliser des dénombrements) :
En plus des valeurs possibles, tous les types ci-dessous peuvent aussi au moins accueillir la valeur NA
qui se retrouve classiquement en lieu et place d’une donnée non fixée. numeric
supporte aussi Inf
(inity) pour représenter + ou – l’infini et NaN
qui correspond au résultat d’une opération arithmétique n’acceptant pas de solution.
Par exemple log(-1) = NaN
tandis que log(0) = -Inf
, dont le typeof()
est « double »
des nombres
integer
(nombre entier signé), les limites sont -2E+9 à +2E+9double
(nombre à virgule, signé, en précision double, on peut d’ailleurs aussi écrirenumeric
, oureal
(obsolète) ), il est possible de signifier un nombre entre 2E-308 et 2E+308 + le signe.complex
(nombre complexe, pouvant s’écrire a+bi, en faisant cependant attention à la priorité des opérations arithmétiques et apparentées)
des valeurs logiques
logical
(représentation booléenne) pouvant accepter TRUE, FALSE (ou NA)
des chaines de caractères
character
pouvant contenir une chaine de caractères.
Il n’existe pas de type charactère isolé (comme il pourrait y avoir en C : char a ;
)
des données « brutes »
raw
( brute)
Ce mode n’a que peu d’intérêt dans la pratique courante vu qu’il est utilisé pour manipuler (sommairement) des représentation binaires. Je n’en parlerai pas (tout du moins avant longtemps)
Et des types particuliers
comme par exemple NULL
pour représenter le vide/l’absence et qui n’est pas modifiable, celle-ci étant différente de NA
et ayant un comportement spécifique. Par exemple, comparez:
> c("Chaine1",NULL,"Chaine3")
[1] "Chaine1" "Chaine3"
> c("Chaine1",NA,"Chaine3")
[1] "Chaine1" NA "Chaine3"
Dans le premier cas, NULL
a été ignoré. Le résultat est un vecteur de chaines de caractères de longueur 2. Tandis que dans le second, le résultat est un vecteur de chaines de caractères de longueur 3 dont la 2ème est « Non attribuée » (ce qu’on qualifierait de null en prenant un emplacement dans un autre langage…)
Le type contenu dans un vecteur.
Nous avons vu qu’une variable en R est un vecteur c’est à dire un ensemble de valeurs. La création d’un vecteur tout juste initialisé se fait par vector()
et n’est quasiment jamais utilisé (quel intérêt d’avoir un vecteur non fonctionnel sachant qu’on peut le créer rempli) tandis que c()
(comme « concaténation ») crée un vecteur initialisé avec les données passées en paramètres. Au sein d’un même vecteur, toutes les valeurs sont du même type. Ainsi :
> c(1,2)
[1] 1 2
> c(1,"2")
[1] "1" "2"
dans le premier cas, c()
accueille 2 nombres et crée donc un vecteur de ce type double
(valeur numérique en double précision, par défaut).
Dans le second cas, le vecteur généré est de type character
(chaine de caractères) car R a tenté de convertir les paramètres dans un type compatible sans perte de précision.
Vous pouvez voir le type de vos données grâce à la fonction typeof()
:
> typeof(c(1,2))
[1] "double"
> typeof(c(1,"2") )
[1] "character"
Et c’est là que l’on voit apparaitre une différence avec la classe… le type « double » étant de classe « numeric » (ne cherchez pas… la raison n’est qu’historique).
> class(c(1,2))
[1] "numeric"
> class(c(1,"2"))
[1] "character"
class(...)
a aussi la particularité d’être modifiable via <-
. Ainsi :
> x <- c(1,2)
> class(x)
[1] "numeric"
> class(x)<-"blu"
> class(x)
[1] "blu"
> x
[1] 1 2
attr(,"class")
[1] "blu"
> class(x)<-"character"
> class(x)
[1] "character"
> x
[1] "1" "2"
> typeof(x)
[1] "character"
En modifiant la classe de x
en « blu » puis « character
« , nous avons modifié le type sous-jacent. Sachez par contre que le type n’est pas directement modifiable ainsi (il n’existe pas d’écriture typeof(...)<-
)
Les tests et la conversion de types
Juste au dessus, nous avons vu une façon sale (à ne pas utiliser) de changer de type… en changeant de classe. Mais la bonne façon est d’utiliser les fonctions de la famille as.xxx()
, alors que pour tester l’appartenance à un type il faut utiliser is.xxx()
.
is.xxx()
Pour tester l’appartenance à un type, par exemple integer
, il suffit d’appeler is.integer(x)
. (Le point « . » n’est pas un caractère réservé dans R, il peut donc être contenu dans un nom de fonction).
Le retour est un résultat booléen (de type logical
) donc :
- soit TRUE si c’est vrai
- soit FALSE si c’est faux
Il s’agit pour la plupart d’un test sur le type et aucune conversion n’est tentée ainsi :
> is.integer(1) # Par défaut un nombre est un double
[1] FALSE
> is.integer(1L) # En précisant un L derrière, on force le nombre entier
[1] TRUE
> is.integer(1.1L) # Mais si on mets une virgule, on ne peut pas forcer le "L"
[1] FALSE
Warning message:
integer literal 1.1L contains decimal; using numeric value
Cependant, certaines convertissent à un niveau supérieur. Ainsi is.numeric() est vrai pour toute valeur, indépendamment du type si la donnée est numérique.
> x<-1L # on met x à 1 (nombre entier)
> typeof(x)
[1] "integer"
> is.numeric(x)
[1] TRUE
> is.double(x)
[1] FALSE
> as.numeric(x
)
[1] 1
> typeof(as.numeric(x))
[1] "double"
as.xxx()
la série des fonctions as.xxx()
permet de convertir une donnée d’un type à l’autre. Ces fonctions interprètent les données en entrée afin de les mettre en forme dans le type de destination, si c’est possible. En cas d’impossibilité, des NA sont mis à la place). Ainsi :
> as.integer(1+2i) # convertir en entier, un nombre complexe (perte de la part imaginaire)
[1] 1
Warning message:
imaginary parts discarded in coercion
> as.integer("t") # tente de convertir sans succès une chaine de caractères
[1] NA
Warning message:
NAs introduced by coercion
> as.numeric("1.54") # convertir une chaine de caractères en nombre flottant (interprétation)
[1] 1.54
La plupart sont assez prévisibles mais il peut être intéressant de s’arrêter quelques secondes sur as.logical()
car selon le type et le contenu de la source, l’interprétation varie :
> as.logical(c(NA,-1,0,1) # Depuis le type numeric
[1] NA TRUE FALSE TRUE
as.logical(c(""," ","TRUE","True","true","T","FALSE","False","false","F","AUTRE","0","1"))
[1] NA NA TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE NA NA NA
# Depuis un type character