The concise data.table
solution already posted is using column numbers instead of column names. This is considered bad practice according to
Frequently Asked Questions about data.table, section 1.1:
If your colleague comes along and reads your code later they may have to hunt around to find out which column is number 5. If you or they change the column ordering higher up in your R program, you may produce wrong results with no warning or error if you forget to change all the places in your code which refer to column number 5.
So, I would like to propose alternative approaches which use column names.
Variant 1
library(data.table)
setDT(df)[, .(average.Age = mean(Age),
average.Wages = mean(Wages),
sum.Education.University = sum(Education.University),
average.Productivity = mean(Productivity)),
by = Company]
Company average.Age average.Wages sum.Education.University average.Productivity
1: A 27.00000 56666.67 2 102.6667
2: B 28.66667 68333.33 3 111.6667
3: C 29.00000 53333.33 1 101.6667
Here, every column is aggregated separately. Although it requires more of typing, it has several benefits:
- It is easy to understand what the code snippet is intended to do.
- The column names in the result can be amended as required.
- The order of columns in the result can be controlled if required.
Variant 2
If there are many columns which require the same operations, the data.table
FAQ recommends to use .SDcols
. So, we can do
m_cols <- c("Age", "Wages", "Productivity")
s_cols <- c("Education.University")
by_cols <- c("Company")
setDT(df)[, c(.SD[, lapply(.SD, mean), .SDcols = m_cols],
.SD[, lapply(.SD, sum ), .SDcols = s_cols]),
by = by_cols]
Company Age Wages Productivity Education.University
1: A 27.00000 56666.67 102.6667 2
2: B 28.66667 68333.33 111.6667 3
3: C 29.00000 53333.33 101.6667 1
This is similar to Akrun's answer but uses column names instead of column numbers. In addition, the column names are stored in a variable which is handy for programming.
Note that by_cols
may contain additional columns for aggregation, .e.g,
by_cols <- c("Company", "Name")
If column order matters, we can use setcolorder()
:
result <- setDT(df)[, c(.SD[, lapply(.SD, mean), .SDcols = m_cols],
.SD[, lapply(.SD, sum ), .SDcols = s_cols]),
by = by_cols]
setcolorder(result, intersect(names(df), names(result)))
result
Company Age Wages Education.University Productivity
1: A 27.00000 56666.67 2 102.6667
2: B 28.66667 68333.33 3 111.6667
3: C 29.00000 53333.33 1 101.6667
Likewise, the column names of the result can be amended to meet OP's requirements:
setnames(result, m_cols, paste0("average.", m_cols))
setnames(result, s_cols, paste0("sum.", s_cols))
result
Company average.Age average.Wages sum.Education.University average.Productivity
1: A 27.00000 56666.67 2 102.6667
2: B 28.66667 68333.33 3 111.6667
3: C 29.00000 53333.33 1 101.6667
Note that the data.table
functions setcolorder()
and setnames()
work in place, i.e., without copying the data.table
object. This saves memory and time which is of particular importantance when dealing with large tables.