У меня есть некоторые проблемы с производительностью Pandas Операции с кадрами.
Что мне нужно сделать, это групповые агрегации и формулы в строках, которые соответствуют каждой строке, которая оценивается. Так что в основном мне нужно l oop поверх Dataframe
Я думал о добавлении логических столбцов в Dataframe, где значение строки совпадает с переменной.
Я уже приводил строки к целым, поскольку экономит память и, вероятно, ускоряет процесс.
Мой код такой:
columns = ['a','b','c']
for i in columns:
labels,unique = pd.factorize(df1[i])
df1[i] = labels
df1[i+"_match"] = np.zeros(len(df1.index),bool)
for i,row in df1.iterrows():
for col in columns:
df1[col + '_match'] = df1[col] == row[col]
result = some_groupby_stuff(i,row,df1)
Я рассчитал время, и узкое место находится в:
df1[col + '_match'] = df1[col] == row[col]
и когда не выпадает это к кадру данных, но только к переменной, время уменьшается вдвое:
d = df1[col] == row[col] # time halves
Как я могу сделать это лучше / быстрее? (на кадре данных 10K на 7 это уже занимает 12 секунд.)
Я не вижу способа устранить l oop во всех строках кадра данных, однако я подумал, что может быть другое решение создать трехмерную матрицу (n_rows, n_rows, n_cols), где на месте i, j, z равен 1, если в строке df1 i и строке j оба значения истинны для столбца z. Но я не знаю, есть ли у Pandas такая опция (реализована в C, иначе я не думаю, что это будет быстрее), и я не уверен, что эта (разреженная) матрица займет много памяти. Cython мог бы быть вариантом, но для легкой корректировки кода я бы предпочел остаться с Pandas / Numpy.
EDIT:
Хорошо, я немного изменил код. Вместо создания циклов при создании столбцов проще / быстрее просто добавить все фиктивные столбцы. Это общий код: генерация данных:
import pandas as pd
import numpy as np
import operator, math,string,random
#creating random data
#num of rows of data to create
n=500
t = np.array([round(math.exp((np.random.random_sample()*5)-1)*1.5,3) for i in np.arange(n)])
t[t<1] = 0
s = np.array([round(math.exp((np.random.random_sample()*2)-1),3) for i in np.arange(n)])
s[s<0.8] = 0
part = min(int((n**.25))*2,26)
a = np.array([''.join(random.choices(string.ascii_uppercase[:part] + string.ascii_uppercase[:part],k=2)) for i in np.arange(n)])
a[list(np.random.choice(n,int(n/15)))] = ''
b = np.array([''.join(random.choices(string.ascii_uppercase[:int(part/2)] + string.ascii_uppercase[:int(part/2)],k=2)) for i in np.arange(n)])
b[list(np.random.choice(n,int(n/10)))] = ''
c = np.array([''.join(random.choices(string.ascii_uppercase[:int(part/3)] + string.ascii_uppercase[:int(part/3)],k=2)) for i in np.arange(n)])
c[list(np.random.choice(n,int(n/8)))] = ''
d = np.array([''.join(random.choices(string.ascii_uppercase[:int(part/4)] + string.ascii_uppercase[:int(part/4)],k=2)) for i in np.arange(n)])
d[list(np.random.choice(n,int(n/5)))] = ''
df = pd.DataFrame(np.array([t,s,a,b,c,d]).transpose(), columns=['T','S','A','B','C','D'])
df['T'] = pd.to_numeric(df['T'])
df['S'] = pd.to_numeric(df['S'])
#minimum T
threshold=100
#order of importance of columns
w = {'A':1,'B':2,'C':3,'D':4}
for i in w.keys():
temp = pd.get_dummies(df[i], dummy_na=False, prefix=i,drop_first=False,sparse=False)
df = df.join(temp)
df['sum_avg'] = np.zeros(len(df.index))
#time till here is negligible
Фактическая функция для ускорения:
def function_to_speed_up(df2,thresholdCondition,w):
"""
loop over rows, and keep adding data from other groups of rows
which are less similar until the threshold is reached.
"""
for i,row in df2.iterrows():
stats = row[['T','S']]
w_temp = w.copy()
while (stats['T'] < threshold) & any(w_temp):
#because prefix at dummy creation make list of current columns to use
cols = [str(x) + '_' + str(y) for x, y in dict(row[w_temp.keys()]).items()]
# if all columns in this selection are 1/True the row belongs to the current "row/group"
temp = df2.loc[df2.loc[:, cols].prod(axis=1) > 0, ['T', 'S']].sum()
#add row values to total
stats['T'] = stats['T'] + temp['T'] - row['T']
stats['S'] = stats['S'] + temp['S'] - row['S']
# make group broader if threshold not reached yet.
del w_temp[min(w_temp.items(), key=operator.itemgetter(1))[0]]
df2['sum_avg'].iloc[i] = stats['S']/stats['T']
return df2
запуск кода с помощью prun:
%prun awnser = function_to_speed_up(df,threshold,w)
возвращает:
9665853 function calls (9526106 primitive calls) in 39.552 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
500 31.663 0.063 31.663 0.063 {built-in method gc.collect}
1542640 0.430 0.000 0.877 0.000 {built-in method builtins.isinstance}
983182 0.316 0.000 0.352 0.000 {built-in method builtins.getattr}
112630/110028 0.213 0.000 0.285 0.000 {built-in method numpy.array}
19914 0.200 0.000 0.586 0.000 {pandas._libs.lib.infer_dtype}
624710 0.181 0.000 0.389 0.000 generic.py:7(_check)
21819 0.155 0.000 0.155 0.000 {method 'reduce' of 'numpy.ufunc' objects}
10107 0.150 0.000 1.008 0.000 base.py:253(__new__)
Таким образом, мы видим, что почти все время тратится на сборку мусора. Хотя я обнаружил, что с этим была проблема в Python 2.6, я не думаю, что сейчас это проблема, а просто нужно много ее очистить из-за петель.
Я не для многих экспертов по Python типам данных, но я подумал, что, возможно, будет возможно:
создать диктовку с набором только «истинных» индексов каждого фиктивного столбца, а затем вместо того, чтобы иметь чтобы проверить, какие значения> 0, а затем умножить столбцы, я могу просто взять пересечение множеств и суммировать эти индексы. Но это все еще требует iterrows () и while (). Кроме того, лучше ли это делать на массиве данных или серии или numpy массивах?