2016-06-06 2 views
2

J'ai une trame de données qui ressemble à ce qui suit:Transposer avec des colonnes uniques en R

  X1 X2 
DocumentID 12345 
    Check# 9876 
Investment Tran1 
Investment$ 200 
Investment Tran5 
Investment$ 100 
DocumentID 23456 
    Check# 8765 
Investment Tran1 
Investment$ 100 
Investment Tran9 
Investment$ 50 
DocumentID 34567 
    Check# 7654 
Investment Tran4 
Investment$ 300 
DocumentID 45678 
    Check# 6543 
Investment Tran2 
Investment$ 10 
Investment Tran5 
Investment$ 20 
Investment Tran9 
Investment$ 70 

Chaque ID de document se situera dans le # des investissements, mais je voudrais remodeler la trame de données de façon à est transposé (large) par DocumentID et possède des colonnes uniques.

Je voudrais que la table à regarder comme ci-dessous:

DocumentID Check# Investment Investment$ 
    12345 9876  Tran1   200 
    12345 9876  Tran5   100 
    23456 8765  Tran1   100 
    23456 8765  Tran9   50 
    34567 7654  Tran4   300 
    45678 6543  Tran2   10 
    45678 6543  Tran5   20 
    45678 6543  Tran9   70 

Alors que l'ID de document et vérification # sont répétées s'il y a plus de 1 investissement dans chaque ID de document.

Appréciez l'aide!

Répondre

3

Vos données sont mal formées, car elles ne possèdent pas d'identifiant unique pour chaque paire de paires valeur/clé, de sorte que les approches habituelles de longueur à grande échelle ne fonctionneront probablement pas sans un certain massage. Vous pouvez faire une colonne appropriée, puis Tartiner chaque rangée à travers les colonnes appropriées, puis remplir et filtre:

library(dplyr) 
library(tidyr) 

     # add row index so spreading will work 
df %>% mutate(row = seq_along(X1)) %>% 
    # spread long to wide, shifting each value into the appropriate column, filling with NA 
    spread(X1, X2, convert = TRUE) %>% 
    # get rid of row index 
    select(-row) %>% 
    # fill in NA values for all but one column... 
    fill(-`Investment$`) %>% 
    # ...so extra NAs in that column make extra rows easy to eliminate 
    filter(complete.cases(.)) 

# Check# DocumentID Investment Investment$ 
# 1 9876  12345  Tran1   200 
# 2 9876  12345  Tran5   100 
# 3 8765  23456  Tran1   100 
# 4 8765  23456  Tran9   50 
# 5 7654  34567  Tran4   300 
# 6 6543  45678  Tran2   10 
# 7 6543  45678  Tran5   20 
# 8 6543  45678  Tran9   70 
2
cns.grp <- c('DocumentID','Check#'); 
ris.dat <- which(!df$X1%in%cns.grp); 
cns.dat <- as.character(unique(df$X1[ris.dat])); 
gs <- cumsum(df$X1==cns.grp[1L])[ris.dat[c(T,rep(F,length(cns.dat)-1L))]]; 
ar <- list(check.names=F); 
with(unstack(df,X2~X1),do.call(data.frame,c(lapply(mget(cns.grp),`[`,gs),mget(cns.dat),ar))); 
## DocumentID Check# Investment Investment$ 
## 1  12345 9876  Tran1   200 
## 2  12345 9876  Tran5   100 
## 3  23456 8765  Tran1   100 
## 4  23456 8765  Tran9   50 
## 5  34567 7654  Tran4   300 
## 6  45678 6543  Tran2   10 
## 7  45678 6543  Tran5   20 
## 8  45678 6543  Tran9   70 

données

