#python #pandas #loops #apply
#python #pandas #циклы #применить
Вопрос:
У меня есть функция, которая возвращает словарь. Функция работает путем вычисления значений на основе массива в фрейме данных.
Фрейм данных содержит около 1000 000 строк и выглядит следующим образом:
col1
row1 [2, 3, 44, 89.6,...]
row2 [10, 4, 33.3, 1.11,...]
row3 [3, 4, 3, 2.6, 5.9, 8, 10,...]
Моя функция принимает каждый массив в каждой строке, выполняет некоторые вычисления и возвращает словарь на основе этих вычислений. Однако это происходит очень медленно. Я ценю необходимость просеивания большого количества данных, но есть ли способ, которым я могу улучшить скорость?
Проблемы с фреймом данных большой длины. Каждый массив может содержать более 100 значений. Колеблется примерно в пределах 10-80.
Мой код выглядит следующим образом:
list1 = []
for i in df.itertuples():
list1.append(list(function(i.data).values()))
Идея здесь в том, что я перебираю каждую строку в ‘df’, применяю свою функцию к столбцу ‘data’ и добавляю результаты в список ‘list1’.
Объяснена функция
Моя функция вычисляет некоторые довольно простые вещи. Он принимает массив в качестве параметра и вычисляет данные на основе этого массива, например, его длину, среднее значение в массиве, минимальное и максимальное значение массива. Я вычисляю 8 значений и сохраняю их в словаре. Последнее, что делает моя функция, это просматривает эти вычисленные значения и добавляет окончательный ключ в словарь в виде логического значения.
Комментарии:
1. Если вы храните списки в ячейках фрейма данных, то нет, вы не можете ускорить это. Возможно, вам удастся это сделать, если вы выберете другую структуру данных
2. Возможно ли добавить свою функцию? Или добавить какой-нибудь пример функции? Возможно ли преобразовать списки в скаляры, то есть
[2, 3, 44, 89.6,...]
в столбец и аналогичные для других списков? Возможно, тогда необходимо изменить вашу функцию.3. Узким местом почти наверняка является применение
function
к (части) каждого элемента вашего контейнера; тип контейнера (фрейм данных, список и т.д.) Не имеет большого значения.4. Если функция, которую вы хотите применить к каждой строке независимо от всех других строк, то вы, вероятно, можете немного ускорить ее, используя многопроцессорную обработку. Обратите внимание, что это верно только в том случае, если ваша функция (по строкам) выполняет сложные вычисления, в противном случае это может замедлить вашу работу.
5. В зависимости от того, что делает ваша функция, также может быть интересно использовать Cython . Нам нужно больше деталей, чтобы ответить на этот вопрос должным образом.
Ответ №1:
Как я уже говорил в комментариях, если ваша функция дорогостоящая (сокращение каждой строки — это трудоемкая часть вашего кода), то первым шагом является использование multiprocessing
, потому что ее легко протестировать.
Вот что вы могли бы попробовать:
import time
from multiprocessing import Pool
def f(x):
time.sleep(10*10**-6) # Faking complex computation
return x
def seq_test(input_array):
return list(map(f, input_array))
def par_test(input_array):
pool = Pool(8) # "nproc --all" or "sysctl -n hw.ncpu" on osx
return pool.map(f, input_array)
def run_test(test_function):
test_size = 10*10**4
test_input = [i for i in range(test_size)]
t0 = time.time()
result = test_function(test_input)
t1 = time.time()
print(f"{test_function.__name__}: {t1-t0:.3f}s")
run_test(seq_test)
run_test(par_test)
На моей машине параллельная версия работает примерно в 7 раз быстрее (довольно близко к коэффициенту 8, на который мы могли надеяться):
seq_test: 2.131s
par_test: 0.300s
Если этого недостаточно, следующим шагом будет написание функции f
на другом языке, еще раз, что кажется более простым здесь, так это использовать Cython. Но для обсуждения этого нам нужно посмотреть, что находится внутри вашей функции.
Ответ №2:
Я предлагаю изменить формат ваших данных следующим образом:
print (df)
col1
row1 [2, 3, 44, 89.6]
row2 [10, 4, 33.3, 1.11]
row3 [3, 4, 3, 2.6, 5.9, 8, 10]
from itertools import chain
df = pd.DataFrame({
'idx' : df.index.repeat(df['col1'].str.len()),
'col1' : list(chain.from_iterable(df['col1'].tolist()))
})
print (df)
idx col1
0 row1 2.00
1 row1 3.00
2 row1 44.00
3 row1 89.60
4 row2 10.00
5 row2 4.00
6 row2 33.30
7 row2 1.11
8 row3 3.00
9 row3 4.00
10 row3 3.00
11 row3 2.60
12 row3 5.90
13 row3 8.00
14 row3 10.00
А затем агрегируйте ваши данные:
df1 = df.groupby('idx')['col1'].agg(['sum','mean','max','min'])
print (df1)
sum mean max min
idx
row1 138.60 34.650000 89.6 2.00
row2 48.41 12.102500 33.3 1.11
row3 36.50 5.214286 10.0 2.60