The French version of this document is no longer maintained: be sure to check the more up-to-date English version.

Introduction à R

Spécificités de R
Exemples
R : documentation
ESS
R : quelques fonctions élémentaires
R : structures de données
Opérateurs
R : structures de contrôle
R : fonctions

Dans cette partie, nous faisons un rapide survol du logiciel : où se situe-t-il face à d'autres logiciels de calculs ? Que peut-on faire avec ? Quelles sont ses limitations ? Quelle est sa syntaxe ?

Spécificités de R

Il s'agit d'un logiciel de statisique : contrairement à d'autres logiciels de calcul numérique (Scilab, Octave),

http://www-rocq.inria.fr/scilab/
http://www.octave.org/

il y a déjà des fonctions pour effectuer des traitements statistiques non triviaux, qu'il soient classiques (régression, régression logistique, analyse de la variance, arbres de décision, analyse en composantes principales, etc.) ou plus modernes (réseaux de neurones, bootstrap, etc.).

Il s'agit d'un vrai langage de programmation, et pas un cliquodrome : on n'est pas limité par l'imagination des concepteurs du logiciel, on peut l'utiliser à n'importe quelle fin.

Il s'agit d'un langage interprété : l'avantage, c'est qu'on passe moins de temps à programmer avec, l'inconvénient, c'est que les calculs sont plus lents -- mais à moins de vouloir faire des calculs en temps réel sur les fichiers de la sécurité sociale, en tenant compte de toute la population française, c'est suffisemment rapide. Si on réellement besoin de rapidité, on peut se tourner vers SAS (commercial) ou DAP (libre).

http://www.gnu.org/software/dap/dap.html

A FAIRE : DAP se voulait initialement un équivalent libre de SAS,
mais il en est vraiment très loin. C'est plutôt une bibliothèque pour
faire des calculs statistiques en C.

A FAIRE : mettre une liste de bibliothèques (en C, C++, voire en
Java) pour faire des calculs statistiques.

Un autre problème de R, c'est qu'il charge toutes les données qu'il manipule en mémoire : pour des données très volumineuses, ce n'est pas une bonne idée. (Toutefois, on peut accéder à un SGBD depuis R, et certains (PostgreSQL) acceptent des procédures stockées en R.) J'ai rencontré le problème une seule fois, en voulant manipuler un gros morceau de musique (quelques minutes) comme une série temporelle.

Il permet de créer des graphiques honnètes (sur ce point, il surpasse S-Plus), mais n'est pas pour autant un logiciel de présentation de données : pour des dessins informatifs, c'est bien, pour des brochures publicitaires, c'est insuffisant -- mais c'est une bonne base : on peut partir d'un dessin fait avec R pour l'améliorer avec d'autres logiciels -- ainsi, l'image de titre de ce document a réalisée avec R, PoVRay et Gimp.

Enfin, il s'agit d'un logiciel libre (attention, certaines de ses bibliothèques ne sont pas libres, mais simplement utilisables librement à des fins non commerciales -- mais la plupart sont libres).

Exemples

A FAIRE : donner quelques exemples, si possible impressionnants.

R : documentation

Premiers pas

