Почему R data.table намного быстрее, чем панды?

У меня есть набор данных из 12 миллионов строк, с 3 столбцами в качестве уникальных идентификаторов и еще 2 столбца со значениями. Я пытаюсь выполнить довольно простую задачу:
- группа по трем идентификаторам. Это дает около 2,6 миллиона уникальных комбинаций
- Задача 1: вычислить медиану для столбца Val1
- Задача 2: вычислить среднее значение для столбца Val1 учитывая некоторое условие на Val2

Вот мои результаты, используя pandas и data.table (обе последние версии на данный момент на той же машине):

+-----------------+-----------------+------------+
|                 |      pandas     | data.table |
+-----------------+-----------------+------------+
| TASK 1          | 150 seconds     | 4 seconds  |
| TASK 1 + TASK 2 |  doesn't finish | 5 seconds  |
+-----------------+-----------------+------------+

Я думаю, что я могу делать что-то не так с пандами - преобразование Grp1 и Grp2 в категории не помогло, и не переключалось между .agg и .apply. Есть идеи?

Ниже приведен воспроизводимый код.
Генерация Dataframe:

import numpy as np
import pandas as pd
from collections import OrderedDict
import time

np.random.seed(123)
list1 = list(pd.util.testing.rands_array(10, 750))
list2 = list(pd.util.testing.rands_array(10, 700))
list3 = list(np.random.randint(100000,200000,5))

N = 12 * 10**6 # please make sure you have enough RAM
df = pd.DataFrame({'Grp1': np.random.choice(list1, N, replace = True),
                   'Grp2': np.random.choice(list2, N, replace = True),
                   'Grp3': np.random.choice(list3, N, replace = True),
                   'Val1': np.random.randint(0,100,N),
                   'Val2': np.random.randint(0,10,N)}) 


# this works and shows there are 2,625,000 unique combinations
df_test = df.groupby(['Grp1','Grp2','Grp3']).size()
print(df_test.shape[0]) # 2,625,000 rows

# export to feather so that same df goes into R
df.to_feather('file.feather')

Задача 1 в Python:

# TASK 1: 150 seconds (sorted / not sorted doesn't seem to matter)
df.sort_values(['Grp1','Grp2','Grp3'], inplace = True)
t0 = time.time()
df_agg1 = df.groupby(['Grp1','Grp2','Grp3']).agg({'Val1':[np.median]})
t1 = time.time()
print("Duration for complex: %s seconds ---" % (t1 - t0))

Задача 1 + Задача 2 в Python:

# TASK 1 + TASK 2: this kept running for 10 minutes to no avail
# (sorted / not sorted doesn't seem to matter)
def f(x):
    d = OrderedDict()
    d['Median_all'] = np.median(x['Val1'])
    d['Median_lt_5'] = np.median(x['Val1'][x['Val2'] < 5])
    return pd.Series(d)

t0 = time.time()
df_agg2 = df.groupby(['Grp1','Grp2','Grp3']).apply(f)
t1 = time.time()
print("Duration for complex: %s seconds ---" % (t1 - t0)) # didn't complete

Эквивалентный код R:

library(data.table)
library(feather)

DT = setDT(feater("file.feather"))
system.time({
DT_agg <- DT[,.(Median_all = median(Val1),
                Median_lt_5 = median(Val1[Val2 < 5])  ), by = c('Grp1','Grp2','Grp3')]
}) # 5 seconds

Ответ 1

Я не могу воспроизвести ваши результаты R, я исправил опечатку, где вы ошибочно написали перо, но я получаю следующее:

Error in '[.data.table'(DT, , .(Median_all = median(Val1), Median_lt_5 = median(Val1[Val2 <  : 
column or expression 1 of 'by' or 'keyby' is type NULL. Do not quote column names. Usage: DT[,sum(colC),by=list(colA,month(colB))] 

Что касается примера python, если вы хотите получить медианную для каждой группы, где Val2 меньше 5, вы должны сначала фильтровать, как в:

 df[df.Val2 < 5].groupby(['Grp1','Grp2','Grp3'])['Val2'].median()

Это заканчивается менее чем за 8 секунд на моем macbook pro.