2013-04-22 115 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