2
votes

Augmentez les performances des opérations arithmétiques avec des combinaisons de colonnes

J'ai un dataframe du type suivant-
df

 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])

Je veux que l'opération suivante soit effectuée -

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

A_B, A_C, B_C correspond à -
p >

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

ce que je fais en utilisant-

A   B   C
5   10  15
20  25  30

Mais le problème est que mon dataframe réel est de la taille de 5 * 381 forme de sorte que le nombre réel de combinaisons de A_B, A_C et ainsi de suite se présente sous la forme 5 * 72390 , ce qui prend 60 minutes à s'exécuter. J'essaie donc de le convertir en tableau numpy afin de pouvoir l'optimiser à l'aide de Numba pour le calculer efficacement ( Approche de programmation parallèle pour résoudre les problèmes de pandas ) mais je suis incapable de le convertir en tableau numpy. En outre, toute autre solution pour résoudre ce problème est également la bienvenue.


4 commentaires

Est-il important d'obtenir les bons noms de colonne pour la sortie?


@Divakar Oui, c'est important.


Avez-vous pu tester les approches affichées de votre côté?


@Divakar Oui, cela a parfaitement fonctionné, merci


3 Réponses :


0
votes

Pandas a une fonction intégrée pour ce faire: df.values

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

Et le calcul ultérieur de A_B, A_C et B_C. p >

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)


3 commentaires

Oui, mais comment effectuer A-B / A + B et des étapes similaires pour le tableau obtenu.


@Bing, vous avez demandé comment le convertir en np.ndarray . Les gens sont encouragés à résoudre leurs problèmes par eux-mêmes. Nous sommes ici pour aider et orienter les gens dans la bonne direction, pas pour faire s.o. travail.


Oui, mais le problème réel était l'optimisation, pas la conversion si vous l'aviez observé.



4
votes

Utilisation :

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)

Commencez par obtenir toutes les combinaisons de colonnes dans 2 listes ( a est pour la première valeur des tuples, b est pour la seconde):

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)

Ensuite, utilisez DataFrame.loc pour répéter les colonnes par listes:

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)

Convertir les valeurs en numpy tableaux pour DataFrame final et obtenir de nouveaux noms de colonnes par compréhension de la liste:

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

Une autre solution est très similaire, ne créez une combinaison que par arange par longueur de colonnes et les derniers nouveaux noms de colonnes sont créés par indexation:

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

Performance :

Testé sur 5 lignes et 381 colonnes:

from  itertools import combinations

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

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 commentaires

C'est du génie, c'est vraiment du génie.


@Bing - Hmmm, ça dépend. btw, est-il possible de changer le titre de la question quelque chose comme Augmenter les performances des opérations arithmétiques avec des combinaisons de noms de colonnes ?



2
votes

En voici un utilisant NumPy et sa puissante fonctionnalité de slicing -

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

Exemple d'exécution -

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

Timings sur 5 x 381 tableau aléatoire -

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)


6 commentaires

Je n'ai pas encore testé cette approche, je la mettrai à jour après l'avoir testée.


@Bing a un peu modifié mon approche pour améliorer les performances. En outre, les horaires ajoutés, espérons qu'ils seront comparables aux résultats de votre scénario de test.


Modifiez simplement la réponse pour ne pas répéter l'appel .values ​​, est-il possible d'ajouter aux horaires? Je vous remercie.


@jezrael Ne semble pas beaucoup changer les horaires, en fait semble légèrement plus lent avec les nouveaux changements. Essayez-le à votre fin?


@Divakar - Cela semble aussi dépendre des timings par nombre de lignes, testé avec 1000 lignes et un peu plus rapide (sous python 3.6, pandas 0.24.1, win7)


@Divakar - et j'oublie - a ajouté des horaires à ma réponse.