Question

Je veux trier une data.frame par plusieurs colonnes. Par exemple, avec le data.frame ci-dessous je voudrais trier par colonne z (décroissant) puis par colonne b (croissant):

dd <- data.frame(b = factor(c("Hi", "Med", "Hi", "Low"), 
      levels = c("Low", "Med", "Hi"), ordered = TRUE),
      x = c("A", "D", "A", "C"), y = c(8, 3, 9, 9),
      z = c(1, 1, 1, 2))
dd
    b x y z
1  Hi A 8 1
2 Med D 3 1
3  Hi A 9 1
4 Low C 9 2
Était-ce utile?

La solution

Vous pouvez utiliser le order() fonction directement sans avoir recours à outils complémentaires - voir cette réponse plus simple qui utilise un droit de tour du haut du code example(order):

R> dd[with(dd, order(-z, b)), ]
    b x y z
4 Low C 9 2
2 Med D 3 1
1  Hi A 8 1
3  Hi A 9 1

Modifier une 2 + ans plus tard: Il est juste demandé comment faire cela par l'indice de colonne. La réponse est de simplement passer la colonne de tri souhaité (s) à la fonction de order():

R> dd[order(-dd[,4], dd[,1]), ]
    b x y z
4 Low C 9 2
2 Med D 3 1
1  Hi A 8 1
3  Hi A 9 1
R> 

plutôt que d'utiliser le nom de la colonne (et with() pour faciliter / accès plus direct).

Autres conseils

Vos choix

  • order de base
  • arrange de dplyr
  • setorder et setorderv de data.table
  • arrange de plyr
  • sort de taRifx
  • orderBy de doBy
  • sortData de Deducer

La plupart du temps, vous devez utiliser les solutions de dplyr ou data.table, à moins d'avoir pas les dépendances est important, dans ce cas, utilisez base::order.


J'ai récemment ajouté sort.data.frame à un ensemble CRAN, ce qui rend la classe compatible tel que discuté ici: La meilleure façon de créer de la cohérence générique / méthode pour le tri .data.frame

Par conséquent, compte tenu de la dd data.frame, vous pouvez trier comme suit:

dd <- data.frame(b = factor(c("Hi", "Med", "Hi", "Low"), 
      levels = c("Low", "Med", "Hi"), ordered = TRUE),
      x = c("A", "D", "A", "C"), y = c(8, 3, 9, 9),
      z = c(1, 1, 1, 2))
library(taRifx)
sort(dd, f= ~ -z + b )

Si vous êtes l'un des auteurs originaux de cette fonction, s'il vous plaît me contacter. Discussion à domaininess public est ici: http://chat.stackoverflow.com/transcript/message/1094290#1094290


Vous pouvez également utiliser la fonction arrange() de plyr comme Hadley a souligné dans le fil ci-dessus:

library(plyr)
arrange(dd,desc(z),b)

Repères: Notez que j'ai chargé chaque paquet dans une nouvelle session R car il y avait beaucoup de conflits. En chargement particulier le paquet DOBY provoque sort retourner "L'objet suivant (s) sont masquées à partir de x (position 17) ': b, x, y, z", et le chargement du paquet Deducer remplace sort.data.frame de Kevin Wright ou le taRifx package.

#Load each time
dd <- data.frame(b = factor(c("Hi", "Med", "Hi", "Low"), 
      levels = c("Low", "Med", "Hi"), ordered = TRUE),
      x = c("A", "D", "A", "C"), y = c(8, 3, 9, 9),
      z = c(1, 1, 1, 2))
library(microbenchmark)

# Reload R between benchmarks
microbenchmark(dd[with(dd, order(-z, b)), ] ,
    dd[order(-dd$z, dd$b),],
    times=1000
)

fois médians:

dd[with(dd, order(-z, b)), ] 778

dd[order(-dd$z, dd$b),] 788

library(taRifx)
microbenchmark(sort(dd, f= ~-z+b ),times=1000)

Temps médian: 1567

library(plyr)
microbenchmark(arrange(dd,desc(z),b),times=1000)

Temps médian: 862

library(doBy)
microbenchmark(orderBy(~-z+b, data=dd),times=1000)

Temps médian: 1694

Notez que Doby prend un bon peu de temps pour charger le paquet.