df <- structure(list(X1=structure(c(2L,1L,3L,4L,3L,4L,2L,1L,3L,4L,3L,4L,2L,1L,3L,4L,2L,1L,3L, 
4L,3L,4L,3L,4L),.Label=c("Check#","DocumentID","Investment","Investment$"),class="factor"), 
X2=structure(c(3L,15L,16L,5L,19L,2L,6L,14L,16L,2L,20L,10L,8L,13L,18L,7L,9L,11L,17L,1L,19L, 
4L,20L,12L),.Label=c("10","100","12345","20","200","23456","300","34567","45678","50", 
"6543","70","7654","8765","9876","Tran1","Tran2","Tran4","Tran5","Tran9"),class="factor")), 
.Names=c("X1","X2"),row.names=c(NA,-24L),class="data.frame"); 

Explication

Quelles lignes du data.frame d'entrée doivent être considérées comme des marqueurs de regroupement ne peuvent pas être dérivées de l'entrée data.frame elle-même; par conséquent, ils doivent être codés en dur par le programmeur. J'ai donc attribué leurs valeurs X1 à cns.grp. Cela correspond aux noms des colonnes des colonnes de regroupement (car elles serviront de colonnes de regroupement dans la sortie).

ris.dat <- which(!df$X1%in%cns.grp); 

Compte tenu cns.grp, nous pouvons tirer les indices de ligne des colonnes de données en trouvant les indices de X1 qui sont pas égale à l'une des valeurs dans cns.grp.

cns.dat <- as.character(unique(df$X1[ris.dat])); 

