test <- data.frame('id'= rep(1:5,2), 'string'= LETTERS[1:10]) test <- test[order(test$id), ] rownames(test) test id ssortingng 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 apparition de chaque paire identifiant / chaîne. Si sqldf acceptait le code R, la requête pourrait ressembler à ceci:
res res id ssortingng 1 1 A 3 2 B 5 3 C 7 4 D 9 5 E
Existe-t-il une solution sans créer une nouvelle colonne comme
test$row <- rownames(test)
et exécuter la même requête sqldf avec min (row)?
Vous pouvez utiliser la duplicated
pour le faire très rapidement.
test[!duplicated(test$id),]
Repères, pour les amateurs 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 à nouveau, mais avec seulement les concurrents de la première manche et avec plus de données et plus de répétitions.
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 renverra la première ligne par clé
jdtu <- function() unique(DT)
Je pense que si vous commandez un test
dehors du benchmark, vous pouvez également supprimer la conversion setkey
et data.table
du benchmark (car la setkey sortinge essentiellement par id, le même que 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.
Une simple option ddply
:
ddply(test,.(id),function(x) head(x,1))
Si la vitesse pose problème, une approche similaire pourrait être adoptée avec data.table
:
testd <- data.table(test) setkey(testd,id) testd[,lapply(.SD,function(x) head(x,1)),by = key(testd)]
(1) SQLite a une pseudo-colonne rowid
, donc ça marche:
sqldf("select min(rowid) rowid, id, ssortingng from test group by id")
donnant:
rowid id ssortingng 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 argument row.names=
:
sqldf("select min(cast(row_names as real)) row_names, id, ssortingng from test group by id", row.names = TRUE)
donnant:
id ssortingng 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, ssortingng from test group by id", row.names = TRUE)
donnant:
id ssortingng 1 1 A 3 2 B 5 3 C 7 4 D 9 5 E
Notez que ces trois éléments reposent sur une extension SQLite de SQL où l’utilisation de min
ou max
garantit que les autres colonnes seront choisies dans la même ligne. (Dans d’autres bases de données SQL qui peuvent ne pas être garanties.)
maintenant, pour dplyr
, append un compteur distinct.
df %>% group_by(aa, bb) %>% summarise(first=head(value,1), count=n_distinct(value))
Vous créez des groupes, les résument au sein des 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
Une option de base R est l’idiome split()
– lapply()
– do.call()
:
> do.call(rbind, lapply(split(test, test$id), head, 1)) id ssortingng 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 ssortingng 1 1 A 2 2 B 3 3 C 4 4 D 5 5 E
L’espace virgule 1, )
à la fin de l’appel lapply()
est essentiel car cela équivaut à appeler [1, ]
pour sélectionner la première ligne et toutes les colonnes.
Je suis en faveur de l’approche dplyr.
library(dplyr) test %>% group_by(id) %>% filter(row_number()==1) # A tibble: 5 x 2 # Groups: id [5] id ssortingng 1 1 A 2 2 B 3 3 C 4 4 D 5 5 E
Regrouper par identifiant et filtre pour ne renvoyer que la première ligne. Dans certains cas, organiser les identifiants après le group_by peut être nécessaire.
test_subset <- test[unique(test$id),]
Juste cette ligne générera le sous-ensemble que vous voulez.