library(Deducer)
microbenchmark(sortData(dd,c("z","b"),increasing= c(FALSE,TRUE)),times=1000)

Impossible de faire la charge Deducer. Besoins console JGR.

esort <- function(x, sortvar, ...) {
attach(x)
x <- x[with(x,order(sortvar,...)),]
return(x)
detach(x)
}

microbenchmark(esort(dd, -z, b),times=1000)

Il ne semble pas être compatible avec microbenchmark en raison de l'attachement / Détacher.


m <- microbenchmark(
  arrange(dd,desc(z),b),
  sort(dd, f= ~-z+b ),
  dd[with(dd, order(-z, b)), ] ,
  dd[order(-dd$z, dd$b),],
  times=1000
  )

uq <- function(x) { fivenum(x)[4]}  
lq <- function(x) { fivenum(x)[2]}

y_min <- 0 # min(by(m$time,m$expr,lq))
y_max <- max(by(m$time,m$expr,uq)) * 1.05

p <- ggplot(m,aes(x=expr,y=time)) + coord_cartesian(ylim = c( y_min , y_max )) 
p + stat_summary(fun.y=median,fun.ymin = lq, fun.ymax = uq, aes(fill=expr))

 terrain microbenchmark

(lignes étendent depuis quartile inférieur au quartile supérieur, dot est la médiane)


Compte tenu de ces résultats et la simplicité de pesée par rapport à la vitesse, je dois donner le feu vert à arrange dans le paquet plyr . Il a une syntaxe simple mais est presque aussi rapide que la base R commandes avec leurs machinations alambiquées. En général brillant travail Hadley Wickham. Mon seul reproche avec c'est qu'il casse la nomenclature standard R où trier les objets s'appelés par sort(object), mais je comprends pourquoi Hadley a fait de cette façon en raison de problèmes abordés dans la question liée ci-dessus.

La réponse de Dirk est grande. Il met également en évidence une différence clé dans la syntaxe utilisée pour data.frames d'indexation et data.tables:

## The data.frame way
dd[with(dd, order(-z, b)), ]

## The data.table way: (7 fewer characters, but that's not the important bit)
dd[order(-z, b)]

La différence entre les deux appels est petit, mais il peut avoir des conséquences importantes. Surtout si vous écrivez le code de production et / ou sont concernés par exactitude dans votre recherche, il est préférable d'éviter la répétition inutile des noms de variables. data.table  vous aide à le faire.

Voici un exemple de la façon dont la répétition des noms de variables pourrait vous causer des problèmes:

Changeons le contexte de la réponse de Dirk et dire que cela fait partie d'un plus grand projet où il y a beaucoup de noms d'objets et ils sont longues et significatives; au lieu de dd il est appelé quarterlyreport. Il devient:

quarterlyreport[with(quarterlyreport,order(-z,b)),]

Ok, très bien. Aucun problème avec ça. Suivant votre patron vous demande d'inclure le rapport du dernier trimestre dans le rapport. Vous passez par votre code, l'ajout d'un lastquarterlyreport d'objets en divers endroits et en quelque sorte vous vous retrouvez avec ce (comment diable?):

quarterlyreport[with(lastquarterlyreport,order(-z,b)),]

Ce n'est pas ce que vous vouliez dire, mais vous ne l'avez pas repéré parce que vous l'avez fait vite et il est niché sur une page de code similaire. Le code ne tombe pas (aucun avertissement et aucune erreur) parce que R pense qu'il est ce que vous vouliez dire. Vous souhaiteriez que celui qui lit votre rapport, il voit, mais peut-être ils ne sont pas. Si vous travaillez avec des langages de programmation beaucoup alors cette situation peut être tout familier. C'était une « erreur typographique » vous allez dire. Je vais corriger la vous « faute de frappe » allez dire à votre patron.

data.table nous sommes préoccupés par les petits détails comme celui-ci. Nous avons donc fait quelque chose de simple pour éviter de taper des noms de variables deux fois. Quelque chose de très simple. i est évaluée dans le cadre de dd déjà, automatiquement. Vous n'avez pas besoin with() du tout.

Au lieu de

dd[with(dd, order(-z, b)), ]

il est juste

dd[order(-z, b)]

au lieu de