Le document suivant explique comment utiliser R, progressivement, à un niveau élémentaire (il ne faut pas être effrayé par les expression « écart-type » ou « distribution gaussienne », mais c'est tout).

http://cran.r-project.org/doc/contrib/SimpleR.pdf

Un autre document élémentaire, avec des exercices, pour les germanophones :

http://cran.r-project.org/doc/contrib/s.pdf

RTFM

Le manuel de référence de R est disponible directement sous R : il suffit de taper le nom de la commande sur laquelle on veut des éclaircissements précédé d'un point d'interrogation.

?round

Pour les mots-clefs du langage, ou pour des commandes non alphanumériques, il faut mettre des guillemets.

?"for"
?"[["

Si on ne connait pas le nom de la commande :

apropos("stem")
help.search("stem")

La documentation en HTML (R lance konqueror) :

help.start()

Il y a aussi quelques fichiers PDF dans /usr/lib/R/library/*/doc/ : il s'agit parfois simplement d'une version PDF du manuel (quand le nom du fichier est celui de la bibliothèque), parfois d'explications différentes, plus pédagogiques.

% cd /usr/lib/R/library/
% ls */doc/*pdf
CGIwithR/doc/CGIwithR-overview.pdf
curve/doc/curve-guide.pdf
DBI/doc/DBI.pdf
dr/doc/drdoc.pdf
dse1/doc/dse1-guide-012.pdf
dse1/doc/dse1-guide.pdf
dse1/doc/dse-guide.pdf
dse2/doc/dse2-guide-012.pdf
dse2/doc/dse2-guide-022.pdf
dse2/doc/dse2-guide-024.pdf
dse2/doc/dse2-guide-034.pdf
dse2/doc/dse2-guide-036.pdf
dse2/doc/dse2-guide-038.pdf
dse2/doc/dse2-guide-040.pdf
dse2/doc/dse2-guide-044.pdf
dse2/doc/dse2-guide.pdf
dsepadi/doc/dsepadi-guide.pdf
e1071/doc/svmdoc.pdf
evd/doc/guide.pdf
genetics/doc/genetics_article.pdf
geoRglm/doc/bookchap.pdf
geoRglm/doc/geoRglm.intro.pdf
gregmisc/doc/gregmisc.pdf
grid/doc/uguide_0.7-4.pdf
hwde/doc/hwde.pdf
ifs/doc/IacLat2001.pdf
ifs/doc/IacLat2002.pdf
ipred/doc/ipred-examples.pdf
juice/doc/juice-guide.pdf
lasso2/doc/Manual-wo-help.pdf
lmtest/doc/lmtest-intro.pdf
maxstat/doc/maxstat.pdf
monitor/doc/monitor-guide.pdf
msm/doc/msm-manual.pdf
multcomp/doc/Rmc.pdf
permax/doc/Permax_Computations.pdf
RSQLite/doc/DBI.pdf
spatstat/doc/Intro.pdf
spatstat/doc/Quickref.pdf
StatDataML/doc/StatDataML.pdf
strucchange/doc/strucchange-intro.pdf
syskern/doc/syskern-guide.pdf
tframe/doc/tframe-guide.pdf

Graphisme

Le document suivant (très instructif, même s'il est écrit dans une langue que je ne connais pas -- probablement de l'espagnol), explique en détails quels types de graphiques on peut réaliser avec R. J'en conseille vivement la lecture.

http://cran.r-project.org/doc/contrib/grafi3.pdf

Autres documents plus techniques

L'anova en psychologie :

http://cran.r-project.org/doc/contrib/rpsych.html

La régression linéaire :

http://cran.r-project.org/doc/contrib/Faraway-PRA.pdf

Tout (c'est relativement complet, mais très dense : sans une connaissance préalable des sujets, c'est incompréhensible, mais sinon c'est très bien -- il y a même des exercices ) :

http://cran.r-project.org/doc/contrib/usingR.pdf

RTFM (suite)

Le manuel est disponible en HTML (cherchez sur le disque, dans /usr/lib/R/), mais il n'y a pas les illustrations... On peut les rajouter comme suit (c'est un peu violent).

Pour que ce soit complet et à jour, j'installe la dernière version de R.

urpmi readline-devel
wget http://stat.ethz.ch/CRAN/src/base/R-1.6.2.tgz
tar zxvf R-1.6.2.tgz
cd R-1.6.2/
./configure --prefix=$HOME/spool/JUNK/R --with-readline --enable-R-shlib
make -j 5
make install
PATH=$HOME/spool/JUNK/R/bin:$PATH

avec tous les paquetages.

cd ..
wget -r -l 0 -np -nc -x -k -E -U Mozilla -p -Q 50M cran.r-project.org/src/contrib/
for i in cran.r-project.org/src/contrib/*.tar.gz
do
  echo Installing $i >> nohup.out
  echo Installing $i
  nohup R CMD INSTALL $i
done

Ca plante pour les paquetages suivants : hdf5, Matrix, mimR, netCDF, Rmpi, RmSQL, RMySQL, RODBC, ROracle, rpvm, RQuantLib, rsprng, RSQLite, subselect, XML -- soit 15 erreurs pour 173 réussites. Très souvent, le problème vient de l'absence de certains paquetages de développement (comme readline, ci-dessus) : toutes les bibliothèques requises sont là, mais il y a juste les fichiers nécessaires à l'utilisation de programmes déjà compilés les requérant (les fichiers *.so) et il manque les fichiers *.h.

On peut ensuite récupérer tous les exemples,

#! perl -w
use strict;
my $n = 0;
  
# Ecriture des fichiers R
mkdir "Rdoc" || die "Cannot mkdir Rdoc/: $!";
my @libraries= `ls lib/R/library/`;
foreach my $lib (@libraries) {
  chomp($lib);
  print STDERR "Processing library \"$lib\"\n";
  print STDERR `pwd`;
  my @pages = grep /\.R$/, `ls lib/R/library/$lib/R-ex/`;
  chdir "Rdoc" || die "Cannot chdir to Rdoc: $!";
  mkdir "$lib" || die "Cannot mkdir $lib: $!";
  chdir "$lib" || die "Cannot chdir to $lib: $!";
  open(M, '>', "Makefile") || die "Cannot open Makefile for writing: $!";
  print M "all:\n";
  foreach my $page (@pages) {
    chomp($page);
    print STDERR "  Processing man page \"$page\" in library \"$lib\"\n";
    my $res = "";
    $res .= "library($lib)\n";
    $res .= "library(ts)##VZ##\n";
    $res .= "library(lattice)##VZ##\n";
    $res .= "library(nls)##VZ##\n";
    $res .= "library(nlme)##VZ##\n";
    $res .= "library(MASS)##VZ##\n";
    $res .= "identify <- function (...) {}##VZ##\n";
    $res .= "x11()##VZ##\n";
    open(P, '<', "../../lib/R/library/$lib/R-ex/$page") || 
      die "Cannot open lib/R/library/$lib/R-ex/$page for reading: $!";
    # On marque les lignes où il faut copier l'écran (entre deux commandes)
    while(<P>) {
      s/^([^ #}])/try(invisible(dev.print(png,width=600,height=600,bg="white",filename="doc$n.png")))##VZ##\n$1/
        && $n++;
      $res .= $_;
    }
    $res .= "try(invisible(dev.print(png,width=600,height=600,bg=\"white\",filename=\"doc$n.png\")))##VZ##\n"; $n++;
    close P;
    # On enlève la ligne supplémentaire dans les cas suivants :
    #   La ligne précdente se termine par une virgule, une parenthèse ouvrante, un signe "égale"
    $res =~ s/[,(=+]\s*\n.*##VZ##.*?\n/,\n/g;
    # La ligne précédente est vide
    $res =~ s/^\s*\n.*##VZ##.*\n/\n/gm;
    # La ligne précédente ne contient qu'un commentaire
    $res =~ s/^(\s*#.*)\n.*##VZ##.*\n/$1\n/gm;
    # La ligne suivante commence par une accolade ouvrante A FAIRE : vérifier (boot / abc.ci)
    $res =~ s/^.*##VZ##.*\n\s*\{/\{/gm;
    # On écrit le numéro correctement
    $res =~ s/doc([0-9]).png/doc00000$1.png/g;
    $res =~ s/doc([0-9][0-9]).png/doc0000$1.png/g;
    $res =~ s/doc([0-9][0-9][0-9]).png/doc000$1.png/g;
    $res =~ s/doc([0-9][0-9][0-9][0-9]).png/doc00$1.png/g;
    $res =~ s/doc([0-9][0-9][0-9][0-9][0-9]).png/doc0$1.png/g;
    open(W, ">", "${lib}_${page}") || die "Cannot open ${lib}_${page} for writing: $!";
    print W $res;
    close W;
    print M "\tR --vanilla <${lib}_${page} >${lib}_${page}.out\n";
    my $p = $page; 
    $p =~ s/\.R$//;
    system 'cp', "../../lib/R/library/$lib/html/$p.html", "${lib}_$p.html";
  }
  print M "\ttouch all\n";
  close(M);
  chdir "../../" || die "Cannot chdir to ../../: $!";
}

on les compile (compter quelques heures et quelques plantages),

cd Rdoc/
for i in *
do
  (
    cd $i
    make
  )
done

Je préfère donc paralléliser tout ça :

killall xscreensaver
\ls -d */ | perl -p -e 's/(.*)/cd $1; make/' | perl fork.pl 5

où fork.pl permet de lancer plusieurs processus en même temps, mais pas trop :

#! /share/nfs/users1/umr-tge/zoonek/gnu/Linux/bin/perl -w
use strict;
my $MAX_PROCESSES = shift || 10;
use Parallel::ForkManager;
my $pm = new Parallel::ForkManager($MAX_PROCESSES);
while(<>){
  my $pid = $pm->start and next; 
  system($_);
  $pm->finish; # Terminates the child process
}

On fait ensuite le ménage dans les fichiers PNG ainsi créés, et on écrit les fichiers HTML,

for i in */
do
  (
    cd $i
    perl ../do.it_2.pl
  )
done

où le script do.it_2.pl est :

#! perl -w
use strict;
  
# Effaçons les fichiers PNG vides ou en double
print STDERR "Computing checksums\n";
use Digest::MD5 qw(md5);
my %checksum;
foreach my $f (sort(<*.png>)) {
  if( -z $f ) {
    unlink $f;
    next;
  }
  local $/;
  open(F, '<', $f) || warn "Cannot open $f for reading: $!";
  my $m = md5(<F>);
  close F;
  if( exists $checksum{$m} ){
    unlink $f;
  } else {
    $checksum{$m} = $f;
  }
}
  
# Transformons tout cela en HTML...
print STDERR "Converting to HTML\n";
open(HTML, '>', "ALL.html") || warn "Cannot open ALL.html for writing: *!";
select(HTML);
print "<html>\n";
print "<head><title>R</title></head>\n";
print "<body>\n";
foreach my $f (<*.R.out>) {
  my $page = $f;
  $page =~ s/\.R.out$//;
  # Lecture du fichier HTML initial
  if( open(H, '<', "$page.html") ){
    my $a = join '', <H>;
    close H;
    $a =~ s#^.*?<body>##gsi;
    $a =~ s#<hr>.*?$##gsi;
    print $a;
  } else {
    warn "Cannot open $page.html for reading: $!";
  }
  open(F, '<', $f) || warn "Cannot open $f for reading: $!";
  #print "<h1>$f</h1>\n";
  print "<h2>Worked out examples</h2>\n";
  print "<pre>\n";
  my $header=1;
  while(<F>) {
    if($header) {
      $header=0 if m/to quit R/;
      next;
    }
    if( m/(doc.*png).*##VZ##/ ){
      my $png = $1;
      next unless -f $png;
      print "</pre>\n";
      print "<img width=600 height=600 src=\"$png\">\n";
      print "<pre>\n";
    }
    next if m/##VZ##/;
    next if m/^>\s*###---/;
    next if m/^>\s*##\s*___/;
    next if m/^>\s*##\s*(alias|keywords)/i;
    s/\&/\&amp;/g;
    s/</&lt;/g;
    print;
  }
  close F;
  print "</pre>\n";
  print "<hr>\n";
}
print "</body>\n";
print "</html>\n";
close HTML;

Pour une raison que j'ignore, les fichiers PNG ont un fond transparent : je le transforme en fond blanc avec ImageMagick (le fichier white.png est un fichier PNG tout blanc, de la même taille, 600x600, créé avec Gimp) -- là aussi, c'est très long...

for i in */*png
do
  echo $i
  composite $i white.png $i
done

Je n'ai pas tenu compte des éventuels liens dans les fichiers HTML : je les efface (ce n'est pas propre, mais...).

perl -p -i.bak -e 's#<a\s.*?>##gi; s#</a>##gi' **/ALL.html

Les fichiers HTML devraient plutôt s'appeler index.html :

rename 's/ALL.html/index.html/' */ALL.html

où rename est le programme :

#!/usr/bin/perl -w
use strict;
my $reg = shift @ARGV;
foreach (@ARGV) {
  my $old = $_;
  eval "$reg;";
  die $@ if $@;
  rename $old, $_;
}

Voici un tout petit morceau du résultat (2.7Mo pour 268 images, alors que la documentation complète fait 93Mo pour 2078 images -- j'ai retiré une grande partie du code HTML)

Rdoc/index.html

En particulier, on pourra regarder les paquetages dont la documentation comporte le plus de graphiques.

% find -name "*.png"  | perl -p -e 's#^\./##;s#/.*##' | sort | uniq -c | sort -n | tail -20
   26 sm
   27 cluster
   29 ade4
   29 nls
   29 spatial
   31 mgcv
   34 car
   38 cobs
   41 MPV
   44 mclust
   45 MASS
   45 vcd
   49 grid
   60 gregmisc
   64 pastecs
   70 qtl
   77 splancs
   88 strucchange
   90 waveslim
  113 spdep

ESS

C'est le mode statistique d'Emacs (sur la Mandrake, il n'est pas présent si on utilise Emacs, mais il l'est si on utilise XEmacs). On peut alors éditer du code en bénéficiant de l'indentation et de la colorisation automatique (emacs reconnait ces fichiers à leur extension),

*

on peut même lancer R sous Emacs (M-x R).

*

R : quelques fonctions élémentaires

Dans cette partie, nous présentons les fonctions les plus simples de R, permettant de lire des données (ou de les construire, par simulation) et de les explorer numériquement ou graphiquement.

Paramètres statistiques et calculs :

>            # 20 nombres entre 0 et 20, 
>            # arondis à un chiffre après la virgule
> x <- round(runif(20,0,20), digits=1)  
> x
 [1] 10.0  1.6  2.5 15.2  3.1 12.6 19.4  6.1  9.2 10.9  9.5 14.1 14.3 14.3 12.8
[16] 15.9  0.1 13.1  8.5  8.7

> min(x)
[1] 0.1

> max(x)
[1] 19.4

> median(x)  # médiane
[1] 10.45

> mean(x)    # moyenne
[1] 10.095

> var(x)     # variance
[1] 27.43734

> sd(x)      # standard deviation (écrat-type)
[1] 5.238067

> sqrt(var(x))
[1] 5.238067

> rank(x)    # classement
 [1] 10.0  2.0  3.0 18.0  4.0 12.0 20.0  5.0  8.0 11.0  9.0 15.0 16.5 16.5 13.0
[16] 19.0  1.0 14.0  6.0  7.0

> sum(x)
[1] 201.9

> length(x)
[1] 20

> round(x)
 [1] 10  2  2 15  3 13 19  6  9 11 10 14 14 14 13 16  0 13  8  9

> fivenum(x)     # les quantiles
[1]  0.10  7.30 10.45 14.20 19.40

> quantile(x)    # les quantiles, calculés avec une convention différente
   0%   25%   50%   75%  100%
 0.10  7.90 10.45 14.15 19.40

> quantile(x, c(0,.33,.66,1))
    0%    33%    66%   100%
 0.100  8.835 12.962 19.400

> mad(x)         # déviation moyenne à la médiane (normalisée)
[1] 5.55975

> cummax(x)
 [1] 10.0 10.0 10.0 15.2 15.2 15.2 19.4 19.4 19.4 19.4 19.4 19.4 19.4 19.4 19.4
[16] 19.4 19.4 19.4 19.4 19.4

> cummin(x)
 [1] 10.0  1.6  1.6  1.6  1.6  1.6  1.6  1.6  1.6  1.6  1.6  1.6  1.6  1.6  1.6
[16]  1.6  0.1  0.1  0.1  0.1

> cor(x,sin(x/20))    # corrélation
[1] 0.997286

Affichage d'un message ou d'une variable

print("Coucou !")

Concaténation de chaines de caractères

paste("min:        ", min (x$DS1, na.rm=T)))

Ecriture dans un fichier (ou à l'écran)

cat("\\end{document}\n", file="RESULT.tex", append=TRUE)

R : structures de données

Comme les logiciels de type matlab, R manipule des tableaux de nombres. Néanmoins, il y a différents types de tableaux : des vecteur (tableaux de dimension 1), des matrices (tableaux de dimension 2), des tableaux, des "Data Frames" (tableaux dans lesquels les colonnes ne sont pas nécessairement de même type), des listes (comme un tableau de dimension 1, mais les éléments ne sont pas nécessairement de même type). Nous allons maintenant présenter ces différents types de tableaux, les manières de les construire, des les manipuler, des les transformer, des les convertir.

Vecteurs

Voici plusieurs manières de les définir.

> c(1,2,3,4,5)
[1] 1 2 3 4 5

> 1:5
[1] 1 2 3 4 5

> seq(1,5,by=1)
[1] 1 2 3 4 5

> seq(1,5,lenth=5)
[1] 1 2 3 4 5

Voici quelques manières d'en récupérer un morceau.

> x <- seq(-1,1,by=.1)
> x
 [1] -1.0 -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1  0.0  0.1  0.2  0.3  0.4
[16]  0.5  0.6  0.7  0.8  0.9  1.0

> x[5:10]
[1] -0.6 -0.5 -0.4 -0.3 -0.2 -0.1

> x[c(5,7:10)]
[1] -0.6 -0.4 -0.3 -0.2 -0.1

> x[-(5:10)] # On enlève les éléments d'indice 5 à 10
 [1] -1.0 -0.9 -0.8 -0.7  0.0  0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9  1.0

> x>0
 [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
[13]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
> x[ x>0 ]
 [1] 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

On peut donner des noms aux composantes d'un vecteur.

> names(x)
NULL
> names(x) <- letters[1:length(x)]
> x
   a    b    c    d    e    f    g    h    i    j    k    l    m    n    o    p
-1.0 -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1  0.0  0.1  0.2  0.3  0.4  0.5
   q    r    s    t    u
 0.6  0.7  0.8  0.9  1.0
> x["r"]
  r
0.7

On peut aussi définir ces noms lors de la création du vecteur.

> c(a=1, b=5, c=10, d=7)
 a  b  c  d
 1  5 10  7

Quelques opérations sur les vecteurs :

> x <- rnorm(10)
> sort(x)
 [1] -1.4159893 -1.1159279 -1.0598020 -0.2314716  0.3117607  0.5376470
 [7]  0.6922798  0.9316789  0.9761509  1.1022298
> rev(sort(x))
 [1]  1.1022298  0.9761509  0.9316789  0.6922798  0.5376470  0.3117607
 [7] -0.2314716 -1.0598020 -1.1159279 -1.4159893
> o <- order(x)
> o
 [1]  3  1  9  6  4  7  8 10  2  5
> x[ o[1:3] ]
[1] -1.415989 -1.115928 -1.059802

> x <- sample(1:5, 10, replace=T)
> x
 [1] 1 4 5 3 1 3 4 5 3 1
> sort(x)
 [1] 1 1 1 3 3 3 4 4 5 5
> unique(x) # Il n'est pas nécassaire de trier les données
[1] 1 4 5 3

Voici d'autres manières de créer des vecteurs. La commande seq permet de créer des vecteurs contenant les termes d'une suite arithmétique.

> seq(0,10, length=11)
 [1]  0  1  2  3  4  5  6  7  8  9 10
> seq(0,10, by=1)
 [1]  0  1  2  3  4  5  6  7  8  9 10

La commande rep permet de répéter un nombre ou un vecteur.

> rep(1,10)
 [1] 1 1 1 1 1 1 1 1 1 1
> rep(1:5,3)
 [1] 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5

On peut aussi lui demander de répéter chaque élément plusieurs fois.

> rep(1:5,each=3)
 [1] 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5

On peut mélanger les deux opérations précédentes.

> rep(1:5,2,each=3)
 [1] 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5

La commande gl fait des choses comparables, essentiellement pour créer des facteurs : nous en reparlerons plus loin.

Facteurs

Il s'agit de vecteurs codant une variable qualitative. On les crée à l'aide de la commande factor.

> x <- factor( sample(c("Oui", "Non", "Peut-être"), 5, replace=T) )
> x
[1] Peut-être Peut-être Peut-être Peut-être Non
Levels:  Non Peut-être

On peut préciser la liste des valeurs possibles de cette variable qualitative.

> l <- c("Oui", "Non", "Peut-être")
> x <- factor( sample(l, 5, replace=T), levels=l )
> x
[1] Non       Peut-être Non       Oui       Oui
Levels:  Oui Non Peut-être
> levels(x)
[1] "Oui"       "Non"       "Peut-être"

On peut préférer un tableau avec les effectifs.

> table(x)
x
      Oui       Non Peut-être
        2         2         1

Si les valeurs suivant un certain motif, on peut les créer à l'aide de la commande gl.

> gl(1,4)
[1] 1 1 1 1
Levels:  1
> gl(2,4)
[1] 1 1 1 1 2 2 2 2
Levels:  1 2
> gl(2,4, labels=c(T,F))
[1] TRUE  TRUE  TRUE  TRUE  FALSE FALSE FALSE FALSE
Levels:  TRUE FALSE
> gl(2,1,8)
[1] 1 2 1 2 1 2 1 2
Levels:  1 2
> gl(2,1,8, labels=c(T,F))
[1] TRUE  FALSE TRUE  FALSE TRUE  FALSE TRUE  FALSE
Levels:  TRUE FALSE

On peut faire le produit cartésien de deux facteurs.

> x <- gl(2,4)
> x
[1] 1 1 1 1 2 2 2 2
Levels:  1 2
> y <- gl(2,1,8)
> y
[1] 1 2 1 2 1 2 1 2
Levels:  1 2
> interaction(x,y)
[1] 1.1 1.2 1.1 1.2 2.1 2.2 2.1 2.2
Levels:  1.1 2.1 1.2 2.2

La commande expand.grid est comparable (mais elle produit un data.frame).

> x <- c("A", "B", "C")
> y <- 1:2
> z <- c("a", "b")
> expand.grid(x,y,z)
   Var1 Var2 Var3
1     A    1    a
2     B    1    a
3     C    1    a
4     A    2    a
5     B    2    a
6     C    2    a
7     A    1    b
8     B    1    b
9     C    1    b
10    A    2    b
11    B    2    b
12    C    2    b

Data Frames

Il s'agit d'une "liste" de vecteurs, chacun ayant la même longueur. Généralement, les lignes du tableau sont les individus et les colonnes les différentes mesures sur ces individus.

> n <- 10
> df <- data.frame( x=rnorm(n), y=sample(c(T,F),n,replace=T) )

La commande str affiche la structure d'un objet (n'importe quel type d'objet) et une partie des données qu'il contient.

> str(df)
`data.frame':   10 obs. of  2 variables:
 $ x: num   0.515 -1.174 -0.523 -0.146  0.410 ...
 $ y: logi  FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE

Quand on manipule des objets plus compliqués, qui ont leur propre commande print, la commande unclass peut s'avérer plus claire.

n <- 10
x <- runif(n)
y <- 1 - 2 * x + rnorm(n)
r <- lm(y~x)
r                  # Appelle print.lm
str(r)
print.default(r)
unclass(r)         # Pareil
print.lm           # Pour avoir une idée de ce que l'on peut faire avec l'objet...
summary.lm
print.summary.lm

La commande summary affiche des informations assez compactes sur un objet (ici, un data.frame, mais ça marche avec presque tout).

> summary(df)
       x                 y
 Min.   :-1.17351   Length:10
 1st Qu.:-0.42901   Mode  :logical
 Median : 0.13737
 Mean   : 0.09217
 3rd Qu.: 0.48867
 Max.   : 1.34213

> df
             x     y
1   0.51481130 FALSE
2  -1.17350867  TRUE
3  -0.52338041 FALSE
4  -0.14589347 FALSE
5   0.41022626 FALSE
6   1.34213009  TRUE
7   0.77715729 FALSE
8  -0.55460889 FALSE
9  -0.03843468 FALSE
10  0.31318467 FALSE

On peut accèder aux colonnes de plusieurs manières.

> df$x
 [1]  0.51481130 -1.17350867 -0.52338041 -0.14589347  0.41022626  1.34213009
 [7]  0.77715729 -0.55460889 -0.03843468  0.31318467
> df[,1]
 [1]  0.51481130 -1.17350867 -0.52338041 -0.14589347  0.41022626  1.34213009
 [7]  0.77715729 -0.55460889 -0.03843468  0.31318467
> df[["x"]]
 [1]  0.51481130 -1.17350867 -0.52338041 -0.14589347  0.41022626  1.34213009
 [7]  0.77715729 -0.55460889 -0.03843468  0.31318467

> dim(df)
[1] 10  2
> names(df)
[1] "x" "y"
> row.names(df)
 [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

On peut changer le nom des lignes ou des colonnes.

> names(df) <- c("a", "b")
> row.names(df) <- LETTERS[1:10]
> names(df)
[1] "a" "b"
> row.names(df)
 [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
> str(df)
`data.frame':   10 obs. of  2 variables:
 $ a: num   0.515 -1.174 -0.523 -0.146  0.410 ...
 $ b: logi  FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE

On peut transformer les colonnes d'un Data Frame en variables à l'aide de la commade attach() (c'est le même principe que les espaces de nommage (namespaces) du C++). Il ne faut pas oublier de détacher le Data Frame quand on a fini de jouer avec, sinon les espaces de nommage risquent de s'empiler.

> data(faithful)
> str(faithful)
`data.frame':   272 obs. of  2 variables:
 $ eruptions: num  3.60 1.80 3.33 2.28 4.53 ...
 $ waiting  : num  79 54 74 62 85 55 88 85 51 85 ...
> attach(faithful)
> str(eruptions)
 num [1:272] 3.60 1.80 3.33 2.28 4.53 ...
> detach()

La commande merge permet de fusionner deux data.frames. C'est l'analogue du JOIN des bases de données. On dispose de deux data.frames a (de colonnes x, y, z) et b (de colonnes x1, x2, y, z) et certaines des observations (lignes) de a correspondent à certaines de celles de b : en les fusionnant, on peut obtenir un data.frame ayant pour colonnes x, x1, x2, y, z. Dans cet exemple, la commande

merge(a,b)

est équivelente à

SELECT * FROM a,b WHERE a.y = b.y AND a.z = b.z

Par défaut, la jonction se fait sur toutes les colonnes communes aux deux data.frames, mais on peut ne prendre que certaines d'entre elles, grace à l'argument by.

On utilise souvent les data.frames pour stocker des données à analyser. Nous détaillerons ces exemples un peu plus tard.

# Régression
data(cars)
lm( dist ~ speed, data=cars)

# Régression polynomiale
lm( dist ~ poly(speed,3), data=cars)

# Régression logistique           A FAIRE
glm( ..., family=binomial, data=...)

# Régression non linéaire
A FAIRE

# Analyse en composantes principales
data(USArrest)
princomp( ~ Murder + Assault + UrbanPop, data=USArrest)

# Graphiques en treillis
A FAIRE

Nous verrons un peu plus loin comment transformer les data.frames, car il y a plusieurs manières de mettre le résultat d'une expérience dans un tableau -- mais généralement, on préférera la présentation avec le plus de lignes et le moins de colonnes.

Importation de données

Dans des exemples simples et de petites tailles, on peut taper directement les données qu'on désire analyser. Dans ce document, nous utiliserons beaucoup de données simulées : elles sont plus volumineuses, mais il suffit de taper quelques lignes pour les obtenir.

A contrario, dans des situations réelles, les données sont volumineuses et stockées dans des fichiers ou des bases de données : comment les importer sous R ?

Personnellement, j'utilise souvent la commande source, qui n'est pourtant pas du tout faite pour ça : elle lit du code, pas des données. Les données que je devais analyser étaient sous une forme assez peu standard (des alignement multiples de séquences génomiques) : j'ai donc écrit un petit programme en Perl pour convertir ce format en code pour R.

Plus précisément, mes données ressemblaient à

CLUSTAL W (1.83) multiple sequence alignment

AB020905        ATGACCAACATCCGAAAAACCCACCCATTAGCTAAAATCATCAACAACTCATTTATTGAC
AB020906        ATGACCAACATCCGAAAAACCCACCCATTAGCTAAAATCATCAACAACTCATTTATTGAC
AB020907        ATGACCAACATCCGAAAAACCCACCCATTAGCTAAAATCATCAACAACTCACTTATTGAC
AB020908        ATGACCAACATCCGAAAAACCCACCCATTAGCTAAAATCATCAACAACTCATTTATTGAC
AB020909        ATGACCAACATCCGAAAAACCCACCCATTAGCTAAAATCATCAACAACTCATTTATTGAC
                *************************************************** ********

AB020905        CTTCCAACACCATCAAACATCTCGGCATGATGAAACTTTGGATCCCTCCTTGGAGTATGT
AB020906        CTTCCAACACCATCAAACATCTCAGCATGATGAAACTTTGGATCCCTCCTTGGAGTATGT
AB020907        CTTCCAACACCATCAAACATCTCAGCATGATGAAACTTTGGATCCCTCCTTGGAGTATGT
AB020908        CTTCCAACACCATCAAACATCTCAGCATGATGAAACTTTGGATCCCTCCTCGGAGTATGT
AB020909        CTTCCAACACCATCAAACATCTCAGCATGATGAAACTTTGGATCCCTCCTCGGAGTATGT
                *********************** ************************** *********

le programme était

#! perl -w
use strict;

my @seq;
my @names;
my $i=0;

# On se place après la première ligne vide
while (<>) {
  chomp;
  print STDERR "Skipping $. ($_)\n";
  last if m/^\s*$/;
}

while (<>) {
  chomp;
  if( m/^\s*$/ ){
    $i=0;
    print STDERR "Skipping $. ($_)\n";
    next;
  }
  print STDERR "Reading $. ($i) ($_)\n";
  if (m/^([^\s]+?)\s+(.*)/) {
    print STDERR "Remembering $.\n";
    $names[$i] = $1;
    $seq[$i] .= $2;
  }
  $i++;
}

# foreach my $s (@seq) { print "$s\n"; }
print "d <- matrix( c(\n";
foreach my $s (@seq) {
  print '"'. join('", "', split('', $s)) .'",' ."\n";
}
print "), nr=". (scalar @seq) .", byrow=T)\n";
print "rownames(d) <- c('". join("', '", @names) ."')\n"

et le résultat ressemblait à

d <- matrix( c( "A", "T", "G", "A", "C", "C", "A", "A", "C", "A",
  "T", "C", "C", "G", "A", "A", "A", "A", "A", "C", "C", "C", "A",
  ...
), nr=5, byrow=T)
rownames(d) <- c('AB020905', 'AB020906', 'AB020907', 'AB020908', 'AB020909')

Le problème, avec cette méthode, c'est que ce ne sont pas des données, mais du code. Si on veut juste les utiliser avec R et pas avec un autre logiciel, c'est bon, mais sinon, mieux vaut un format un peu plus universel.

La commande read.table permet de lire des data.frames, i.e., des tableaux (rectangulaires), dont les colonnes peuvent éventuellement avoir des types différents (mais le type ne varie pas au sein d'une colonne). Si on reprend l'exemple précédent, il s'agirait simplement de produire un fichier qui ressemble à

AB020905 "T" "T" "A" "A" "A" "G" "T" "G" ...
AB020906 "T" "T" "A" "A" "A" "G" "T" "G" ...
AB020907 "T" "T" "A" "A" "A" "G" "T" "G" ...
AB020908 "T" "T" "A" "A" "A" "G" "T" "G" ...
AB020909 "T" "T" "A" "A" "T" "G" "T" "G" ...

(avec des lignes très, très longues).

Souvent, ça marche sans qu'on ait à y penser, mais des problèmes peuvent survenir.

Considérons tout d'abord le cas simple où le fichier ne contient que les données, des données numériques, sans nom de ligne ou de colonne. Il pourrait ressembler à

2 7 3 9 2 
8 7 3 2 2
6 2 8 8 1

Il vient :

> d <- read.table('A.txt')
> d
  V1 V2 V3 V4 V5
1  2  7  3  9  2
2  8  7  3  2  2
3  6  2  8  8  1

On remarquera que R a donné des noms aux colonnes. S'ils ne nous plaisent pas, on peut les changer.

> names(d)
[1] "V1" "V2" "V3" "V4" "V5"
> length(d)
[1] 5
> names(d) <- 1:length(d)
> d
  1 2 3 4 5
1 2 7 3 9 2
2 8 7 3 2 2
3 6 2 8 8 1
> names(d) <- LETTERS[1:length(d)]
> d
  A B C D E
1 2 7 3 9 2
2 8 7 3 2 2
3 6 2 8 8 1

Notre fichier peut être plus complexe et contenir des nom de lignes.

x1  2 7 3 9 2
x2  8 7 3 2 2
x3  6 2 8 8 1

Mais ça marche un peu moins bien, car l'ordinateur n'a aucun moyen de savoir que la première colonne est le nom des lignes et pas une variable.

> read.table('A.txt')
  V1 V2 V3 V4 V5 V6
1 x1  2  7  3  9  2
2 x2  8  7  3  2  2
3 x3  6  2  8  8  1

On peut lui demander de retirer la première colonne et de l'utiliser comme nom de lignes (c'est un bon exercice : essayez de le faire seul)

> d <- read.table('A.txt')
> row.names(d) <- d[,1]
> d <- d[,-1]
> d
   V2 V3 V4 V5 V6
x1  2  7  3  9  2
x2  8  7  3  2  2
x3  6  2  8  8  1
> names(d) <- LETTERS[1:length(d)]
> d
   A B C D E
x1 2 7 3 9 2
x2 8 7 3 2 2
x3 6 2 8 8 1

Autre situation : on a à la fois des noms de lignes et des noms de colonnes. Le fichier ressemble à :

   A B C D E
x1 2 7 3 9 2
x2 8 7 3 2 2
x3 6 2 8 8 1

Maintenant, R s'apperçoit que la première ligne contient le nom des variables et que la première colonne contient le nom des observations, car la première ligne du fichier comporte un champ de moins que les autres lignes.

> read.table('A')
   A B C D E
x1 2 7 3 9 2
x2 8 7 3 2 2
x3 6 2 8 8 1

Dernière situation : les colonnes ont des noms, mais pas les lignes. Le fichier ressemble à

A B C D E
2 7 3 9 2
8 7 3 2 2
6 2 8 8 1

Si on essaye naïvement :

> d <- read.table('A.txt')
> d
  V1 V2 V3 V4 V5
1  A  B  C  D  E
2  2  7  3  9  2
3  8  7  3  2  2
4  6  2  8  8  1
> str(d)
`data.frame':   4 obs. of  5 variables:
 $ V1: Factor w/ 4 levels "2","6","8","A": 4 1 3 2
 $ V2: Factor w/ 3 levels "2","7","B": 3 2 2 1
 $ V3: Factor w/ 3 levels "3","8","C": 3 1 1 2
 $ V4: Factor w/ 4 levels "2","8","9","D": 4 3 1 2
 $ V5: Factor w/ 3 levels "1","2","E": 3 2 2 1

D'une part, il ne s'est pas apperçu que la première ligne contenait le nom des colonnes, d'autre part, il a cru que chaque colonne contenait des chaines de caractères... on peut contourner ces problèmes en ajoutant un argument à la commande read.table.

> read.table('A.txt', header=T)
  A B C D E
1 2 7 3 9 2
2 8 7 3 2 2
3 6 2 8 8 1

Avons-nous fait le tour de l'utilisation et des problèmes soulevés par la commande read.table ? Eh bien non. Reprenons le tout premier exemple, avec les séquences nucléiques.

> read.table('A.txt')
        V1   V2   V3 V4 V5 V6 V7   V8 V9
1 AB020905 TRUE TRUE  A  A  A  G TRUE  G
2 AB020906 TRUE TRUE  A  A  A  G TRUE  G
3 AB020907 TRUE TRUE  A  A  A  G TRUE  G
4 AB020908 TRUE TRUE  A  A  A  G TRUE  G
5 AB020909 TRUE TRUE  A  A  T  G TRUE  G

Chaque colonne contenait des caractères (les quatres lettres A, C, G, T), mais il a compris que les T étaient des valeurs booléennes. On peut heureusement préciser le type des colonnes (ici, c'est toujours le même type, donc il suffit de le mentionner une seule fois).

> read.table('A', colClasses=c('character'))
        V1 V2 V3 V4 V5 V6 V7 V8 V9
1 AB020905  T  T  A  A  A  G  T  G
2 AB020906  T  T  A  A  A  G  T  G
3 AB020907  T  T  A  A  A  G  T  G
4 AB020908  T  T  A  A  A  G  T  G
5 AB020909  T  T  A  A  T  G  T  G

A FAIRE : et si le fichier n'est pas sur le disque local, mais donné par un URL ?

A FAIRE : et si le fichier provient d'un tableur (dont je taierai le nom...)

A FAIRE : la commande scan

A FAIRE : connection à des bases de données.

A FAIRE : lire la documentation sur l'importation de données sous R.

Listes

Les vecteurs contiennent uniquement des types simples (nombres, booléens ou chaînes de caractères) ; les listes, au contraire, peuvent contenir n'importe quoi, par exemples des Data Frames ou d'autres listes. On peut les utiliser pour stocker des données complexes, comme des arbres. On peut aussi les utiliser, simplement, comme des tables de hachage.

> h <- list()
> h[["foo"]] <- 1
> h[["bar"]] <- c("a", "b", "c")
> str(h)
List of 2
 $ foo: num 1
 $ bar: chr [1:3] "a" "b" "c"

Par exemple, les paramètres graphiques sont stockés dans une liste utilisée comme table de hachage.

> str( par() )
List of 68
 $ adj      : num 0.5
 $ ann      : logi TRUE
 $ ask      : logi FALSE
 $ bg       : chr "transparent"
 $ bty      : chr "o"
 $ cex      : num 1
 $ cex.axis : num 1
 $ cex.lab  : num 1
 $ cex.main : num 1.2
 $ cex.sub  : num 1
 $ cin      : num [1:2] 0.147 0.200
 ...
 $ xpd      : logi FALSE
 $ yaxp     : num [1:3] 0 1 5
 $ yaxs     : chr "r"
 $ yaxt     : chr "s"
 $ ylog     : logi FALSE

Pour effacer un élément d'une liste :

> h[["bar"]] <- NULL
> str(h)
List of 1
 $ foo: num 1

Matrices

Les matrices sont des tableaux, mais contrairement aux data.frames (dont le type peut carier d'une colonne à l'autre), leurs éléments sont tous du même type.

Une matrice :

> m <- matrix( c(1,2,3,4), nrow=2 )
> m
     [,1] [,2]
[1,]    1    3
[2,]    2    4

ATTENTION : par défaut, les coefficients des matrices sont écrits verticalement.

> matrix( 1:3, nrow=3, ncol=3 )
     [,1] [,2] [,3]
[1,]    1    1    1
[2,]    2    2    2
[3,]    3    3    3

> matrix( 1:3, nrow=3, ncol=3, byrow=T )
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    1    2    3
[3,]    1    2    3

> t(matrix( 1:3, nrow=3, ncol=3 ))
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    1    2    3
[3,]    1    2    3

Le produit matriciel :

> x <- matrix( c(6,7), nrow=2 )
> x
     [,1]
[1,]    6
[2,]    7
> m %*% x
     [,1]
[1,]   27
[2,]   40

Le déterminant d'une matrice :

> det(m)
[1] -2

La transposée d'une matrice :

> t(m)
     [,1] [,2]
[1,]    1    2
[2,]    3    4

Une matrice diagonale :

> diag(c(1,2))
     [,1] [,2]
[1,]    1    0
[2,]    0    2

La matrice identité (ou la matrice d'une homothétie) :

> diag(1,2)
     [,1] [,2]
[1,]    1    0
[2,]    0    1

> diag(rep(1,2))
     [,1] [,2]
[1,]    1    0
[2,]    0    1

> diag(2)
     [,1] [,2]
[1,]    1    0
[2,]    0    1

On a déjà vu les fonctions cbind et rbind, qui permettent de juxtaposer des vecteurs-colonnes ou d'empiler des vecteurs-lignes pour créer des matrices. On peut aussi les utiliser avec des matrices.

> cbind( c(1,2), c(3,4) )
     [,1] [,2]
[1,]    1    3
[2,]    2    4

> rbind( c(1,3), c(2,4) )
     [,1] [,2]
[1,]    1    3
[2,]    2    4

La trace d'une matrice :

> sum(diag(m))
[1] 5

L'inverse d'une matrice :

> solve(m)
     [,1] [,2]
[1,]   -2  1.5
[2,]    1 -0.5

Généralement, on n'a pas besoin de l'inverse d'une matrice, mais simplement de multiplier par l'inverse d'une matrice : c'est plus rapide et numériquement plus stable.

> solve(m, x)
     [,1]
[1,] -1.5
[2,]  2.5

> solve(m) %*% x
     [,1]
[1,] -1.5
[2,]  2.5

Valeurs propres :

> eigen(m)$values
[1]  5.3722813 -0.3722813

Vecteurs propres :

> eigen(m)$vectors
           [,1]       [,2]
[1,] -0.5742757 -0.9093767
[2,] -0.8369650  0.4159736

On vérifie qu'on a effectivement diagonalisé la matrice :

> p <- eigen(m)$vectors
> d <- diag(eigen(m)$values)
> p %*% d %*% solve(p)
     [,1] [,2]
[1,]    1    3
[2,]    2    4

C'est peut-être le bon moment pour rappeler les principales décomposition d'une matrice, que l'on rencontre en algèbre linéaire.

La décomposition LU, ou plus précisément PA = LDU (P: matrice de permutation ; L, U : matrices triangulaires inférieure ou supérieure, avec des 1 sur la diagonale ; L contient toutes les opérations effectuées sur les lignes ; D : matrice des pivots) traduit le résultat de l'algorithme du pivot de Gauss.

On n'en a pas réellement besoin, car l'algorithme du pivot de Gauss est implémenté dans la commande solve, qui permet de multiplier par l'inverse d'une matrice (sans calculer explicitement cet inverse, ce qui serait numériquement plus instable).

?solve

La décomposition de Cholesky est un cas particulier de la décomposition LU : si A est symétrique réelle définie positive, alors on peut l'écrire A*A' où A est triangulaire supérieure. On l'utilise pour résoudre des systèmes d'équations AX=Y dans le cas où A est symétrique -- c'est le cas des équations définissant les estimateurs des moindres carrés. Dans le même ordre d'idées, on peut l'utiliser pour

Nous verrons une autre application pour simuler des variables normales non indépendantes, de matrice de covariance donnée.

La décomposition de Jordan est une généralisation de la diagonalisation (car toutes les matrices ne sont pas diagonalisables).

On n'en a pas réellement besoin, car si on prend une matrice au hasard, elle est diagonalisable.

Il y a ensuite plein de décompositions basées sur la matrice t(A)*A.

La décomposition A=QR (R : triangulaire supérieure, Q : unitaire) traduit l'orthonormalisation de Gram-Schmidt des colonnes de A (on peut l'obtenir à l'aide de la décomposition LU de t(A)*A).

?qr

C'est elle qu'on utilise généralement pour faire une régression :

Modèle : Y = b X + bruit
X = QR
\hat Y = Q Q' Y
b = R^-1 Q' Y

La décomposition en valeurs singulières A=Q1*S*Q2 (Q1, Q2 : matrices des vecteurs propres de A*t(A) et t(A)*A ; S : matrice diagonale des racines carrées des valeurs propres de A*t(A) ou t(A)*A), qui donne, dans le cas où A est symétrique, sa diagonalisation dans une base orthonormale, et qui intervient aussi dans le calcul du pseudo-inverse. On peut aussi voir cette décomposition comme une écriture de la matrice comme somme de matrices de rang 1, de sorte que les premières approchent « au mieux » la matrice de départ.

?svd

La décomposition polaire, A = QR (Q : orthogonale, R : symétrique définie positive), qui est un analogue de la décomposition polaire d'un nombre complexe, et qui sépare la rotation des étirements. On la rencontre dans la méthode des moindres carrés : on cherche à minimiser la valeur absolue de Ax - b, on montre que cela se ramène à résoudre

t(A) A  x = t(A) b

(généralement, t(A)A est inversible, sinon, on utiliserait des pseudo-inverses).

Matrices et tableaux

Il y a aussi un type "array", qui généralise les matrices en dimension quelconque.

> d <- array(rnorm(3*3*2), dim=c(3,3,2))
> d
, , 1

           [,1]       [,2]       [,3]
[1,] 0.97323599 -0.7319138 -0.7355852
[2,] 0.06624588 -0.5732781 -0.4133584
[3,] 1.65808464 -1.3011671 -0.4556735

, , 2

           [,1]       [,2]       [,3]
[1,]  0.6314685  0.6263645  1.2429024
[2,] -0.2562622 -1.5338054  0.9634999
[3,]  0.1652014 -0.9791350 -0.2040375

> str(d)
 num [1:3, 1:3, 1:2]  0.9732  0.0662  1.6581 -0.7319 -0.5733 ...

On les rencontre, par exemple, pour décrire des tableaux de contingence avec plus de deux variables.

> data(HairEyeColor)
> HairEyeColor
, , Sex = Male

       Eye
Hair    Brown Blue Hazel Green
  Black 32    11   10     3
  Brown 38    50   25    15
  Red   10    10    7     7
  Blond  3    30    5     8

, , Sex = Female

       Eye
Hair    Brown Blue Hazel Green
  Black 36     9    5     2
  Brown 81    34   29    14
  Red   16     7    7     7
  Blond  4    64    5     8

> str(HairEyeColor)
 table [1:4, 1:4, 1:2] 32 38 10 3 11 50 10 30 10 25 ...
 - attr(*, "dimnames")=List of 3
  ..$ Hair: chr [1:4] "Black" "Brown" "Red" "Blond"
  ..$ Eye : chr [1:4] "Brown" "Blue" "Hazel" "Green"
  ..$ Sex : chr [1:2] "Male" "Female"
 - attr(*, "class")= chr "table"

Le fait que la commande "str" nous dise "table" et pas "array" n'est pas inquétant :

> is.array(HairEyeColor)
[1] TRUE

Attributs

A FAIRE
On peut attacher des méta-informations à un objet.
Exemples

Nous allons voir tout de suite une autre application de l'un de ces attributs : la notion de classe.

Types plus complexes

L'affichage du résultat de certaines commandes ne ressemble pas du tout à celui des types que nous venons de décrire. C'est le cas par exemple de la régression

n <- 200
x <- rnorm(n)
y <- 1 - 2 * x + rnorm(n)
r1 <- lm(y~x)
r2 <- summary(r1)

qui donne

> r1

Call:
lm(formula = y ~ x)

Coefficients:
(Intercept)            x
      0.924       -2.042

> r2

Call:
lm(formula = y ~ x)

Residuals:
     Min       1Q   Median       3Q      Max
-2.85364 -0.66754 -0.04169  0.61238  2.78004

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)  0.92395    0.07345   12.58   <2e-16 ***
x           -2.04152    0.07613  -26.82   <2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 1.038 on 198 degrees of freedom
Multiple R-Squared: 0.7841,     Adjusted R-squared: 0.783
F-statistic: 719.1 on 1 and 198 DF,  p-value: < 2.2e-16

Pourtant, la commande str nous dit qu'il s'agit quand-même de l'un des types que nous venons de décrire -- très souvent, comme ici, une liste (j'en ai enlevé un gros morceau).

> str(r2)
List of 11
 $ call         : language lm(formula = y ~ x)
 $ terms        :Classes 'terms', 'formula' length 3 y ~ x
  .. ..- attr(*, "variables")= language list(y, x)
  .. ..- attr(*, "factors")= int [1:2, 1] 0 1
  .. .. ..- attr(*, "dimnames")=List of 2
  .. .. .. ..$ : chr [1:2] "y" "x"
  .. .. .. ..$ : chr "x"
(...)
 - attr(*, "class")= chr "summary.lm"

Il y a une différence notable avec les listes que nous avons créées précédemment : l'attribut "class". Les objets r1 et r2 sont ce classe "lm" et "summary.lm". cela a pour effet de changer certaines fonctions qui s'appliquent à ces objets : en particulier, la fonction print, qui est appelée implicitement quand on demande l'affichage de l'objet.

> print
function (x, ...)
UseMethod("print")
<environment: namespace:base>

> print.lm
function (x, digits = max(3, getOption("digits") - 3), ...)
{
    cat("\nCall:\n", deparse(x$call), "\n\n", sep = "")
    if (length(coef(x))) {
        cat("Coefficients:\n")
        print.default(format(coef(x), digits = digits), print.gap = 2,
            quote = FALSE)
    }
    else cat("No coefficients\n")
    cat("\n")
    invisible(x)
}
<environment: namespace:base>

On qualifie la commande print de "méthode" : son code sera différent selon la classe de l'objet auquel on l'applique.

On peut d'ailleurs écrire ses propres classes : il suffit d'ajouter un attribut à un objet et de définir la méthode correspondante.

x <- pi
attr(x, 'class') <- "nombre"
print.nombre <- function (x) {
  cat("(nombre) ")
  cat(signif(x))
  cat("\n")
}

Il vient :

> x
(nombre) 3.14159

On peut aussi définir de nouvelles méthodes.

affiche <- function (x,...) {
  UseMethod("affiche")
}
affiche.default <- print
affiche.nombre <- function (x) {
  cat("(nombre) ")
  cat(signif(x))
  cat("\n")
}

Il vient :

> affiche(x)
(nombre) 3.14159
> affiche(pi)
[1] 3.141593

La commande methods donne la liste des implémentations de cette méthode.

> methods(plot)
 [1] plot.acf*              plot.ACF*              plot.augPred*
 [4] plot.compareFits*      plot.data.frame        plot.decomposed.ts*
 [7] plot.default           plot.dendrogram*       plot.density
[10] plot.factor            plot.formula           plot.function
[13] plot.gls*              plot.hclust*           plot.histogram
[16] plot.HoltWinters*      plot.intervals.lmList* plot.isoreg*
[19] plot.lm                plot.lme               plot.lme1*
[22] plot.lmList*           plot.mlm               plot.nffGroupedData*
[25] plot.nfnGroupedData*   plot.nls*              plot.nmGroupedData*
[28] plot.pdMat*            plot.POSIXct           plot.POSIXlt
[31] plot.ppr*              plot.prcomp*           plot.princomp*
[34] plot.profile.nls*      plot.ranef.lme*        plot.ranef.lmList*
[37] plot.shingle*          plot.simulate.lme*     plot.spec
[40] plot.spec1*            plot.spec.coherency    plot.spec.phase
[43] plot.stl*              plot.table             plot.ts
[46] plot.tskernel*         plot.TukeyHSD          plot.Variogram*
    Non-visible functions are asterisked

Remarquons qu'un objet peut avoir plusieurs types différents : l'attribut "class" peut contenur un vecteur de chaines de caractères -- lors de l'appel d'une méthode, on prend ces chaines une par une, jusqu'à trouver une méthode qui soit implémentée.

Par exemple, le résumtat de la commande aov

n <- 500
x <- rnorm(n)
y <- 1 - x + rnorm(n)
r <- aov(y~x)

a pour classe aov et lm.

> class(r)
[1] "aov" "lm"
> attr(r,"class")
[1] "aov" "lm"

Le contenu des objets complexes

Si on veut utiliser un objet complexe, obtenu comme résultat d'une certaine commande, en en extrayant certains éléments, ou si on veut explorer son contenu, la fonction d'affichage ne suffit pas : il nous faut d'autres moyens de regarder à l'intérieur d'un objet.

La fonction unclass enlève la classe d'un objet : ainsi, son affichage est effectué par la méthode print.default et on voit réellement ce qu'il contient.

> data(USArrests)
> r <- princomp(USArrests)$loadings

> r
Loadings:
         Comp.1 Comp.2 Comp.3 Comp.4
Murder                         0.995
Assault  -0.995
UrbanPop        -0.977 -0.201
Rape            -0.201  0.974

               Comp.1 Comp.2 Comp.3 Comp.4
SS loadings      1.00   1.00   1.00   1.00
Proportion Var   0.25   0.25   0.25   0.25
Cumulative Var   0.25   0.50   0.75   1.00

> class(r)
[1] "loadings"

> unclass(r)
              Comp.1      Comp.2      Comp.3      Comp.4
Murder   -0.04170432  0.04482166  0.07989066  0.99492173
Assault  -0.99522128  0.05876003 -0.06756974 -0.03893830
UrbanPop -0.04633575 -0.97685748 -0.20054629  0.05816914
Rape     -0.07515550 -0.20071807  0.97408059 -0.07232502

On peut aussi utiliser directement la fonction print.default().

> print.default(r)
              Comp.1      Comp.2      Comp.3      Comp.4
Murder   -0.04170432  0.04482166  0.07989066  0.99492173
Assault  -0.99522128  0.05876003 -0.06756974 -0.03893830
UrbanPop -0.04633575 -0.97685748 -0.20054629  0.05816914
Rape     -0.07515550 -0.20071807  0.97408059 -0.07232502
attr(,"class")
[1] "loadings"

La fonction str affiche le contenu d'un objet en tronquant tous les vecteurs : cela permet de voir le contenu d'objets gros mais pas nécessairement complexes.

> str(r)
 loadings [1:4, 1:4] -0.0417 -0.9952 -0.0463 -0.0752  0.0448 ...
 - attr(*, "dimnames")=List of 2
  ..$ : chr [1:4] "Murder" "Assault" "UrbanPop" "Rape"
  ..$ : chr [1:4] "Comp.1" "Comp.2" "Comp.3" "Comp.4"
 - attr(*, "class")= chr "loadings"

> str(USArrests)
`data.frame':   50 obs. of  4 variables:
 $ Murder  : num  13.2 10 8.1 8.8 9 7.9 3.3 5.9 15.4 17.4 ...
 $ Assault : int  236 263 294 190 276 204 110 238 335 211 ...
 $ UrbanPop: int  58 48 80 50 91 78 77 72 80 60 ...
 $ Rape    : num  21.2 44.5 31 19.5 40.6 38.7 11.1 15.8 31.9 25.8 ...

Enfin, si on veut avoir une idée de ce qu'on peut faire avec le contenu d'un objet, on peut toujours regarder le contenu des méthodes print ou summary qui s'y appliquent.

> print.lm
function (x, digits = max(3, getOption("digits") - 3), ...)
{
  cat("\nCall:\n", deparse(x$call), "\n\n", sep = "")
  if (length(coef(x))) {
    cat("Coefficients:\n")
    print.default(format(coef(x), digits = digits), print.gap = 2,
                  quote = FALSE)
  }
  else cat("No coefficients\n")
  cat("\n")
  invisible(x)
}
<environment: namespace:base>

> summary.lm
function (object, correlation = FALSE, symbolic.cor = FALSE,
    ...)
{
  z <- object
  p <- z$rank
  if (p == 0) {
    r <- z$residuals
    n <- length(r)
etc.

> print.summary.lm
function (x, digits = max(3, getOption("digits") - 3), symbolic.cor = x$symbolic.cor,
          signif.stars = getOption("show.signif.stars"), ...)
{
  cat("\nCall:\n")
  cat(paste(deparse(x$call), sep = "\n", collapse = "\n"),
            "\n\n", sep = "")
  resid <- x$residuals
  df <- x$df
  rdf <- df[2]
  cat(if (!is.null(x$w) && diff(range(x$w)))
      "Weighted ", "Residuals:\n", sep = "")
  if (rdf > 5) {
etc.

La commande deparse permet d'avoir une chaine de caractères dont l'évaluation reconstruira l'objet initial (le syntaxe est un peu étrange : si on construisait ces objets à la main, on procèderait un peu différemment).

> deparse(r)
[1] "structure(c(-0.0417043206282872, -0.995221281426497, -0.0463357461197109, "
[2] "-0.075155500585547, 0.0448216562696701, 0.058760027857223, -0.97685747990989, "
[3] "-0.200718066450337, 0.0798906594208107, -0.0675697350838044, "
[4] "-0.200546287353865, 0.974080592182492, 0.994921731246978, -0.0389382976351601, "
[5] "0.0581691430589318, -0.0723250196376096), .Dim = c(4, 4), .Dimnames = list("
[6] "    c(\"Murder\", \"Assault\", \"UrbanPop\", \"Rape\"), c(\"Comp.1\", \"Comp.2\", "
[7] "    \"Comp.3\", \"Comp.4\")), class = \"loadings\")"

> cat(deparse(r)); cat("\n")
structure(c(-0.0417043206282872, -0.995221281426497,
-0.0463357461197109, -0.075155500585547, 0.0448216562696701,
0.058760027857223, -0.97685747990989, -0.200718066450337,
0.0798906594208107, -0.0675697350838044, -0.200546287353865,
0.974080592182492, 0.994921731246978, -0.0389382976351601,
0.0581691430589318, -0.0723250196376096), .Dim = c(4, 4), .Dimnames
= list( c("Murder", "Assault", "UrbanPop", "Rape"), c("Comp.1",
"Comp.2", "Comp.3", "Comp.4")), class = "loadings")

Opérations sur les vecteurs ou les tableaux

Il y a plusieurs manières de coder les résultats d'une expérience.

Premier exemple : on a mesuré plusieurs variables qualitatives sur plusieurs (centaines de) sujets. On peut représenter ces données sous forme d'un tableau, une ligne par sujet, une colonne par varible. On peut aussi représenter ces données sous forme de tableau de contingence (c'est pertinent s'il y a peu de variables, mais inutilisable sinon : le tableau aura trop de cases nulles).

Comment passer d'une formulation à l'autre ?

Dans un sens, il y a la commande "table", qui calcule un tableau de contingence.

n <- 1000
x1 <- factor( sample(1:3, n, replace=T), levels=1:3 )
x2 <- factor( sample(LETTERS[1:5], n, replace=T), levels=LETTERS[1:5] )
x3 <- factor( sample(c(F,T),n,replace=T), levels=c(F,T) )
d <- data.frame(x1,x2,x3)
r <- table(d)

Il vient :

> r
, , x3 = FALSE

   x2
x1  A  B  C  D  E
  1 27 45 31 38 25
  2 41 33 30 35 33
  3 33 30 28 35 39

, , x3 = TRUE

   x2
x1  A  B  C  D  E
  1 26 30 28 42 29
  2 35 33 22 37 40
  3 42 31 31 36 35

Il y a aussi une commande ftable, qui présente le résultat différemment.

> ftable(d)
      x3 FALSE TRUE
x1 x2
1  A        27   26
   B        45   30
   C        31   28
   D        38   42
   E        25   29
2  A        41   35
   B        33   33
   C        30   22
   D        35   37
   E        33   40
3  A        33   42
   B        30   31
   C        28   31
   D        35   36
   E        39   35

Voyons maintenant comment transformer un tableau de contingence en data.frame.

Cas 1 : table uni-dimensionnelle

n <- 100
k <- 10
x <- factor( sample(LETTERS[1:k], n, replace=T), levels=LETTERS[1:k] )
d <- table(x)
factor( rep(names(d),d), levels=names(d) )

Cas 2 : table bi-dimensionnelle

n <- 100
k <- 4
x1 <- factor( sample(LETTERS[1:k], n, replace=T), levels=LETTERS[1:k] )
x2 <- factor( sample(c('x','y','z'),n,replace=T), levels=c('x','y','z') )
d <- data.frame(x1,x2)
d <- table(d)
y2 <- rep(colnames(d)[col(d)], d)
y1 <- rep(rownames(d)[row(d)], d)
dd <- data.frame(y1,y2)

Cas 3 : cas général

n <- 1000
x1 <- factor( sample(1:3, n, replace=T), levels=1:3 )
x2 <- factor( sample(LETTERS[1:5], n, replace=T), levels=LETTERS[1:5] )
x3 <- factor( sample(c(F,T),n,replace=T), levels=c(F,T) )
d <- data.frame(x1,x2,x3)
r <- table(d)
  
# Une fonction qui généralise row et col en dimension supérieures.
foo <- function (r,i) {
  d <- dim(r)
  rep(
    rep(1:d[i], each=prod(d[0:(i-1)])),
    prod(d[(i+1):(length(d)+1)], na.rm=T)
  )
}
k <- length(dimnames(r))
y <- list()
for (i in 1:k) {
  y[[i]] <- rep( dimnames(r)[[i]][foo(r,i)], r )
}
d <- data.frame(y)
colnames(d) <- LETTERS[1:k]
# Vérification
r - table(d)

Opérations sur les vecteurs ou les tableaux (suite)

Autre exemple : on fait trois fois la même expérience sur le même individu. On peut choisir de mettre une ligne par sujet, avec donc plusieurs résultats

  subject, result1, result2, result3

on peut aussi choisir de mettre une ligne par expérience, avec le numéro du sujet, le numéro de l'expérience et le résultat.

  subject, result, retry

Exercice : écrire des fonctions qui effectuent la conversion d'une forme vers l'autre. On pourra utiliser la commande split, qui permet de séparer des données selon la valeur d'un facteur.

Autre exemple : même situation, mais cette fois-ci, le nombre de répétitions par individu n'est pas constant. On peut représenter les données par une liste de vecteurs.

n <- 100
k <- 10
subject <- factor( sample(1:k,n,replace=T), levels=1:k )
x <- rnorm(n)
d1 <- data.frame(subject, x)

# Data.frame vers liste de vecteurs
d2 <- split(d1$x, d1$subject)

# Liste de vecteurs vers data.frame
rep(names(d2), sapply(d2, length))

Aggregate functions: by

A FAIRE : voir s'il ne fait pas fusionner cette section avec la
suivante.

En SQL (le langage avec lequel on s'adresse aux bases de données -- pour simplifier, on va considérer qu'une base de donnée, c'est un (ensemble) de data.frame(s)), on veut souvent appliquer une fonction (par exemple la moyenne, la somme) à des groupes d'enregistrements (un enregistrement, c'est une ligne dans le data.frame). Par exemple, si on stocke sa comptabilité personnelle dans une base de données, en spécifiant, pour chaque dépense, le montant et la nature (loyer, nourriture, transport, impôts, livres, cinéma, etc.),

montant    nature
------------------
390        loyer
4.90       cinéma
6.61       nourriture
10.67      nourriture
6.40       livres
14.07      nourriture
73.12      livres
4.90       cinéma

on peut vouloir calculer la somme des dépenses de chaque type. En SQL, on dirait :

A FAIRE

On peut faire la même chose avec R :

A FAIRE
?by

A FAIRE : un exemple avec des données réelles ou simulées.

Opérations sur les vecteurs ou les tableaux : liste des commandes utiles

A FAIRE : mettre ça un peu plus haut ?

La commande apply permet d'appliquer une fonction (par exemple, la moyenne, des quartiles, etc.) à chaque colonne (ou ligne) d'un Data Frame (ou d'une matrice).

> options(digits=4)
> df <- data.frame(x=rnorm(20),y=rnorm(20),z=rnorm(20))
> apply(df,2,mean)
       x        y        z
 0.04937 -0.11279 -0.02171
> apply(df,2,range)
          x      y      z
[1,] -1.564 -1.985 -1.721
[2,]  1.496  1.846  1.107

Ca marche aussi en dimensions supérieures. Le deuxième argument indique les dimensions qui restent, i.e., les dimensions qu'on utilise pour couper les données en tranches.

> options(digits=2)
> m <- array(rnorm(10^3), dim=c(10,10,10))
> a <- apply(m, 1, mean)
> a
 [1]  0.060 -0.027  0.037  0.160  0.054  0.012 -0.039 -0.064 -0.013  0.061
> b <- apply(m, c(1,2), mean)
> b
        [,1]    [,2]   [,3]   [,4]   [,5]   [,6]    [,7]   [,8]   [,9]   [,10]
 [1,] -0.083 -0.7297  0.547  0.283  0.182 -0.409 -0.0029  0.170 -0.131  0.7699
 [2,] -0.044  0.3618 -0.206 -0.095  0.062 -0.568 -0.4841  0.334  0.362  0.0056
 [3,]  0.255  0.2359 -0.331  0.040  0.213 -0.547 -0.1852  0.492 -0.257  0.4525
 [4,] -0.028  0.7422  0.417 -0.088  0.205 -0.521 -0.1981  0.042  0.604  0.4244
 [5,] -0.085  0.3461  0.047  0.683 -0.018 -0.173  0.1825 -0.826 -0.037  0.4153
 [6,] -0.139 -0.4761  0.276  0.174  0.145  0.232 -0.1194 -0.010  0.176 -0.1414
 [7,] -0.139  0.0054 -0.328 -0.264  0.078  0.496  0.2812 -0.336  0.124 -0.3110
 [8,] -0.060  0.1291  0.313 -0.199 -0.325  0.338 -0.2703  0.166 -0.133 -0.5998
 [9,]  0.091  0.2250  0.155 -0.277  0.075 -0.044 -0.4169  0.050  0.200 -0.1849
[10,] -0.157 -0.3316 -0.103  0.373 -0.034  0.116  0.0660  0.249 -0.040  0.4689
> apply(b, 1, mean)
 [1]  0.060 -0.027  0.037  0.160  0.054  0.012 -0.039 -0.064 -0.013  0.061

La fonction tapply permet de regroupper les observations selon la valeur d'un (ou plusieurs) facteurs et d'appliquer une fonction (moyenne, etc.) à chacun des groupes ainsi obtenus. La commande by fait à peu près la même chose.

> tapply(1:20, gl(2,10,20), sum)
  1   2
 55 155

> by(1:20, gl(2,10,20), sum)
INDICES: 1
[1] 55
------------------------------------------------------------
INDICES: 2
[1] 155

La fonction sapply applique une fonction à chaque élément d'une liste (ou d'un vecteur, etc.) et renvoie si possible un vecteur. La fonction lapply fait la même chose, mais renvoie une liste.

> x <- list(a=rnorm(10), b=runif(100), c=rgamma(50,1))
> lapply(x,sd)
$a
[1] 1.041

$b
[1] 0.294

$c
[1] 1.462
> sapply(x,sd)
    a     b     c
1.041 0.294 1.462

En particulier, la commande sapply permet d'appliquer une fonction à chaque colonne d'un data.frame sans avoir à préciser le numéro des dimensions à garder comme avec la commande apply.

A FAIRE : la commande split

La commande split permet de découper des données : exactement comme tapply, sauf qu'on n'applique aucune fonction derrière.

Divers

A FAIRE : match

?match
# Get the 2's and 4's
x[as.logical( match(x, c(2,4), nomatch=0)  )]

A FAIRE
  
?nchar
?substring

A FAIRE

On peut aussi manipuler des dates :

library(help=date)
library(help=chron)

Applications de ces fonctions : exercices de "débouclage"

Beaucoup de commande R manipulent des vecteurs ou des tableaux, permettant ainsi une programmation (presque) sans boucle, une programmation parallèle. Ainsi, les calculs se font beaucoup plus rapidement que si la boucle était écrite explicitement (car R est un langage interprété). Le style de programmation qui en résulte est très différent de ce à quoi on est habitué : voici donc quelques exercices pour vous rôder. Nous aurons besoin des fonctions de manipulation de tableaux que nous avons vues (en particulier, apply).

Soit x un tableau. Calculer la somme de chacune de ses lignes, la somme de chacune de ses colonnes. Si x est le tableau d'effectifs de deux variables qualitatives, calculer le tableau des effectifs théoriques sous l'hypothèse d'indépendance de ces deux variables. Si vous savez ce que c'est, calculer le Chi^2 correspondant.

# Pour être sûr de ne pas confondre lignes et colonnes, je ne prends
# pas un tableau carré.
n <- 4
m <- 5
x <- matrix( rpois(n*m,10), nr=n, nc=m )
rownames(x) <- 1:n
colnames(x) <- LETTERS[1:m]
x
apply(x,1,sum)
apply(x,2,sum)
# Effectifs théoriques
y <- matrix(apply(x,1,sum),nr=n,nc=m) * matrix(apply(x,2,sum),nr=n,nc=m,byrow=T) / sum(x)
# Effectifs théoriques
y <- apply(x,1,sum) %*% t(apply(x,2,sum)) / sum(x)
# On peut aussi calculer le Chi2 à la main
sum((x-y)^2/y)
# Vérification
chisq.test(x)$statistic

Soit x un vecteur booléen. Compter le nombre de suites de 0 (par exemple, dans 00101001010110, il y a 6 suites de 0). Compter le nombre de suites de 1. Compter le nombre de suites. Même question avec un facteur à plus de deux valeurs.

n <- 50
x <- sample(0:1, n, replace=T, p=c(.2,.8))
# Nombre de suites
sum(abs(diff(x)))+1
# Nombre de suites de 1
f <- function (x, v=1) { # Si quelqu'un a quelque chose de plus simple...
  x <- diff(x==v)
  x <- x[x!=0]
  if(x[1]==1)
    sum(x==1)
  else
    1+sum(x==1)
}
f(x,1)
# Nombre de suites de 0
f(x,0)


n <- 50
k <- 4
x <- sample(1:k, n, replace=T)
# Avec une boucle
s <- 0
for (i in 1:4) {
  s <- s + f(x,i)
}
s
# Sans boucle (peu lisible)
a <- apply(matrix(1:k,nr=1,nc=k), 2, function (i) { f(x,i) } )
a 
sum(a)

Dans un vecteur binaire de taille n, trouver la position des suites de 1 de longueur supérieure à k.

n <- 100
k <- 10
M <- sample(0:1, n, replace=T, p=c(.2,.8))
x <- c(0,M,0)
# Début des suites de 1 
deb <- which( diff(x) == 1 )
# Fin des suites de 1
fin <- which( diff(x) == -1 ) -1
# Longueur des suites de 1
long <- fin - deb
# Position de début et de fin des suites de 1 de longueur au moins k
cbind(deb,fin)[ fin-deb >= k, ]

Exercice : même question, mais on cherche des suites de 1 de longueur au moins k dans les lignes d'une matrice n*m. Présenter le résultat sous forme de tableau.

foo <- function (M,k) {
  x <- c(0,M,0)
  deb <- which( diff(x) == 1 )
  fin <- which( diff(x) == -1 ) -1
  cbind(deb,fin)[ fin-deb >= k, ]
}
n <- 50
m <- 50
M <- matrix( sample(0:1, n*m, replace=T, prob=c(.2,.8)), nr=n, nc=m )
res <- apply(M, 1, foo, k=10)

# Ajout du numéro de la ligne (ça n'est pas très élégant : si
# quelqu'un a mieux...
i <- 0
res <- lapply(res, function (x) { 
  x <- matrix(x, nc=2)
  i <<- i+1
  #if (length(x)) {
    cbind(ligne=rep(i,length(x)/2), deb=x[,1], fin=x[,2])
  #} else {
  #  x
  #}
})
# Présentation du résultat sous forme de tableau
do.call('rbind', res)   # Mais il manque les numéros de ligne.

A FAIRE : vérifier que je parle de la fonction do.call quelque part dans ce document...

A FAIRE : problème de langue...

Let r be the return of a financial asset. The clustered return is the accumulative return for a sequence of returns of the same sign. The trend number is the number of steps in such a sequence. The average return is their ratio. Compute these quantities.

data(EuStockMarkets)
x <- EuStockMarkets
# We aren't interested in the spot prices, but in the returns
# return[i] = ( price[i] - price[i-1] ) / price[i-1]
y <- apply(x, 2, function (x) { diff(x)/x[-length(x)] })
# We normalize the data
z <- apply(y, 2, function (x) { (x-mean(x))/sd(x) })
# A single time series
r <- z[,1]
# The runs
f <- factor(cumsum(abs(diff(sign(r))))/2)
r <- r[-1]
accumulative.return <- tapply(r, f, sum)
trend.number <- table(f)
boxplot(abs(accumulative.return) ~ trend.number, col='pink',
        main="Accumulative return")

*

boxplot(abs(accumulative.return)/trend.number ~ trend.number,
        col='pink', main="Average return")

*

op <- par(mfrow=c(2,2))
for (i in 1:4) {
  r <- z[,i]
  f <- factor(cumsum(abs(diff(sign(r))))/2)
  r <- r[-1]
  accumulative.return <- tapply(r, f, sum)
  trend.number <- table(f)
  boxplot(abs(accumulative.return) ~ trend.number, col='pink')
}
par(op)

*

op <- par(mfrow=c(2,2))
for (i in 1:4) {
  r <- z[,i]
  f <- factor(cumsum(abs(diff(sign(r))))/2)
  r <- r[-1]
  accumulative.return <- tapply(r, f, sum)
  trend.number <- table(f)
  boxplot(abs(accumulative.return)/trend.number ~ trend.number, col='pink')
}
par(op)

*

Soit M une matrice n*m (représentant une image en niveaux de gris) ; calculer la valeur moyenne de chaque quadripixel.

data(volcano)
M <- volcano
n <- dim(M)[1]
m <- dim(M)[2]
M1 <- M [1:(n-1),] [,1:(m-1)]
M2 <- M [2:n,] [,1:(m-1)]
M3 <- M [1:(n-1),] [,2:m]
M4 <- M [2:n,] [,2:m]
# Avec des quadripixels qui se chevauchent
M0 <- (M1+M2+M3+M4)/4

# Avec des quadripixels qui ne se chevauchent pas
nn <- floor((n-1)/2)
mm <- floor((m-1)/2)
M00 <- M0 [2*(1:nn),] [,2*(1:mm)]

op <- par(mfrow=c(2,2))
image(M, main="Image initiale")
image(M0, main="Quadripixels chevauchants")
image(M00, main="Quadripixels non chevauchants")
par(op)

*

Calculer une matrice de Van der Monde.

outer(x, 0:n, '^')

Tracer un graphe à l'aide de sa matrice d'incidence

n <- 100
m <- matrix(runif(2*n),nc=2)
library(ape)
r <- mst(dist(m)) # La matrice d'incidence (de l'arbre couvrant
                  # minimal des points précédents).
plot(m)
n <- dim(r)[1]
w <- which(r!=0)
i <- as.vector(row(r))[w]
j <- as.vector(col(r))[w]
segments( m[i,1], m[i,2], m[j,1], m[j,2], col='red' )

*

A FAIRE : trouver d'autres exercices...

Opérateurs

Les opérateurs suivants ont le sens auquel vous pensez (mais ils ont tendance à manipuler des vecteurs) :

+ * / - ^ 
< <= > >= == !=

Les opérateurs logiques sont !, & et | (mais on peut écrire && ou || à la place de | et & : le résultat est alors toujours un scalaire, même si les arguments sont des vecteurs).

L'opérateur : (deux-points) permet de créer des vecteurs.

> -5:7
 [1] -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7

L'opérateur [ permet de récupérer un (ou plusieurs) éléments d'un tableau.

> x <- floor(10*runif(10))
> x
 [1] 3 6 5 1 0 6 7 8 5 8
> x[3]
[1] 5
> x[1:3]
[1] 3 6 5
> x[c(1,2,5)]
[1] 3 6 0

L'opérateur $ permet de récupérer un élément d'une liste, sans avoir à mettre son nom entre guillemets, contrairement à l'opérateur [[. L'intéret de l'opérateur [[ est que son argument peut être une variable.

> op <- par()
> op$col
[1] "black"
> op[["col"]]
[1] "black"
> a <- "col"
> op[[a]]
[1] "black"

L'assignation se note <-.

x <- 1.17
y <- c(1, 2, 3, 4)

Le produit matriciel se note %*%, le produit tensoriel (aka produit de Kronecker) %x%.

> A <- matrix(c(1,2,3,4), nr=2, nc=2)
> J <- matrix(c(1,0,2,1), nr=2, nc=2)
> A
     [,1] [,2]
[1,]    1    3
[2,]    2    4
> J
     [,1] [,2]
[1,]    1    2
[2,]    0    1
> J %x% A
     [,1] [,2] [,3] [,4]
[1,]    1    3    2    6
[2,]    2    4    4    8
[3,]    0    0    1    3
[4,]    0    0    2    4

L'opérateur %o% sert à fabriquer des tables de multiplications (il appelle la fonction outer avec la multiplication).

> A <- 1:5
> B <- 11:15
> names(A) <- A
> names(B) <- B
> A %o% B
  11 12 13 14 15
1 11 12 13 14 15
2 22 24 26 28 30
3 33 36 39 42 45
4 44 48 52 56 60
5 55 60 65 70 75

La division euclidienne est %/%, son reste est %%.

> 1234 %% 3
[1] 1
> 1234 %/% 3
[1] 411
> 411*3 + 1
[1] 1234

L'appartenance à un "ensemble" se note %in%.

> 17 %in% 1:100
[1] TRUE
> 17.1 %in% 1:100
[1] FALSE

L'opérateur ~ est utilisé pour décrire des modèles : nous en parlerons plus loin.

Pour plus de détails (et pour les opérateurs que j'ai passé sous silence, comme <<- -> ->> @ :: _ =), voir le manuel :

?"+"
?"<"
?"<-"
?"!"
?"["
?Syntax
?kronecker
?match
library(methods)
?slot

On peut s'amuser à définir ses propres opérateurs : ce sont juste des fonction à deux arguments dont le nom commence et se termine par %. L'exemple suivant est tiré du manuel.

> "%w/o%" <- function(x,y) x[!x %in% y]
> (1:10) %w/o% c(3,7,12)
[1]  1  2  4  5  6  8  9 10

R : structures de contrôle

R est en fait un langage de programmation : à ce titre, on dispose des structures de contrôle habituelles (boucles, conditioinnelles, récursivité, etc.).

Conditionnelle :

if(...) {
  ...
} else {
  ...
}

On peut utiliser les conditionnelles à l'intérieur d'autres constructions.

x <- if(...) 3.14 else 2.71

On peut aussi construire des vecteurs à l'aide de conditionnelles.

x <- rnorm(100)
y <- ifelse(x>0, 1, -1)
z <- ifelse(x>0, 1, ifelse(x<0, -1, 0))

Branchement :

x <- letters[floor(1+runif(1,0,4))]
y <- switch(x,
            a='Bonjour',
            b='Gutten Tag',
            c='Hello',
            d='Konnichi wa',
           )

Boucle for :

for (i in 1:10) {
  ...
  if(...) { next }
  ...
  if(...) { break }
  ...
}

Boucle while :

while(...) {
  ...
}

Boucle repeat :

repeat {
  ...
  if(...) { break }
  ...
}

R : fonctions

R se ratache à la famille des langages fonctionnels (Lisp, OCaML, mais aussi Python) : la notion de fonction est centrale (en particulier, si on en a besoin, il est très facile d'écrire des fonctions qui prennent en argument d'autres fonctions).

On définit une fonction ainsi :

f <- function(x) {
  x^2 + x + 1
}

La valeur de retour est la dernière valeur calculée (mais on peut aussi utiliser la commande return).

On peut donner des valeurs par défaut aux arguments.

f <- function(x, y=3) { ... }

Lors de l'appel de la fonction, on peut utiliser le nom des arguments, sans tenir compte de leur ordre.

f(y=4, x=3.14)

A la fin des arguments, on peut mettre trois points de suspension, qui représentent tous les arguments que l'on n'a pas précisés, et que l'on peut passer en argument à d'autres fonctions.

f <- function(x, ...) {
  plot(x, ...)
}

Les fonctions n'ont pas d'effet de bord : toutes les modifications sont locales. En particulier, on ne peut pas écrire de fonction qui modifie une variable globale. (En fait, si, on peut : voir un peu plus loin la partie "bidouillages".)

Vincent Zoonekynd
<zoonek@math.jussieu.fr>
latest modification on Wed Oct 13 22:32:30 BST 2004