# Group-by performance benchmarks and recommendations

**URL:** https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313
**Category:** Data
**Created:** [February 25, 2018, 11:23am UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313 "2018-02-25T11:23:26Z")
**Posts on this page:** 13
**Page:** 1

<div class="post-metadata">

### Author: ![xiaodai](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/xiaodai/32/15937_2.png) [@xiaodai](https://discourse.julialang.org/u/xiaodai)
#### Post date: [February 25, 2018, 11:23am UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/1 "2018-02-25T11:23:26Z")

</div>

I have been trying to improve Julia’s DataFrame group-by for a while now and I think I am able to synthesized my thinking into APIs.

Here are my **synthesized recommendation (as of 25th Feb 2018)**

| Recommendation | Why? | Benchmark Status |
| --- | --- | --- |
| If you need to perform **group-by A LOT** use **JuliaDB.jl/IndexedTables.jl** | You can create indexes on the group-by columns and it is generally faster than other available methods | No benchmarks; as the benchmarks was intended to test non-indexing performance |
| If the # of unique groups is small use **DataFramesMeta.jl** ’s `@by` | DataFramesMeta.jl uses a hashtable (Dict) in the background and it works pretty well for types with a small number of unique values (due to efficient CPU cache usage I assume) | Benchmarked. it beats FastGroupBy.jl in one case |
| **FastGroupBy.jl** ’s fast algorithm will eventually but contributed back into DataFrames.jl and JuliaDB.jl. Use **DataFramesMeta.jl** instead and the group-by will gain a speed boost when these algorithms get there in a PR | **FastGroupBy.jl** tends to have the fastest group-by algorithms. Use **CategoricalArrays.jl** whenever possible as the group-by vectors. FastGroupBy.jl implemented [an optimization that is not yet in data.table](https://github.com/Rdatatable/data.table/issues/2458) | Benchmarked |
| if nice API is more important than speed then use **DataFramesMeta.jl** | It’s got the cleanest API and easy to remember verbs | Benchmarked |
| Do **NOT** use **Query.jl** if performance matters | You can see in my benchmarking code that I have benchmarked Query.jl but it’s way too slow for even 10m records. This issue has been reported before, I am not sure the design of Query.jl can support performance-oriented group-bys | Benchmarked; not plotted |

**Benchmarks**  
Group-by in Julia using strings as the group-by variable tends to result in much slower timings than R’s data.table. This can’t be avoided as R has string interning. So I have provided benchmarks for when the group-by is a string (data.table is faster) vs when the group-by is a `categorical` (FastGroupBy.jl 10x faster) for one particular case to illustrate. When the group-by is a reduce operation e.g. sum but not mean, then using `fgroupreduce` will yield faster results. Again **do not** try to use FastGroupBy.jl just yet, as it’s immature and only have algorithms for the five types of group-by’s benchmarked below. Also R’s data.table is faster than any Julia implementation unless the group-by variables is a `cateogrical`; but FastGroupBy.jl beats the best known Julia solutions (without indexing) by a wide margin most of the time, so its algorithms which are radix-sort based should be contributed back to DataFrames.jl and JuliaDB.jl

 ![groupby1a](https://global.discourse-cdn.com/julialang/original/3X/c/5/c5cbeae1d91c16d6c2cf000964a3bde6c72f7e22.png)  
 ![groupby1b](https://global.discourse-cdn.com/julialang/original/3X/3/4/34563d40d558dd968ea1a269b9165337b8057019.png)  
 ![groupby2](https://global.discourse-cdn.com/julialang/original/3X/e/b/ebaa24c6be5a4d6709077477ff24a5420d791790.png)  
 ![groupby3](https://global.discourse-cdn.com/julialang/original/3X/0/d/0d0e473458d55352c8d2d803c6be0867c4635eed.png)  
 ![groupby4](https://global.discourse-cdn.com/julialang/original/3X/1/9/197443feebda1eb5b80ebad97211c2f01bcfcf5b.png)  
 ![groupby5](https://global.discourse-cdn.com/julialang/original/3X/f/6/f6bc0c982f6b4951f2983c9290ea663c5a585952.png)

**Benchmarking code**

> <https://github.com/xiaodaigh/DataBench.jl/blob/master/benchmark/benchmark_groupby_vs_r_2_benchmark.jl>

---

<div class="post-metadata">

### Author: ![nalimilan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nalimilan/32/147_2.png) [@nalimilan](https://discourse.julialang.org/u/nalimilan)
#### Post date: [February 25, 2018, 12:00pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/2 "2018-02-25T12:00:31Z")

</div>

DataFramesMeta uses the plain DataFrames `by` method, right? Then its slowness is due in large part to the type instability of the reduction, since the type of the columns isn’t encoded in the object type. I’m afraid the performance of the algorithm won’t be the main determinant as long as we haven’t fixed that. See [this issue](https://github.com/JuliaData/DataFrames.jl/issues/1256).

---

<div class="post-metadata">

### Author: ![xiaodai](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/xiaodai/32/15937_2.png) [@xiaodai](https://discourse.julialang.org/u/xiaodai)
#### Post date: [February 25, 2018, 12:12pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/3 "2018-02-25T12:12:36Z")

</div>

> [@nalimilan](#):
>
> Then its slowness is due in large part to the type instability of the reduction, since the type of the columns isn’t encoded in the object type.

I think you can address the above by updating the DataFramesMeta.jl’s macro `@by` to make it do a “translation” step. The trick is not to work with DataFrames but have macros that “break it down” so that the functions work on vectors or tuples of vectors; so NamedTuples would be one implementation.

You can write the functions so that they are called by passing columns into them. We have macros like DataFramesMeta that works like this

```julia
@> df begin
  @by(:bygroup, sum(:v1)
end

```

you see how that can be transformed into something like `groupreduce(+, df[:bygroup], df[:v1])` and if you make that a “function boundary” then optimized type-stable code will be called at that point. You can see some of it in action in my benchmarking code.

I can probably post an example of this in FastGroupBy.jl next weekend.

I have an idea about how to design the API and have the implementation use some weighted graphs and apply Dykstra’s algorithm to find optimal ways to perform these types of operation. There is a good chance I will blog about it in the next 6 months or so.

---

<div class="post-metadata">

### Author: ![nalimilan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nalimilan/32/147_2.png) [@nalimilan](https://discourse.julialang.org/u/nalimilan)
#### Post date: [February 25, 2018, 8:55pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/4 "2018-02-25T20:55:18Z")

</div>

Yes, that approach would work. Though I’m not sure it’s easy to implement in full generality, e.g. if the function returns a data frame for each group (which `by` currently supports). Sometimes it’s also useful to have access to all columns in the data frame, e.g. if you want to standardize them within each group. So it would be useful to also make `by` more efficient with plain DataFrames.

---

<div class="post-metadata">

### Author: ![xiaodai](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/xiaodai/32/15937_2.png) [@xiaodai](https://discourse.julialang.org/u/xiaodai)
#### Post date: [February 25, 2018, 9:00pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/5 "2018-02-25T21:00:33Z")

</div>

Yeah. I think some functions will have to work with Dataframe but again all functions should woke with vectors and have function boundaries so everything is optimised whenever possible. In R’s data.table they basically recognise that you what `mean(v1)` and provide a fast path for that.

Anyway, that how DataFramesMeta should work as well.

Functions that deal with DataFramesMeta e.g size should work fine. But the moment you work with as vector it should be called via a function so the function boundary effect can take place

---

<div class="post-metadata">

### Author: ![Juan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juan/32/7657_2.png) [@Juan](https://discourse.julialang.org/u/Juan)
#### Post date: [February 25, 2018, 11:45pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/6 "2018-02-25T23:45:31Z")

</div>

Where are the results for JuliaDB.jl/IndexedTables.jl?

---

<div class="post-metadata">

### Author: ![xiaodai](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/xiaodai/32/15937_2.png) [@xiaodai](https://discourse.julialang.org/u/xiaodai)
#### Post date: [February 25, 2018, 11:54pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/7 "2018-02-25T23:54:57Z")

</div>

didn’t put them up because they seem slow without indexing. Might add later

---

<div class="post-metadata">

### Author: ![bramtayl](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bramtayl/32/3614_2.png) [@bramtayl](https://discourse.julialang.org/u/bramtayl)
#### Post date: [February 16, 2019, 8:44pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/8 "2019-02-16T20:44:27Z")

</div>

Curious about the results with LightQuery?

---

<div class="post-metadata">

### Author: ![xiaodai](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/xiaodai/32/15937_2.png) [@xiaodai](https://discourse.julialang.org/u/xiaodai)
#### Post date: [February 16, 2019, 10:52pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/9 "2019-02-16T22:52:33Z")

</div>

Yeah. Would they be drastically different?

---

<div class="post-metadata">

### Author: ![bramtayl](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bramtayl/32/3614_2.png) [@bramtayl](https://discourse.julialang.org/u/bramtayl)
#### Post date: [February 17, 2019, 1:25am UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/10 "2019-02-17T01:25:18Z")

</div>

Maybe? If the data is presorted I think LightQuery will almost definitely be pretty fast. I just tagged a new version (0.1.6).

---

<div class="post-metadata">

### Author: ![datnamer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/datnamer/32/3471_2.png) [@datnamer](https://discourse.julialang.org/u/datnamer)
#### Post date: [February 17, 2019, 1:34am UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/11 "2019-02-17T01:34:47Z")

</div>

also query.jl

---

<div class="post-metadata">

### Author: ![Juan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juan/32/7657_2.png) [@Juan](https://discourse.julialang.org/u/Juan)
#### Post date: [September 2, 2019, 5:05pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/12 "2019-09-02T17:05:36Z")

</div>

Any update?

---

<div class="post-metadata">

### Author: ![xiaodai](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/xiaodai/32/15937_2.png) [@xiaodai](https://discourse.julialang.org/u/xiaodai)
#### Post date: [September 2, 2019, 9:58pm UTC](https://discourse.julialang.org/t/group-by-performance-benchmarks-and-recommendations/9313/13 "2019-09-02T21:58:48Z")

</div>

Might update this at some point. Currently I only spend Sunday mornings on OSS’s work, so progress will be slow. But I think DataFrames with DataFramesMeta is the way to go. Other table libraries have lost steam
