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
數據
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");
說明
cns.grp <- c('DocumentID','Check#');
輸入data.frame的哪些行應被視爲分組標記不是從輸入data.frame本身派生的;因此它們必須由程序員進行硬編碼。因此,我選派了X1
值cns.grp
。這代表分組列的列名(因爲它們將作爲輸出分組列)。
ris.dat <- which(!df$X1%in%cns.grp);
鑑於cns.grp
,我們可以通過尋找X1
指標是不等於cns.grp
任何值獲得的數據列的排索引。
cns.dat <- as.character(unique(df$X1[ris.dat]));
鑑於ris.dat
,我們可以通過跨ris.dat
行獲得的唯一X1
值獲得的數據列的列名。我添加了一個as.character()
脅迫來處理輸入data.frame具有係數列,相對於字符列的可能性。
gs <- cumsum(df$X1==cns.grp[1L])[ris.dat[c(T,rep(F,length(cns.dat)-1L))]];
爲了正確地分割輸入數據幀,我們必須派生一個分組向量。假設第一個分組列名稱表示組的開始(這是一個合理的假設,似乎是輸入數據框架的基本屬性),我們可以使用cumsum()
在每次出現第一個分組列時遞增產生一個對應於輸入數據幀的所有行的分組向量。爲了向前跳,我們將使用這個分組向量來擴展沿着唯一數據列實例從unstack()
收到的唯一分組值向量。例如,對於每個Investment
輸入行,我們將索引與其對應的DocumentID
元素。因此,我們必須針對每個數據子組中的每個組的單個實例過濾cumsum()
的結果。換句話說,對於長度爲length(cns.dat)
的每個範圍,我們都必須獲得該分組索引的一個且僅有的一個實例。這可以通過用單個真值構建該長度的邏輯向量來實現(無論哪個都是重要的,因爲所有分組元素在整個範圍內都是相同的)。我們可以用c(T,rep(F,length(cns.dat)-1L))
構建這個邏輯向量,從ris.dat
中索引出所選行索引,然後在選定行索引上過濾cumsum()
結果。我存儲在gs
中的結果。
ar <- list(check.names=F);
在這裏,我只是預先計算額外的參數給data.frame()
通話將構建輸出data.frame。指定check.names=F
對於保護非句法列名稱Check#
和Investment$
免於由標準化data.frame()
。您也可以選擇指定stringsAsFactors=F
來獲取字符列而不是默認因子列。
with(unstack(df,X2~X1),do.call(data.frame,c(lapply(mget(cns.grp),`[`,gs),mget(cns.dat),ar)));
最後,我們可以將unstack()
data.frame獲得每個分組列和數據列作爲一個獨立的列表組件,並且在使用這些with()
矢量的上下文中執行的表達式。
在這種情況下,我們只需要對data.frame()
運行一次調用即可產生所需的輸出。基本上,我們需要將分組列,通過mget()
檢索和gs
適當擴大相結合,用數據列,也可以通過mget()
檢索,以及包括預先計算的附加參數ar
生產參數列表data.frame()
將由do.call()
中繼。結果是所需的輸出。
標杆
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
一如既往,非常漂亮的答案。我非常喜歡你的詳細解釋和全面的基準。 –
謝謝@JosephWood,我真的很感激。 – bgoldst