quarterlyreport[with(lastquarterlyreport,order(-z,b)),]

il est juste

quarterlyreport[order(-z,b)]

Il est une différence très petite, mais il pourrait juste sauver votre cou un jour. Lors de la pesée les différentes réponses à cette question, pensez à compter les répétitions de noms de variables comme l'un de vos critères pour décider. Certaines réponses ont assez peu de répétitions, d'autres ont pas.

Il y a beaucoup d'excellentes réponses, mais dplyr donne la seule syntaxe que je peux me rappeler rapidement et facilement (et donc maintenant utiliser très souvent):

library(dplyr)
# sort mtcars by mpg, ascending... use desc(mpg) for descending
arrange(mtcars, mpg)
# sort mtcars first by mpg, then by cyl, then by wt)
arrange(mtcars , mpg, cyl, wt)

Pour le problème de l'OP:

arrange(dd, desc(z),  b)

    b x y z
1 Low C 9 2
2 Med D 3 1
3  Hi A 8 1
4  Hi A 9 1

Le package R data.table fournit à la fois rapide et mémoire efficace commande de data.tables avec une syntaxe simple (dont une partie a Matt mis en évidence tout à fait bien dans sa réponse ). Il y a eu beaucoup d'améliorations et aussi une nouvelle setorder() fonction depuis. De v1.9.5+, setorder() travaille également avec data.frames .

Tout d'abord, nous allons créer un ensemble de données assez grand et comparer les différentes méthodes mentionnées d'autres réponses et la liste alors les caractéristiques de data.table .

Données:

require(plyr)
require(doBy)
require(data.table)
require(dplyr)
require(taRifx)

set.seed(45L)
dat = data.frame(b = as.factor(sample(c("Hi", "Med", "Low"), 1e8, TRUE)),
                 x = sample(c("A", "D", "C"), 1e8, TRUE),
                 y = sample(100, 1e8, TRUE),
                 z = sample(5, 1e8, TRUE), 
                 stringsAsFactors = FALSE)

Repères:

Les horaires indiqués sont en cours d'exécution de system.time(...) sur ces fonctions ci-dessous. Les horaires sont présentés ci-dessous (dans l'ordre le plus lent au plus rapide).

orderBy( ~ -z + b, data = dat)     ## doBy
plyr::arrange(dat, desc(z), b)     ## plyr
arrange(dat, desc(z), b)           ## dplyr
sort(dat, f = ~ -z + b)            ## taRifx
dat[with(dat, order(-z, b)), ]     ## base R

# convert to data.table, by reference
setDT(dat)

dat[order(-z, b)]                  ## data.table, base R like syntax
setorder(dat, -z, b)               ## data.table, using setorder()
                                   ## setorder() now also works with data.frames 

# R-session memory usage (BEFORE) = ~2GB (size of 'dat')
# ------------------------------------------------------------
# Package      function    Time (s)  Peak memory   Memory used
# ------------------------------------------------------------
# doBy          orderBy      409.7        6.7 GB        4.7 GB
# taRifx           sort      400.8        6.7 GB        4.7 GB
# plyr          arrange      318.8        5.6 GB        3.6 GB 
# base R          order      299.0        5.6 GB        3.6 GB
# dplyr         arrange       62.7        4.2 GB        2.2 GB
# ------------------------------------------------------------
# data.table      order        6.2        4.2 GB        2.2 GB
# data.table   setorder        4.5        2.4 GB        0.4 GB
# ------------------------------------------------------------
  • syntaxe data.table de DT[order(...)] était ~ 10x plus vite que le plus rapide des autres méthodes (dplyr), tout en consommant la même quantité de mémoire dplyr.

  • La data.table de setorder() était ~ 14x plus vite que le plus rapide des autres méthodes (dplyr), tout en prenant juste 0.4GB mémoire supplémentaire . dat est maintenant dans l'ordre dont nous avons besoin (comme il est mis à jour par référence).

Caractéristiques data.table:

Vitesse:

  • data.table ordre d ' est extrêmement rapide, car il met en œuvre radix commande .

  • La DT[order(...)] de syntaxe est optimisée en interne à utiliser data.table de commande rapide aussi bien. Vous pouvez continuer à utiliser la syntaxe de base de R familière, mais accélérer le processus (et utiliser moins de mémoire).

