2016-08-01 2 views
4

Функция Apply работает очень медленно с большим фреймворком данных (около 1 ~ 3 миллионов строк).Pandas - Объяснение о применении функции медленное

Я проверил вопросы, связанные здесь, как Speed up Pandas apply function и Counting within pandas apply() function, кажется, лучший способ ускорить его не использовать применить функцию :)

Для моего случая, у меня есть два вида задач, чтобы сделать с функцией apply.

Первый: применять с поиска Dict запроса

f(p_id, p_dict): 
    return p_dict[p_dict['ID'] == p_id]['value'] 

p_dict = DataFrame(...) # it's another dict works like lookup table 
df = df.apply(f, args=(p_dict,)) 

Второе: применять с GroupBy

f(week_id, min_week_num, p_dict): 
    return p_dict[(week_id - min_week_num < p_dict['WEEK']) & (p_dict['WEEK'] < week_id)].ix[:,2].mean() 

f_partial = partial(f, min_week_num=min_week_num, p_dict=p_dict) 
df = map(f, df['WEEK']) 

Я думаю, для случая кулак, то это может быть сделано с dataframe Join, в то время как я не уверен, о стоимости ресурсов для такого объединения в большом наборе данных.

Мой вопрос:

  1. Есть ли способ заменить применять в двух вышеуказанных случаях?
  2. Почему применяется так медленно? Для случая поиска dict, я думаю, что это должно быть O (N), оно не должно стоить так много, даже если N равно 1 миллиону.
+1

Какова ваша первая часть вопроса, которую вы пытаетесь сделать? Из определения 'f' у меня есть подозрение, что вы не делаете то, что хотите. Возможно, вы должны добавить небольшие примеры данных и ожидаемые результаты. –

+0

Спасибо, что для первого вопроса, например, p_dict является идентификатором страны с таблицей поиска страны, я хочу запросить имя страны с идентификатором страны как входной, если нет идентификатора страны, верните NA :) – linpingta

ответ

2

Что касается вашего первого вопроса, я не могу точно сказать, почему этот пример медленный. Но в целом, apply не использует преимущества векторизации. Кроме того, apply возвращает новый объект Series или DataFrame, поэтому с очень большим DataFrame у вас есть значительные накладные расходы на IO (я не могу гарантировать, что это так в 100% случаев, поскольку Pandas имеет множество внутренних оптимизаций реализации).

Для вашего первого метода, я предполагаю, что вы пытаетесь заполнить столбец «значение» в df, используя p_dict в качестве таблицы поиска. Речь идет о 1000x быстрее использовать pd.merge:

import string, sys 

import numpy as np 
import pandas as pd 

## 
# Part 1 - filling a column by a lookup table 
## 
def f1(col, p_dict): 
    return [p_dict[p_dict['ID'] == s]['value'].values[0] for s in col] 

# Testing 
n_size = 1000 
np.random.seed(997) 
p_dict = pd.DataFrame({'ID': [s for s in string.ascii_uppercase], 'value': np.random.randint(0,n_size, 26)}) 
df = pd.DataFrame({'p_id': [string.ascii_uppercase[i] for i in np.random.randint(0,26, n_size)]}) 

# Apply the f1 method as posted 
%timeit -n1 -r5 temp = df.apply(f1, args=(p_dict,)) 
>>> 1 loops, best of 5: 832 ms per loop 

# Using merge 
np.random.seed(997) 
df = pd.DataFrame({'p_id': [string.ascii_uppercase[i] for i in np.random.randint(0,26, n_size)]}) 
%timeit -n1 -r5 temp = pd.merge(df, p_dict, how='inner', left_on='p_id', right_on='ID', copy=False) 

>>> 1000 loops, best of 5: 826 µs per loop 

Что касается второй задачи, мы можем быстро добавить новый столбец p_dict, который вычисляет среднее значение, когда окно времени начинается в min_week_num и заканчивается в неделю для этой строки в p_dict. Для этого необходимо, чтобы p_dict сортировался по возрастанию вдоль столбца WEEK. Затем вы можете снова использовать pd.merge.

Я предполагаю, что min_week_num равен 0 в следующем примере. Но вы можете легко изменить rolling_growing_mean, чтобы получить другое значение. Метод rolling_growing_mean будет работать в O (n), поскольку он выполняет фиксированное количество операций на итерацию.

n_size = 1000 
np.random.seed(997) 
p_dict = pd.DataFrame({'WEEK': range(52), 'value': np.random.randint(0, 1000, 52)}) 
df = pd.DataFrame({'WEEK': np.random.randint(0, 52, n_size)}) 

def rolling_growing_mean(values): 
    out = np.empty(len(values)) 
    out[0] = values[0] 
    # Time window for taking mean grows each step 
    for i, v in enumerate(values[1:]): 
     out[i+1] = np.true_divide(out[i]*(i+1) + v, i+2) 
    return out 

p_dict['Means'] = rolling_growing_mean(p_dict['value']) 

df_merged = pd.merge(df, p_dict, how='inner', left_on='WEEK', right_on='WEEK') 
+0

Спасибо andrew, действительно приятный и добрый для первого вопроса :) Но я думаю, что вы можете неправильно понять мой второй вопрос, так как week_id является входным параметром, например, week_id = 5, min_week_num = 2, тогда я хочу запросить среднее значение p_dict ['WEEK'] с 3-4, в то время как week_id = 6, запрос с 4-5, так что я думаю, что это не эквивалентно его перемещению. – linpingta

+0

@linpingta да, конечно. Я думал о математике, а не о кодировании! Я удалил эту часть ответа. – andrew

+0

Так какой-нибудь способ решить это? Я знаю, может быть, мне стоит поставить вопрос на другой вопрос, но я советую получить оценку :) – linpingta

Смежные вопросы