ris.dat Étant donné, nous pouvons tirer les noms de colonnes des colonnes de données en obtenant les valeurs X1 uniques à travers les rangées de ris.dat. J'ai ajouté une coercition as.character() pour gérer la possibilité que l'entrée data.frame ait des colonnes de facteur, par opposition aux colonnes de caractères. Afin de diviser correctement le data.frame d'entrée le long de ses groupes, nous devons dériver un vecteur de regroupement. En supposant que le nom de la première colonne de regroupement indique le début d'un groupe (ce qui est une hypothèse raisonnable et semble être la propriété fondamentale de l'entrée data.frame), nous pouvons utiliser cumsum() pour incrémenter chaque occurrence de la première colonne de regroupement. produire un vecteur de regroupement correspondant à toutes les lignes du nom de données d'entrée.Pour aller de l'avant, nous allons utiliser ce vecteur de regroupement pour développer les vecteurs de valeur de regroupement uniques reçus de unstack() le long des instances de colonnes de données uniques. Par exemple, pour chaque ligne d'entrée Investment, nous allons indexer l'élément DocumentID qui lui correspond. Par conséquent, nous devons filtrer le résultat du cumsum() pour une seule instance de chaque groupe par sous-groupe de données. En d'autres termes, pour chaque longueur de length(cns.dat), nous devons obtenir une et une seule instance de cet index de regroupement. Cela peut être réalisé en construisant un vecteur logique de cette longueur avec une seule vraie valeur (peu importe laquelle, puisque tous les éléments de regroupement seront les mêmes sur l'étendue). Nous pouvons construire ce vecteur logique avec c(T,rep(F,length(cns.dat)-1L)), indexer les index de ligne sélectionnés de ris.dat, puis filtrer le résultat cumsum() sur les index de ligne sélectionnés. Le résultat que je stocke dans gs.

ar <- list(check.names=F); 

Ici je viens Précalculer arguments supplémentaires à l'appel data.frame() qui construira la data.frame de sortie. La spécification check.names=F est nécessaire pour protéger les noms de colonnes non-syntaxiques Check# et Investment$ de la normalisation par data.frame(). Vous pouvez également spécifier stringsAsFactors=F pour obtenir des colonnes de caractères au lieu des colonnes de facteur par défaut.

with(unstack(df,X2~X1),do.call(data.frame,c(lapply(mget(cns.grp),`[`,gs),mget(cns.dat),ar))); 

Enfin, nous pouvons unstack() la data.frame pour obtenir chaque colonne de regroupement et de la colonne de données en tant que composante de liste indépendante, et exécuter une expression dans le contexte de ces vecteurs en utilisant with().

Il suffit d'exécuter un seul appel à data.frame() dans ce contexte pour produire la sortie requise. Fondamentalement, nous devons combiner les colonnes de regroupement, récupérées via mget() et correctement étendues par gs, avec les colonnes de données, également récupérées via mget(), et inclure les arguments supplémentaires précalculés ar pour produire la liste d'arguments à data.frame() qui sera relayée par do.call(). Le résultat est la sortie requise.


Analyse comparative

library(dplyr); 
library(tidyr); 
library(microbenchmark); 

bgoldst <- function(df) { cns.grp <- c('DocumentID','Check#'); ris.dat <- which(!df$X1%in%cns.grp); cns.dat <- as.character(unique(df$X1[ris.dat])); gs <- cumsum(df$X1==cns.grp[1L])[ris.dat[c(T,rep(F,length(cns.dat)-1L))]]; ar <- list(check.names=F); with(unstack(df,X2~X1),do.call(data.frame,c(lapply(mget(cns.grp),`[`,gs),mget(cns.dat),ar))); }; 
alistaire <- function(df) { df %>% mutate(row = seq_along(X1)) %>% spread(X1, X2, convert = TRUE) %>% select(-row) %>% fill(-`Investment$`) %>% filter(complete.cases(.)); }; 

## OP's input 
df <- structure(list(X1=structure(c(2L,1L,3L,4L,3L,4L,2L,1L,3L,4L,3L,4L,2L,1L,3L,4L,2L,1L,3L, 
4L,3L,4L,3L,4L),.Label=c("Check#","DocumentID","Investment","Investment$"),class="factor"), 
X2=structure(c(3L,15L,16L,5L,19L,2L,6L,14L,16L,2L,20L,10L,8L,13L,18L,7L,9L,11L,17L,1L,19L, 
4L,20L,12L),.Label=c("10","100","12345","20","200","23456","300","34567","45678","50", 
"6543","70","7654","8765","9876","Tran1","Tran2","Tran4","Tran5","Tran9"),class="factor")), 
.Names=c("X1","X2"),row.names=c(NA,-24L),class="data.frame"); 

ex <- lapply(bgoldst(df),as.character); o <- names(ex); 
identical(ex,lapply(alistaire(df)[o],as.character)); 
## [1] TRUE 

microbenchmark(bgoldst(df),alistaire(df)); 
## Unit: microseconds 
##   expr  min  lq  mean median  uq  max neval 
## bgoldst(df) 794.151 862.362 917.3149 891.4415 934.2075 1488.659 100 
## alistaire(df) 2560.782 2677.318 3376.1405 2758.5720 2861.6365 53457.399 100 

## scale test 
set.seed(1L); NR <- 2L*1e5L; ND <- 8L; probG <- 0.25; X1 <- character(NR); cns.grp <- c('DocumentID','Check#'); NG <- length(cns.grp); cns.dat <- c(LETTERS[seq_len(ND-1L)],'Investment$'); X1[seq_len(NG)] <- cns.grp; i <- NG+1L; while (i<=NR-ND+1L) { if (runif(1L)<probG) { X1[seq(i,len=NG)] <- cns.grp; i <- i+NG; } else { X1[seq(i,len=ND)] <- cns.dat; i <- i+ND; }; }; if (i<=NR) { X1[seq(i,NR)] <- cns.grp; }; df <- data.frame(X1=X1,X2=seq_len(NR)); 

ex <- lapply(bgoldst(df),as.character); o <- names(ex); 
identical(ex,lapply(alistaire(df)[o],as.character)); 
## [1] TRUE 

microbenchmark(bgoldst(df),alistaire(df)); 
## Unit: milliseconds 
##   expr  min  lq  mean median  uq  max neval 
## bgoldst(df) 34.20791 35.90591 47.60333 44.02403 46.78709 119.4467 100 
## alistaire(df) 482.73097 540.84550 568.00577 557.26885 572.44025 741.9781 100 
+0

comme toujours, réponse très agréable. J'apprécie vraiment vos explications détaillées et mes repères complets. –

+0

Merci @JosephWood, j'apprécie vraiment cela. – bgoldst