Mémoire:

  • La plupart du temps, nous ne nécessitent pas l'original data.frame ou data.table après remise en ordre. Autrement dit, nous attribuons habituellement le résultat au même objet, par exemple:

    DF <- DF[order(...)]
    

    Le problème est que cela nécessite au moins deux fois (2x) la mémoire de l'objet d'origine. Pour être mémoire efficace , data.table donc également une setorder() fonction.

    setorder() réassorts> data.tables by reference ( en place ), sans faire de copies supplémentaires. Il utilise uniquement la mémoire supplémentaire égale à la taille d'une colonne.

Autres caractéristiques:

  1. Il prend en charge integer, logical, numeric, character et même types bit64::integer64.

      

    Notez que factor, Date, etc .. POSIXct classes sont tous les types integer / numeric sous avec des attributs supplémentaires et sont donc pris en charge.

  2. Dans la base R, nous ne pouvons pas utiliser - sur un vecteur de caractères pour trier par cette colonne dans l'ordre décroissant. nous devons plutôt utiliser -xtfrm(.).

    Cependant, dans data.table , nous pouvons simplement faire, par exemple, dat[order(-x)] ou setorder(dat, -x).

ce (très utile) fonction de Kevin Wright , posté dans la section des conseils du wiki R, cela est facilement atteint.

sort(dd,by = ~ -z + b)
#     b x y z
# 4 Low C 9 2
# 2 Med D 3 1
# 1  Hi A 8 1
# 3  Hi A 9 1

ou vous pouvez utiliser package Doby

library(doBy)
dd <- orderBy(~-z+b, data=dd)

Supposons que vous ayez un data.frame de A et que vous voulez faire le tri en utilisant la colonne appelée x ordre décroissant. Appelez le data.frame de newdata trié

newdata <- A[order(-A$x),]

Si vous voulez remplacer l'ordre croissant, puis "-" rien. Vous pouvez avoir quelque chose comme

newdata <- A[order(-A$x, A$y, -A$z),]

x et z quelques colonnes dans data.frame A. Cela signifie sorte data.frame A par x descendant, y ascendant et descendant z.

En variante, en utilisant le paquet Deducer

library(Deducer)
dd<- sortData(dd,c("z","b"),increasing= c(FALSE,TRUE))

si SQL vient à vous naturellement, poignées sqldf ORDER BY comme Codd prévu.

La réponse de Dirk est bonne, mais si vous avez besoin du genre à vous persister aurez envie d'appliquer de nouveau le genre sur le nom de cette trame de données. En utilisant l'exemple de code:

dd <- dd[with(dd, order(-z, b)), ] 

J'ai appris order avec l'exemple suivant qui m'a alors confondu pendant longtemps:

set.seed(1234)

ID        = 1:10
Age       = round(rnorm(10, 50, 1))
diag      = c("Depression", "Bipolar")
Diagnosis = sample(diag, 10, replace=TRUE)

data = data.frame(ID, Age, Diagnosis)

databyAge = data[order(Age),]
databyAge

La seule raison pour laquelle cet exemple fonctionne parce que order est le tri par le vector Age, non pas par la colonne nommée Age dans le data frame data.

Pour voir cela crée une trame de données identiques en utilisant read.table avec les noms de colonnes légèrement différentes et sans utiliser un quelconque des vecteurs ci-dessus:

