Python Pandas ускорение сравнения столбцов со скаляром - PullRequest
0 голосов
/ 16 января 2020

У меня есть некоторые проблемы с производительностью 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 массивах?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...