web-dev-qa-db-fra.com

Sélectionnez les N premières valeurs par groupe

Ceci est en réponse à une question posée sur la liste de diffusion de r-help .

Voici beaucoup d'exemples sur la façon de rechercher les valeurs supérieures par groupe à l'aide de sql, alors j'imagine qu'il est facile de convertir cette connaissance par rapport au package R sqldf.

Un exemple: lorsque mtcars est groupé par cyl, voici les trois premiers enregistrements pour chaque valeur distincte de cyl. Notez que les cravates sont exclues dans ce cas, mais il serait agréable de montrer différentes manières de traiter les cravates.

                     mpg cyl  disp  hp drat    wt  qsec vs am gear carb ranks
Toyota Corona       21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1   2.0
Volvo 142E          21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2   1.0
Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1   2.0
Merc 280            19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4   3.0
Merc 280C           17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4   1.0
Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4   1.5
Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4   1.5
Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4   3.0

Comment trouver les N ou les premiers ou les derniers (maximum ou minimum) N enregistrements par groupe?

35
Anthony Damico

Cela semble plus simple en utilisant data.table car il effectue le tri tout en définissant la clé.

Donc, si je devais obtenir les 3 premiers enregistrements en tri (ordre croissant), alors,

require(data.table)
d <- data.table(mtcars, key="cyl")
d[, head(.SD, 3), by=cyl]

le fait.

Et si vous voulez l'ordre décroissant

d[, tail(.SD, 3), by=cyl] # Thanks @MatthewDowle

Éditer: Pour trier les liens en utilisant la colonne mpg

d <- data.table(mtcars, key="cyl")
d.out <- d[, .SD[mpg %in% head(sort(unique(mpg)), 3)], by=cyl]

#     cyl  mpg  disp  hp drat    wt  qsec vs am gear carb rank
#  1:   4 22.8 108.0  93 3.85 2.320 18.61  1  1    4    1   11
#  2:   4 22.8 140.8  95 3.92 3.150 22.90  1  0    4    2    1
#  3:   4 21.5 120.1  97 3.70 2.465 20.01  1  0    3    1    8
#  4:   4 21.4 121.0 109 4.11 2.780 18.60  1  1    4    2    6
#  5:   6 18.1 225.0 105 2.76 3.460 20.22  1  0    3    1    7
#  6:   6 19.2 167.6 123 3.92 3.440 18.30  1  0    4    4    1
#  7:   6 17.8 167.6 123 3.92 3.440 18.90  1  0    4    4    2
#  8:   8 14.3 360.0 245 3.21 3.570 15.84  0  0    3    4    7
#  9:   8 10.4 472.0 205 2.93 5.250 17.98  0  0    3    4   14
# 10:   8 10.4 460.0 215 3.00 5.424 17.82  0  0    3    4    5
# 11:   8 13.3 350.0 245 3.73 3.840 15.41  0  0    3    4    3

# and for last N elements, of course it is straightforward
d.out <- d[, .SD[mpg %in% tail(sort(unique(mpg)), 3)], by=cyl]
35
Arun

