Повысить производительность арифметических операций с комбинациями столбцов - PullRequest
2 голосов
/ 12 марта 2019

У меня есть кадр данных следующего типа -
df

A   B   C
5   10  15
20  25  30

Я хочу выполнить следующую операцию -

A_B   A_C  B_C
-0.33 -0.5 -0.2
-0.11 -0.2 -0.09

A_B, A_C, B_C соответствует-

A_B: A-B/A+B
A_C: A-C/A+C
B_C: B-C/B+C    

, который я делаю, используя-

 colnames = df.columns.tolist()[:-1]
 list_name=[]
 for i,c in enumerate(colnames):
     if i!=len(colnames):
        for k in range(i+1,len(colnames)):
            df[c+'_'+colnames[k]]=(df[c]- 
            df[colnames[k]])/(df[c]+df[colnames[k]])
            list_name.append(c+'_'+colnames[k])

Но проблема в том, что мой фактический фрейм данных имеет размер 5*381, поэтому фактическое количество комбинацийиз A_B, A_C and so on получаются в форме 5*72390, для запуска которой требуется 60 минут.Поэтому я пытаюсь преобразовать его в массив NumPy, чтобы я мог оптимизировать его с помощью Numba для его эффективного расчета ( Подход параллельного программирования для решения проблем панд ), но я не могу преобразовать его в NUMPY массив.Также приветствуются любые другие решения для решения этой проблемы.

Ответы [ 3 ]

4 голосов
/ 12 марта 2019

Использование:

df = pd.DataFrame({
         'A':[5,20],
         'B':[10,25],
         'C':[15,30]
})

print (df)
    A   B   C
0   5  10  15
1  20  25  30

Сначала получите все комбинации столбцов в 2 списках (a для первого значения кортежей, b для второго):

from  itertools import combinations

a, b = zip(*(combinations(df.columns, 2)))

Затем используйте DataFrame.loc для повторяющихся столбцов списками:

df1 = df.loc[:, a]
print (df1)
    A   A   B
0   5   5  10
1  20  20  25

df2 = df.loc[:, b]
print (df2)
    B   C   C
0  10  15  15
1  25  30  30

Преобразование значений в пустые массивы для окончательного DataFrame и получение новых имен столбцов по списку:

c = [f'{x}_{y}' for x, y in zip(a, b)]
arr1 = df1.values
arr2 = df2.values
df = pd.DataFrame((arr1-arr2)/(arr1+arr2), columns=c)
print (df)
        A_B  A_C       B_C
0 -0.333333 -0.5 -0.200000
1 -0.111111 -0.2 -0.090909

Другое решение очень похоже, только создать комбинацию по arange по длине столбцов, а имена последних новых столбцов создаются путем индексации:

from  itertools import combinations

a, b = zip(*(combinations(np.arange(len(df.columns)), 2)))
arr = df.values
cols = df.columns.values
arr1 = arr[:, a]
arr2 = arr[:, b]
c = [f'{x}_{y}' for x, y in zip(cols[np.array(a)], cols[np.array(b)])]
df = pd.DataFrame((arr1-arr2)/(arr1+arr2), columns=c)

Производительность

Проверено в 5 строках и 381 столбце:

np.random.seed(2019)
df = pd.DataFrame(np.random.randint(10,100,(5,381)))
df.columns = ['c'+str(i+1) for i in range(df.shape[1])]
#print (df)

In [4]: %%timeit
   ...: a, b = zip(*(combinations(np.arange(len(df.columns)), 2)))
   ...: arr = df.values
   ...: cols = df.columns.values
   ...: arr1 = arr[:, a]
   ...: arr2 = arr[:, b]
   ...: c = [f'{x}_{y}' for x, y in zip(cols[np.array(a)], cols[np.array(b)])]
   ...: pd.DataFrame((arr1-arr2)/(arr1+arr2), columns=c)
   ...: 