my.data <- read.table(text = '

  id age  diagnosis
   1  49 Depression
   2  50 Depression
   3  51 Depression
   4  48 Depression
   5  50 Depression
   6  51    Bipolar
   7  49    Bipolar
   8  49    Bipolar
   9  49    Bipolar
  10  49 Depression

', header = TRUE)

La structure de la ligne ci-dessus pour order ne fonctionne plus parce qu'il n'y a pas de vecteur nommé age:

databyage = my.data[order(age),]

La ligne suivante fonctionne parce que les sortes de order sur la age de colonne dans my.data.

databyage = my.data[order(my.data$age),]

Je pensais que cela valait l'affichage étant donné la confusion que j'étais par cet exemple depuis si longtemps. Si ce poste ne semble pas approprié pour le fil, je peux le supprimer.

EDIT: 13 mai 2014

Voici une façon générale de trier une trame de données par chaque colonne sans spécifier les noms de colonnes. Le code ci-dessous montre comment trier de gauche à droite ou de droite à gauche. Cela fonctionne si chaque colonne est numérique. Je ne l'ai pas essayé avec une colonne de caractères ajouté.

J'ai trouvé le code do.call un mois ou deux il y a dans un ancien poste sur un autre site, mais seulement après une recherche approfondie et difficile. Je ne suis pas sûr que je pourrais déplacer ce poste maintenant. Le fil est présent le premier coup pour commander un data.frame dans R. Donc, je pensais que ma version étoffée de ce code de do.call d'origine pourrait être utile.

set.seed(1234)

v1  <- c(0,0,0,0, 0,0,0,0, 1,1,1,1, 1,1,1,1)
v2  <- c(0,0,0,0, 1,1,1,1, 0,0,0,0, 1,1,1,1)
v3  <- c(0,0,1,1, 0,0,1,1, 0,0,1,1, 0,0,1,1)
v4  <- c(0,1,0,1, 0,1,0,1, 0,1,0,1, 0,1,0,1)

df.1 <- data.frame(v1, v2, v3, v4) 
df.1

rdf.1 <- df.1[sample(nrow(df.1), nrow(df.1), replace = FALSE),]
rdf.1

order.rdf.1 <- rdf.1[do.call(order, as.list(rdf.1)),]
order.rdf.1

order.rdf.2 <- rdf.1[do.call(order, rev(as.list(rdf.1))),]
order.rdf.2

rdf.3 <- data.frame(rdf.1$v2, rdf.1$v4, rdf.1$v3, rdf.1$v1) 
rdf.3

order.rdf.3 <- rdf.1[do.call(order, as.list(rdf.3)),]
order.rdf.3

En réponse à un commentaire ajouté dans l'OP pour savoir comment trier programme:

Utilisation dplyr et data.table

library(dplyr)
library(data.table)

dplyr

Il suffit d'utiliser arrange_, qui est la version d'évaluation standard pour arrange.

df1 <- tbl_df(iris)
#using strings or formula
arrange_(df1, c('Petal.Length', 'Petal.Width'))
arrange_(df1, ~Petal.Length, ~Petal.Width)
    Source: local data frame [150 x 5]

   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          (dbl)       (dbl)        (dbl)       (dbl)  (fctr)
1           4.6         3.6          1.0         0.2  setosa
2           4.3         3.0          1.1         0.1  setosa
3           5.8         4.0          1.2         0.2  setosa
4           5.0         3.2          1.2         0.2  setosa
5           4.7         3.2          1.3         0.2  setosa
6           5.4         3.9          1.3         0.4  setosa
7           5.5         3.5          1.3         0.2  setosa
8           4.4         3.0          1.3         0.2  setosa
9           5.0         3.5          1.3         0.3  setosa
10          4.5         2.3          1.3         0.3  setosa
..          ...         ...          ...         ...     ...


#Or using a variable
sortBy <- c('Petal.Length', 'Petal.Width')
arrange_(df1, .dots = sortBy)
    Source: local data frame [150 x 5]

   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          (dbl)       (dbl)        (dbl)       (dbl)  (fctr)
1           4.6         3.6          1.0         0.2  setosa
2           4.3         3.0          1.1         0.1  setosa
3           5.8         4.0          1.2         0.2  setosa
4           5.0         3.2          1.2         0.2  setosa
5           4.7         3.2          1.3         0.2  setosa
6           5.5         3.5          1.3         0.2  setosa
7           4.4         3.0          1.3         0.2  setosa
8           4.4         3.2          1.3         0.2  setosa
9           5.0         3.5          1.3         0.3  setosa
10          4.5         2.3          1.3         0.3  setosa
..          ...         ...          ...         ...     ...

#Doing the same operation except sorting Petal.Length in descending order
sortByDesc <- c('desc(Petal.Length)', 'Petal.Width')
arrange_(df1, .dots = sortByDesc)

plus d'infos ici: https://cran.r-project.org/web /packages/dplyr/vignettes/nse.html

Il est préférable d'utiliser la formule car il capture aussi l'environnement pour évaluer une expression dans

data.table

dt1 <- data.table(iris) #not really required, as you can work directly on your data.frame
sortBy <- c('Petal.Length', 'Petal.Width')
sortType <- c(-1, 1)
setorderv(dt1, sortBy, sortType)
dt1
     Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
  1:          7.7         2.6          6.9         2.3 virginica
  2:          7.7         2.8          6.7         2.0 virginica
  3:          7.7         3.8          6.7         2.2 virginica
  4:          7.6         3.0          6.6         2.1 virginica
  5:          7.9         3.8          6.4         2.0 virginica
 ---                                                            
146:          5.4         3.9          1.3         0.4    setosa
147:          5.8         4.0          1.2         0.2    setosa
148:          5.0         3.2          1.2         0.2    setosa
149:          4.3         3.0          1.1         0.1    setosa
150:          4.6         3.6          1.0         0.2    setosa

L 'ARRANGEMENT () dans dplyer est mon option préférée. Utilisez l'opérateur de conduite et passer du moins important aspect le plus important

dd1 <- dd %>%
    arrange(z) %>%
    arrange(desc(x))

Par souci d'exhaustivité: vous pouvez également utiliser la fonction sortByCol() du paquet BBmisc:

library(BBmisc)
sortByCol(dd, c("z", "b"), asc = c(FALSE, TRUE))
    b x y z
4 Low C 9 2
2 Med D 3 1
1  Hi A 8 1
3  Hi A 9 1

Comparaison des performances:

library(microbenchmark)
microbenchmark(sortByCol(dd, c("z", "b"), asc = c(FALSE, TRUE)), times = 100000)
median 202.878

library(plyr)
microbenchmark(arrange(dd,desc(z),b),times=100000)
median 148.758

microbenchmark(dd[with(dd, order(-z, b)), ], times = 100000)
median 115.872

Tout comme les trieuses de cartes mécaniques d'il y a longtemps, Tri le moins clé importante, la prochaine plus importante, etc. Aucune bibliothèque nécessaire, travaille avec un certain nombre de clés et une combinaison de touches ascendante et descendante.

 dd <- dd[order(dd$b, decreasing = FALSE),]

Maintenant, nous sommes prêts à faire la clé la plus importante. Le tri est stable, et les liens en ont déjà été résolus la clé la plus importante.

dd <- dd[order(dd$z, decreasing = TRUE),]

Cela peut ne pas être le plus rapide, mais il est certainement simple et fiable

Une autre alternative, en utilisant le package rgr:

> library(rgr)
> gx.sort.df(dd, ~ -z+b)
    b x y z
4 Low C 9 2
2 Med D 3 1
1  Hi A 8 1
3  Hi A 9 1

Je luttais avec les solutions ci-dessus quand je voulais automatiser mon processus de commande pour n colonnes, dont les noms colonne pourrait être différent à chaque fois. J'ai trouvé une fonction super utile du paquet psych de le faire d'une manière simple:

dfOrder(myDf, columnIndices)

columnIndices sont des indices d'une ou plusieurs colonnes, dans l'ordre dans lequel vous voulez les trier. Plus d'informations ici:

fonction dfOrder de paquet 'psych'

Juste pour être complet, puisque pas beaucoup a été dit sur le tri par nombre de colonnes ... On peut certainement affirmer qu'il est souvent pas souhaitable (parce que l'ordre des colonnes pourrait changer, ouvrant la voie à des erreurs ), mais dans certaines situations spécifiques (lorsque, par exemple, vous avez besoin d'un travail rapide fait et il n'y a pas de risque de colonnes changeantes commandes), il peut être la chose la plus sensée à faire, surtout lorsqu'ils traitent avec un grand nombre de colonnes.

Dans ce cas, do.call() vient à la rescousse:

ind <- do.call(what = "order", args = iris[,c(5,1,2,3)])
iris[ind, ]

##        Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
##    14           4.3         3.0          1.1         0.1     setosa
##    9            4.4         2.9          1.4         0.2     setosa
##    39           4.4         3.0          1.3         0.2     setosa
##    43           4.4         3.2          1.3         0.2     setosa
##    42           4.5         2.3          1.3         0.3     setosa
##    4            4.6         3.1          1.5         0.2     setosa
##    48           4.6         3.2          1.4         0.2     setosa
##    7            4.6         3.4          1.4         0.3     setosa
##    (...)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top