Il suffit de trier par quelque chose (mpg par exemple, la question n'est pas claire à ce sujet)

mt <- mtcars[order(mtcars$mpg), ]

puis utilisez la fonction by pour obtenir les n premières lignes de chaque groupe

d <- by(mt, mt["cyl"], head, n=4)

Si vous voulez que le résultat soit un data.frame:

Reduce(rbind, d)

Edit: La gestion des liens est plus difficile, mais si tous les liens sont souhaités:

by(mt, mt["cyl"], function(x) x[rank(x$mpg) %in% sort(unique(rank(x$mpg)))[1:4], ])

Une autre approche consiste à rompre les liens en se basant sur une autre information, par exemple: 

mt <- mtcars[order(mtcars$mpg, mtcars$hp), ]
by(mt, mt["cyl"], head, n=4)
19
Ista

dplyr fait le tour

mtcars %>% 
arrange(desc(mpg)) %>% 
group_by(cyl) %>% slice(1:2)


 mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1  33.9     4  71.1    65  4.22 1.835 19.90     1     1     4     1
2  32.4     4  78.7    66  4.08 2.200 19.47     1     1     4     1
3  21.4     6 258.0   110  3.08 3.215 19.44     1     0     3     1
4  21.0     6 160.0   110  3.90 2.620 16.46     0     1     4     4
5  19.2     8 400.0   175  3.08 3.845 17.05     0     0     3     2
6  18.7     8 360.0   175  3.15 3.440 17.02     0     0     3     2
8
Azam Yahya

S'il y avait une égalité à la quatrième position pour mtcars $ mpg, cela devrait renvoyer toutes les égalités:

top_mpg <- mtcars[ mtcars$mpg >= mtcars$mpg[order(mtcars$mpg, decreasing=TRUE)][4] , ]

> top_mpg
                mpg cyl disp  hp drat    wt  qsec vs am gear carb
Fiat 128       32.4   4 78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic    30.4   4 75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla 33.9   4 71.1  65 4.22 1.835 19.90  1  1    4    1
Lotus Europa   30.4   4 95.1 113 3.77 1.513 16.90  1  1    5    2

Puisqu'il y a une égalité à la position 3-4, vous pouvez la tester en remplaçant 4 par un 3 et il renvoie toujours 4 éléments. Ceci est une indexation logique et vous devrez peut-être ajouter une clause qui supprime les NA ou encapsule quel () autour de l'expression logique. Ce n'est pas beaucoup plus difficile de faire cela "par" cyl:

 Reduce(rbind,  by(mtcars, mtcars$cyl, 
        function(d) d[ d$mpg >= d$mpg[order(d$mpg, decreasing=TRUE)][4] , ]) )
#-------------
                   mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Fiat 128          32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic       30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla    33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Lotus Europa      30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
Mazda RX4         21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Hornet 4 Drive    21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
Ferrari Dino      19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
Hornet Sportabout 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
Merc 450SE        16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
Merc 450SL        17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
Pontiac Firebird  19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2

Incorporer ma suggestion à @Ista:

Reduce(rbind,  by(mtcars, mtcars$cyl, function(d) d[ d$mpg <= sort( d$mpg )[3] , ]) )
4
42-

Vous pouvez écrire une fonction qui divise la base de données par un facteur, les ordres par une autre variable souhaitée, extraire le nombre de lignes souhaitées dans chaque facteur (catégorie) et les combiner dans une base de données.

top<-function(x, num, c1,c2){
sorted<-x[with(x,order(x[,c1],x[,c2],decreasing=T)),]
splits<-split(sorted,sorted[,c1])
df<-lapply(splits,head,num)
do.call(rbind.data.frame,df)}

x est la trame de données;

num est le nombre de lignes que vous souhaitez voir.

c1 est la colonne nombre de la variable avec laquelle vous souhaitez fractionner;

c2 est la colonne nombre de la variable que vous souhaitez classer ou gérer liens.

En utilisant les données mtcars, la fonction extrait les voitures 3 les plus lourdes (mtcars $ wt est la colonne 6 e) dans chaque classe de cylindre (mtcars $ cyl est la colonne 2 nd)

 top(mtcars,3,2,6)
                         mpg cyl  disp  hp drat    wt  qsec vs am gear carb
 4.Merc 240D           24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
 4.Merc 230            22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
 4.Volvo 142E          21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2
 6.Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
 6.Merc 280            19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
 6.Merc 280C           17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
 8.Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
 8.Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
 8.Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4

Vous pouvez aussi facilement obtenir le plus léger d'une classe en remplaçant head dans la fonction lapply par tail OR en supprimant l'argument décroissant = T de la commande order qui retournera à sa valeur par défaut, décroissant = F.

3
Kimberly Gonzales

Je préfère la solution @Ista, car elle ne nécessite aucun paquet supplémentaire et est simple.
Une modification de la solution data.table résout également mon problème, et est plus générale.
Mon data.frame est 

> str(df)
'data.frame':   579 obs. of  11 variables:
 $ trees     : num  2000 5000 1000 2000 1000 1000 2000 5000 5000 1000 ...
 $ interDepth: num  2 3 5 2 3 4 4 2 3 5 ...
 $ minObs    : num  6 4 1 4 10 6 10 10 6 6 ...
 $ shrinkage : num  0.01 0.001 0.01 0.005 0.01 0.01 0.001 0.005 0.005 0.001     ...
 $ G1        : num  0 2 2 2 2 2 8 8 8 8 ...
 $ G2        : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ qx        : num  0.44 0.43 0.419 0.439 0.43 ...
 $ efet      : num  43.1 40.6 39.9 39.2 38.6 ...
 $ prec      : num  0.606 0.593 0.587 0.582 0.574 0.578 0.576 0.579 0.588 0.585 ...
 $ sens      : num  0.575 0.57 0.573 0.575 0.587 0.574 0.576 0.566 0.542 0.545 ...
 $ acu       : num  0.631 0.645 0.647 0.648 0.655 0.647 0.619 0.611 0.591 0.594 ...

La solution data.table a besoin de order sur i pour faire le travail: 

> require(data.table)
> dt1 <- data.table(df)
> dt2 = dt1[order(-efet, G1, G2), head(.SD, 3), by = .(G1, G2)]
> dt2
    G1    G2 trees interDepth minObs shrinkage        qx   efet  prec  sens   acu
 1:  0 FALSE  2000          2      6     0.010 0.4395953 43.066 0.606 0.575 0.631
 2:  0 FALSE  2000          5      1     0.005 0.4294718 37.554 0.583 0.548 0.607
 3:  0 FALSE  5000          2      6     0.005 0.4395753 36.981 0.575 0.559 0.616
 4:  2 FALSE  5000          3      4     0.001 0.4296346 40.624 0.593 0.570 0.645
 5:  2 FALSE  1000          5      1     0.010 0.4186802 39.915 0.587 0.573 0.647
 6:  2 FALSE  2000          2      4     0.005 0.4390503 39.164 0.582 0.575 0.648
 7:  8 FALSE  2000          4     10     0.001 0.4511349 38.240 0.576 0.576 0.619
 8:  8 FALSE  5000          2     10     0.005 0.4469665 38.064 0.579 0.566 0.611
 9:  8 FALSE  5000          3      6     0.005 0.4426952 37.888 0.588 0.542 0.591
10:  2  TRUE  5000          3      4     0.001 0.3812878 21.057 0.510 0.479 0.615
11:  2  TRUE  2000          3     10     0.005 0.3790536 20.127 0.507 0.470 0.608
12:  2  TRUE  1000          5      4     0.001 0.3690911 18.981 0.500 0.475 0.611
13:  8  TRUE  5000          6     10     0.010 0.2865042 16.870 0.497 0.435 0.635
14:  0  TRUE  2000          6      4     0.010 0.3192862  9.779 0.460 0.433 0.621  

Pour une raison quelconque, il n’agit pas comme indiqué (probablement par ordre des groupes). Donc, une autre commande est faite. 

> dt2[order(G1, G2)]
    G1    G2 trees interDepth minObs shrinkage        qx   efet  prec  sens   acu
 1:  0 FALSE  2000          2      6     0.010 0.4395953 43.066 0.606 0.575 0.631
 2:  0 FALSE  2000          5      1     0.005 0.4294718 37.554 0.583 0.548 0.607
 3:  0 FALSE  5000          2      6     0.005 0.4395753 36.981 0.575 0.559 0.616
 4:  0  TRUE  2000          6      4     0.010 0.3192862  9.779 0.460 0.433 0.621
 5:  2 FALSE  5000          3      4     0.001 0.4296346 40.624 0.593 0.570 0.645
 6:  2 FALSE  1000          5      1     0.010 0.4186802 39.915 0.587 0.573 0.647
 7:  2 FALSE  2000          2      4     0.005 0.4390503 39.164 0.582 0.575 0.648
 8:  2  TRUE  5000          3      4     0.001 0.3812878 21.057 0.510 0.479 0.615
 9:  2  TRUE  2000          3     10     0.005 0.3790536 20.127 0.507 0.470 0.608
10:  2  TRUE  1000          5      4     0.001 0.3690911 18.981 0.500 0.475 0.611
11:  8 FALSE  2000          4     10     0.001 0.4511349 38.240 0.576 0.576 0.619
12:  8 FALSE  5000          2     10     0.005 0.4469665 38.064 0.579 0.566 0.611
13:  8 FALSE  5000          3      6     0.005 0.4426952 37.888 0.588 0.542 0.591
14:  8  TRUE  5000          6     10     0.010 0.2865042 16.870 0.497 0.435 0.635
0
xm1