62 ms ± 7.29 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [5]: %%timeit
   ...: a, b = zip(*(combinations(df.columns, 2)))
   ...: df1 = df.loc[:, a]
   ...: df2 = df.loc[:, b]
   ...: arr1 = df1.values
   ...: arr2 = df2.values
   ...: c = [f'{x}_{y}' for x, y in zip(a, b)]
   ...: pd.DataFrame((arr1-arr2)/(arr1+arr2), columns=c)
   ...: 
63.2 ms ± 253 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [7]: %%timeit
   ...: func1(df)
   ...: 
89.2 ms ± 331 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [8]: %%timeit
   ...: a, b = zip(*(combinations(df.columns, 2)))
   ...: df1 = df.loc[:, a]
   ...: df2 = df.loc[:, b]
   ...: c = [f'{x}_{y}' for x, y in zip(a, b)]
   ...: pd.DataFrame((df1.values-df2.values)/(df1.values+df2.values), columns=c)
   ...: 
69.8 ms ± 6.04 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
2 голосов
/ 12 марта 2019

Вот один из них, использующий NumPy, и его мощная функциональность: slicing -

def func1(df):
    a = df.values
    n = a.shape[1]
    L = n*(n-1)//2
    idx = np.concatenate(( [0], np.arange(n-1,0,-1).cumsum() ))
    start, stop = idx[:-1], idx[1:]
    c = df.columns.values.astype(str)
    d = 2*int(''.join(x for x in str(c.dtype) if x.isdigit()))+1
    outc = np.empty(L,dtype='S'+str(2*d+1))
    out = np.empty((a.shape[0],L))
    for i,(s0,s1) in enumerate(zip(start, stop)):
        outc[s0:s1] = np.char.add(c[i]+'_',c[i+1:])
        out[:,s0:s1] = (a[:,i,None]-a[:,i+1:])/(a[:,i,None]+a[:,i+1:])
    return pd.DataFrame(out,columns=outc)

Пример выполнения -

In [361]: df
Out[361]: 
    A   B   C
0   5  10  15
1  20  25  30

In [362]: func1(df)
Out[362]: 
        A_B  A_C       B_C
0 -0.333333 -0.5 -0.200000
1 -0.111111 -0.2 -0.090909

Синхронизация 5 x 381 случайный массив -

In [147]: df = cdf(np.random.randint(10,100,(5,381)))
     ...: df.columns = ['c'+str(i+1) for i in range(df.shape[1])]

# @jezrael's soln
In [148]: %%timeit
     ...: a, b = zip(*(combinations(df.columns, 2)))
     ...: df1 = df.loc[:, a]
     ...: df2 = df.loc[:, b]
     ...: c = [x+'_'+y for x, y in zip(a, b)]
     ...: pd.DataFrame((df1.values-df2.values)/(df1.values+df2.values), columns=c)
10 loops, best of 3: 58.1 ms per loop

# From this post
In [149]: %timeit func1(df)
10 loops, best of 3: 22.6 ms per loop
0 голосов
/ 12 марта 2019

Pandas имеет встроенную функцию для этого: df.values

import pandas as pd
df = pd.DataFrame({'A': [5, 20], 'B': [10, 25], 'C': [15,30]})

print(df.head())
#     A   B   C
# 0   5  10  15
# 1  20  25  30

print(df.values)
# array([[ 5, 10, 15],
#        [20, 25, 30]], dtype=int64)

И последующие вычисления A_B, A_C и B_C.

def A_B(x):
    return (x[0]-x[1])/(x[0]+x[1])

def A_C(x):
    return (x[0]-x[2])/(x[0]+x[2])

def B_C(x):
    return (x[1]-x[2])/(x[1]+x[2])

def combine(x):
    return pd.DataFrame({'A_B': A_B(x), 'A_C': A_C(x), 'B_C': B_C(x)})

combine(df.values.T)
#         A_B  A_C       B_C
# 0 -0.333333 -0.5 -0.200000
# 1 -0.111111 -0.2 -0.090909
...