2013-04-22 23 views
1

使用R,我需要爲每個部門的前兩名員工創建一份報告,報告費用最高,併爲該部門的其他員工添加一個「其他」。例如,我需要一個類似這樣的報告。總結和排名數據框

Dept.  EmployeeId  Expense 
Marketing  12345   100 
Marketing  12346   90 
Marketing  Others   200 
Sales   12347   50 <-- There's just one employee with expenses 
Research  12348  2000 
Research  12349   900 
Research  Others  10000 

換句話說,我需要總結一下數據,重點關注費用最高的前兩名員工。費用總額應該是公司費用總額。

employeIds <- sample(1000:9999, 20) 
depts <- sample(c('Sales', 'Marketing', 'Research'), 20, replace = TRUE) 
expenses <- sample(1:1000, 20, replace = TRUE) 

df <- data.frame(employeIds, depts, expenses) 

# Based on that data, how do I build a table with the top 2 employees with the most expenses in each department, including an "Other" employee per department. 

我是R的新手,我不確定如何解決這個問題。在SQL中,我可以使用RANK()函數和JOIN,但這不是一個選項。

回答

4

這裏有一個data.table解決方案:

創建數據:我也發病例,其中 「其他」將不會發生(該部門的條目數爲:1 < =條目< = 2)

set.seed(45) 
employeIds <- sample(1000:9999, 20) 
depts <- sample(c('Sales', 'Marketing', 'Research'), 20, replace = TRUE) 
expenses <- sample(1:1000, 20, replace = TRUE) 

df <- data.frame(employeIds, depts, expenses) 
df <- df[-c(6,10,12,18,19), ] 

data.table溶液:

require(data.table) 
dt <- data.table(df, key=c("depts", "expenses")) 
k <- 2 
dt[, if(.N > k) { 
     idx <- (seq_len(.N)-1) %/% max(k, (.N - k)) == 1 
     list(EmployeeIds = c(employeIds[idx], "Others"), 
      Expenses = c(expenses[idx], sum(expenses[!idx]))) 
    } else { 
     list(EmployeeIds = as.character(employeIds), Expenses = expenses) 
    }, by = depts] 

#  depts EmployeeIds Expenses 
# 1: Marketing  4870  567 
# 2: Marketing  3167  591 
# 3: Marketing  Others  2285 
# 4: Research  5989  878 
# 5: Research  9667  930 
# 6: Research  Others  1301 
# 7:  Sales  6700  129 
# 8:  Sales  3857  714 

想法:與key = depts, expenses創建dt的第一步驟確保expenses爲遞增次序排序。然後,根據每個dept的條目數量,我們要麼創建一個「其他」條目或不創建。

+0

令人印象深刻的答案Arun!這正是我所期待的! data.frame和date.table有什麼區別?什麼代表.N?如果我想要每個部門的前5名員工呢?非常感謝您的回答! – Martin 2013-04-22 14:54:57

+0

@Martin,我通過設置對應於* top k employees *的變量'k'來修改答案。您可以將其設置爲2或5以獲得適當的結果。 'data.table'是一個建立在'data.frame'之上的外部包,但是非常快速和高效。你可以先看看'vignettes' [** here **](http://cran.r-project.org/web/packages/data.table/index.html) – Arun 2013-04-22 16:51:11

2

未必是最優雅的,但它是一個解決方案:

func <- function(data) { 
data1 <- aggregate(data$expenses, list(employeIds=data$employeIds), sum) 
# rank without ties.method = "first" will screw things up with identical values 
data1$employeIds[!(rank(data1$x, ties.method="first") %in% 1:2)] <- 'Others' 
data1 <- aggregate(data.frame(expenses=data1$x), list(employeIds=data1$employeIds), sum) 
} 

do.call(rbind, by(df, df$depts, func)) 
+1

如果兩個值是相同的,出來'ties.method =「第一」'會給平均'rank'作爲等級。從我的示例'df',執行此操作:'df $ expenses [1] < - 714',然後嘗試您的代碼。相應地進行編輯。 – Arun 2013-04-22 14:35:30

1
df <- split(df, df$depts) 
df <- lapply(df, FUN=function(x){ 
    x <- x[order(x$expenses, decreasing=TRUE), ] 
    x$total.expenses <- sum(x$expenses) 
    x$group <- 1:nrow(x) 
    x$group <- ifelse(x$group <= 2, x$group, "Other") 
    x 
}) 
df <- do.call(rbind, df) 
2

另一個data.table方法(這可能是更接近你知道SQL風格):

dt <- data.table(employeIds, depts, expenses) 
dt[, rank:=rank(-expenses), by=depts][, 
    list("Expenses"=sum(expenses)), 
    keyby=list(depts, "Employee"=ifelse(rank<=2,employeIds,"Other")) 
] 
       depts Employee Expenses 
1: Marketing     6988      986 
2: Marketing     7011      940 
3: Marketing    Other     2614 
4:  Research     2434      763 
5:  Research     9852      731 
6:  Research    Other     3397 
7:     Sales     3120      581 
8:     Sales     6069      868