# Multi Way Summary

**URL:** <https://discourse.julialang.org/t/multi-way-summary/41269>\
**Category:** General Usage\
**Tags:** statistics, dataframes\
**Created:** [June 12, 2020, 2:00pm UTC](https://discourse.julialang.org/t/multi-way-summary/41269 "2020-06-12T14:00:44Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![bernhard](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bernhard/32/2619_2.png) [@bernhard](https://discourse.julialang.org/u/bernhard)\
**Post date:** [June 12, 2020, 2:00pm UTC](https://discourse.julialang.org/t/multi-way-summary/41269/1 "2020-06-12T14:00:45Z")

</div>

All I am looking for a simple way to perform a summary on multiple levels

Attached is a screenshot from SAS, which shows what I am trying to achieve (consider the first row as well as the subsequent 26 rows).  
Is there any package in Julia that provides this?  
By default `combine` only aggregates on the most granular level, see example.

```julia
lnk="https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/639388c2cbc2120a14dcf466e85730eb8be498bb/iris.csv"
fi=download(lnk)

using DataFrames
using CSV

df=CSV.read(fi)
agg=combine(groupby(df,[:species,:petal_width]),:sepal_length=>sum)

```

 ![sas](https://global.discourse-cdn.com/julialang/original/3X/f/0/f0137f1a87c9398c375b1a7cdfbd3105b8542e6a.jpeg)

---

<div class="post-metadata">

**Author:** ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)\
**Post date:** [June 12, 2020, 2:54pm UTC](https://discourse.julialang.org/t/multi-way-summary/41269/2 "2020-06-12T14:54:31Z")

</div>

Just to make sure I understand you correctly, your aim is to have

```julia
combine(groupby(df,[:species]),:sepal_length=>sum)
combine(groupby(df,[:petal_width]),:sepal_length=>sum)
combine(groupby(df,[:species,:petal_width]),:sepal_length=>sum)

```

all in one call, and output into one table? If so I’m unaware of a package that would provide this, but it should be relatively easy to roll your own?

---

<div class="post-metadata">

**Author:** ![bernhard](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bernhard/32/2619_2.png) [@bernhard](https://discourse.julialang.org/u/bernhard)\
**Post date:** [June 12, 2020, 2:58pm UTC](https://discourse.julialang.org/t/multi-way-summary/41269/3 "2020-06-12T14:58:21Z")

</div>

Yes, this is what I want.  
Indeed it is probably not too difficult, but I don’t want to reinvent the wheel.  
I hope it is clear, that I plan to call this function with an arbitrary number of arguments which could be of different types.  
I think I even may have a snippet somewhere that does this but I was hoping “proper code” exists for this somewhere online. (it has happened to me that I coded something that actually existed in some function I did not know…)

---

<div class="post-metadata">

**Author:** ![bernhard](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bernhard/32/2619_2.png) [@bernhard](https://discourse.julialang.org/u/bernhard)\
**Post date:** [June 12, 2020, 4:08pm UTC](https://discourse.julialang.org/t/multi-way-summary/41269/4 "2020-06-12T16:08:15Z")

</div>

As per your suggestion I gave this a try.  
I think this suffices for me (for now), although it certainly has potential for improvements.

```julia

using DataFrames
using CSV
using IterTools

function multiwayaggregation(df::DataFrame,v::Vector{Symbol},cs::Union{Pair, typeof(nrow), DataFrames.ColumnIndex, DataFrames.MultiColumnIndex}...)
    res=DataFrame()

    for c in v 
        @assert !(any(ismissing,df[!,c])) #otherwise the appending will not be meaningful, as we set the values to missing for columns which are not considered in the multi way summary
    end
    
    for subsetlength=length(v):-1:0
        for subs in IterTools.subsets(v,subsetlength)
            #@show subs
            if subsetlength==0 
                agg = DataFrames.combine(df,cs...)
            else 
                agg = DataFrames.combine(DataFrames.groupby(df,subs),cs...)
            end
            nonAggregatedVars=setdiff(v,subs)
            
            k=1
            DataFrames.insertcols!(agg,k,:_TYPE_ => repeat(vcat(subsetlength),size(agg,1)))
            k+=1
            for addcol in nonAggregatedVars 
                DataFrames.insertcols!(agg,k,addcol => repeat(vcat(missing),size(agg,1)))
                k+=1
            end 
            
            DataFrames.allowmissing!(agg)
            DataFrames.append!(res,agg)            
        end
    end
    
    sort!(res,vcat(:_TYPE_,v))
    return res 
end

lnk="https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/639388c2cbc2120a14dcf466e85730eb8be498bb/iris.csv"
fi=download(lnk)

df=CSV.read(fi)

v=[:species,:petal_width]
rs=multiwayaggregation(df,v,:sepal_length=>sum)

```
