2013-04-22 2 views
1

Используя R, мне нужно собрать отчет о двух лучших сотрудниках в каждом отделе с наибольшим количеством расходов и добавить «Другие» для других сотрудников отдела. Для экземпляров мне нужен отчет, похожий на этот.Обобщение и ранжирование данных Frame

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 

Идея: первый шаг создания dt с key = depts, expenses гарантирует, что expenses сортируются в порядке возрастания. Затем, в зависимости от количества записей на dept, мы либо создаем запись «Другие», либо нет.

+0

Впечатляющий ответ Арун! Это именно то, что я искал! В чем разница между data.frame и date.table? Что представляют .N? Что, если бы я хотел, чтобы пятеро сотрудников на один отдел? Большое спасибо за ваш ответ! – Martin

+0

@Martin, я изменил ответ, установив переменную 'k', которая соответствует * топ-сотрудникам *. Вы можете установить его на 2 или 5, чтобы получить соответствующие результаты. 'data.table' - это внешний пакет, который строится поверх' data.frame', но очень * быстрый и эффективный. Вы можете начать, посмотрев «виньетки» [** здесь **] (http://cran.r-project.org/web/packages/data.table/index.html) – Arun

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

Если два значения совпадают, 'rank' с из' ties.method = «первой» 'даст среднее как ранг. Из моего примера 'df', сделайте следующее:' df $ costs [1] <- 714', а затем попробуйте код. Соответственно отредактировал. – Arun

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 
Смежные вопросы