À partir d'un cadre de données comme celui-ci
test <- data.frame('id'= rep(1:5,2), 'string'= LETTERS[1:10])
test <- test[order(test$id), ]
rownames(test) <- 1:10
> test
id string
1 1 A
2 1 F
3 2 B
4 2 G
5 3 C
6 3 H
7 4 D
8 4 I
9 5 E
10 5 J
Je veux en créer un nouveau avec la première ligne de chaque paire id/string. Si sqldf accepte le code R qu'il contient, la requête pourrait ressembler à ceci:
res <- sqldf("select id, min(rownames(test)), string
from test
group by id, string")
> res
id string
1 1 A
3 2 B
5 3 C
7 4 D
9 5 E
Y at-il une solution à court de créer une nouvelle colonne comme
test$row <- rownames(test)
et en exécutant la même requête sqldf avec min (ligne)?
Vous pouvez utiliser duplicated
pour le faire très rapidement.
test[!duplicated(test$id),]
Benchmarks, pour les freaks de vitesse:
ju <- function() test[!duplicated(test$id),]
gs1 <- function() do.call(rbind, lapply(split(test, test$id), head, 1))
gs2 <- function() do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
jply <- function() ddply(test,.(id),function(x) head(x,1))
jdt <- function() {
testd <- as.data.table(test)
setkey(testd,id)
# Initial solution (slow)
# testd[,lapply(.SD,function(x) head(x,1)),by = key(testd)]
# Faster options :
testd[!duplicated(id)] # (1)
# testd[, .SD[1L], by=key(testd)] # (2)
# testd[J(unique(id)),mult="first"] # (3)
# testd[ testd[,.I[1L],by=id] ] # (4) needs v1.8.3. Allows 2nd, 3rd etc
}
library(plyr)
library(data.table)
library(rbenchmark)
# sample data
set.seed(21)
test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
test <- test[order(test$id), ]
benchmark(ju(), gs1(), gs2(), jply(), jdt(),
replications=5, order="relative")[,1:6]
# test replications elapsed relative user.self sys.self
# 1 ju() 5 0.03 1.000 0.03 0.00
# 5 jdt() 5 0.03 1.000 0.03 0.00
# 3 gs2() 5 3.49 116.333 2.87 0.58
# 2 gs1() 5 3.58 119.333 3.00 0.58
# 4 jply() 5 3.69 123.000 3.11 0.51
Essayons encore une fois, mais avec seulement les prétendants de la première manche, avec plus de données et plus de réplications.
set.seed(21)
test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
test <- test[order(test$id), ]
benchmark(ju(), jdt(), order="relative")[,1:6]
# test replications elapsed relative user.self sys.self
# 1 ju() 100 5.48 1.000 4.44 1.00
# 2 jdt() 100 6.92 1.263 5.70 1.15
Qu'en est-il de
DT <- data.table(test)
setkey(DT, id)
DT[J(unique(id)), mult = "first"]
Il existe également une méthode unique pour data.tables
qui retournera la première ligne par clé
jdtu <- function() unique(DT)
Je pense que si vous commandez test
en dehors du point de référence, vous pouvez supprimer les setkey
et data.table
conversion du repère également (comme setkey trie essentiellement par id, comme order
).
set.seed(21)
test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
test <- test[order(test$id), ]
DT <- data.table(DT, key = 'id')
ju <- function() test[!duplicated(test$id),]
jdt <- function() DT[J(unique(id)),mult = 'first']
library(rbenchmark)
benchmark(ju(), jdt(), replications = 5)
## test replications elapsed relative user.self sys.self
## 2 jdt() 5 0.01 1 0.02 0
## 1 ju() 5 0.05 5 0.05 0
et avec plus de données
** Modifier avec une méthode unique **
set.seed(21)
test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
test <- test[order(test$id), ]
DT <- data.table(test, key = 'id')
test replications elapsed relative user.self sys.self
2 jdt() 5 0.09 2.25 0.09 0.00
3 jdtu() 5 0.04 1.00 0.05 0.00
1 ju() 5 0.22 5.50 0.19 0.03
La méthode unique est la plus rapide ici.
Je privilégie l'approche Dplyr.
group_by(id)
suivi de l'un ou l'autre
filter(row_number()==1)
ouslice(1)
outop_n(n = -1)
top_n()
utilise en interne la fonction rank. Negative sélectionne depuis le bas du rang.Dans certains cas, il peut être nécessaire de disposer les identifiants après group_by.
library(dplyr)
# using filter(), top_n() or slice()
m1 <-
test %>%
group_by(id) %>%
filter(row_number()==1)
m2 <-
test %>%
group_by(id) %>%
slice(1)
m3 <-
test %>%
group_by(id) %>%
top_n(n = -1)
Les trois méthodes donnent le même résultat
# A tibble: 5 x 2
# Groups: id [5]
id string
<int> <fct>
1 1 A
2 2 B
3 3 C
4 4 D
5 5 E
Une simple option ddply
:
ddply(test,.(id),function(x) head(x,1))
Si la rapidité est un problème, une approche similaire pourrait être adoptée avec data.table
:
testd <- data.table(test)
setkey(testd,id)
testd[,.SD[1],by = key(testd)]
ou cela pourrait être considérablement plus rapide:
testd[testd[, .I[1], by = key(testd]$V1]
maintenant, pour dplyr
, ajouter un compteur distinct.
df %>%
group_by(aa, bb) %>%
summarise(first=head(value,1), count=n_distinct(value))
Vous créez des groupes, résumez-les au sein de groupes.
Si les données sont numériques, vous pouvez utiliser:first(value)
[il y a aussi last(value)
] à la place de head(value, 1)
voir: http://cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html
Plein:
> df
Source: local data frame [16 x 3]
aa bb value
1 1 1 GUT
2 1 1 PER
3 1 2 SUT
4 1 2 GUT
5 1 3 SUT
6 1 3 GUT
7 1 3 PER
8 2 1 221
9 2 1 224
10 2 1 239
11 2 2 217
12 2 2 221
13 2 2 224
14 3 1 GUT
15 3 1 HUL
16 3 1 GUT
> library(dplyr)
> df %>%
> group_by(aa, bb) %>%
> summarise(first=head(value,1), count=n_distinct(value))
Source: local data frame [6 x 4]
Groups: aa
aa bb first count
1 1 1 GUT 2
2 1 2 SUT 2
3 1 3 SUT 3
4 2 1 221 3
5 2 2 217 3
6 3 1 GUT 2
(1) SQLite a une pseudo-colonne rowid
intégrée pour que cela fonctionne:
sqldf("select min(rowid) rowid, id, string
from test
group by id")
donnant:
rowid id string
1 1 1 A
2 3 2 B
3 5 3 C
4 7 4 D
5 9 5 E
(2) Aussi sqldf
lui-même a un row.names=
argument:
sqldf("select min(cast(row_names as real)) row_names, id, string
from test
group by id", row.names = TRUE)
donnant:
id string
1 1 A
3 2 B
5 3 C
7 4 D
9 5 E
(3) Une troisième alternative qui mélange les éléments des deux précédents pourrait être encore meilleure:
sqldf("select min(rowid) row_names, id, string
from test
group by id", row.names = TRUE)
donnant:
id string
1 1 A
3 2 B
5 3 C
7 4 D
9 5 E
Notez que tous les trois reposent sur une extension SQLite à SQL dans laquelle l'utilisation de min
ou max
a pour résultat que les autres colonnes sont choisies dans la même ligne. (Dans d'autres bases de données SQL, il peut ne pas être garanti.)
Une option de base R est la split()
-lapply()
-do.call()
idiome:
> do.call(rbind, lapply(split(test, test$id), head, 1))
id string
1 1 A
2 2 B
3 3 C
4 4 D
5 5 E
Une option plus directe consiste à lapply()
la fonction [
:
> do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
id string
1 1 A
2 2 B
3 3 C
4 4 D
5 5 E
La virgule 1, )
À la fin de l'appel lapply()
est essentiel, car cela équivaut à l'appel de [1, ]
Pour sélectionner la première ligne et tous colonnes.