3 votes

Je dois comparer les données de chaque ligne d'un DataFrame Pandas avec les données des autres lignes, y a-t-il un moyen d'accélérer le calcul?

Supposons que j'ai un DataFrame pandas (chargé à partir d'un fichier csv) avec cette structure (le nombre de colonnes var et err n'est pas fixe, il varie d'un fichier à l'autre) :

var_0; var_1; var_2;
32;    9;     41;
47;    22;    41;
15;    12;    32;
3;     4;     4;
10;    9;     41;
43;    21;    45;
32;    14;    32;
51;    20;    40;

Supposons que nous supprimons les colonnes err_ds_j et err_mean pour les besoins de cette question. Je dois effectuer une comparaison automatique des valeurs de chaque ligne avec les valeurs des autres lignes ; par exemple : je dois comparer la première ligne avec la deuxième, puis avec la troisième, puis avec la quatrième, et ainsi de suite, puis je dois prendre la deuxième ligne et la comparer avec la première, puis avec la troisième, et ainsi de suite pour le reste du DataFrame.

En allant plus loin dans le problème, je veux voir si pour chaque couple de lignes, toutes les valeurs "var_i" de l'une d'entre elles sont supérieures ou égales aux valeurs correspondantes de l'autre ligne. Si c'est le cas, la ligne avec les valeurs plus élevées est appelée DOMINANTE, et j'ajoute une ligne dans un autre DataFrame, avec cette structure :

SET_A; SET_B; DOMINANT_SET
0;     1;     B
...

Où les valeurs SET_A et SET_B sont des indices du DataFrame csv, et DOMINANT_SET me dit laquelle des deux est le jeu dominant (ou s'il n'y en a pas, elle est simplement assignée comme "none"). J'ai trouvé la troisième colonne utile car elle m'aide à éviter la comparaison des lignes que j'ai déjà comparées dans l'autre sens (par exemple : comparer la ligne 1 avec la ligne 0 est inutile, car j'ai déjà comparé les lignes 0 et 1 précédemment).

Ainsi, pour ce fichier csv, la sortie produite devrait être (et l'est effectivement, avec mon code) :

   SET_A SET_B DOMINANT_SET
1      0     1            B
2      0     2         none
3      0     3            A
4      0     4            A
5      0     5            B
6      0     6         none
7      0     7         none
8      1     2            A
9      1     3            A
10     1     4            A
11     1     5         none
12     1     6            A
13     1     7         none
14     2     3            A
15     2     4         none
16     2     5            B
17     2     6            B
18     2     7            B
19     3     4            B
20     3     5            B
21     3     6            B
22     3     7            B
23     4     5            B
24     4     6         none
25     4     7         none
26     5     6            A
27     5     7         none
28     6     7            B

J'ai déjà écrit tout le code pour ce problème particulier, et cela fonctionne très bien avec certains jeux de données de test (100 lignes échantillonnées à partir d'un jeu de données réel).

Voici un extrait du code pertinent :

import numpy as np
import pandas as pd

def couple_deja_teste(index1, index2, dataframe):
    return (((dataframe['SET_A'] == index1) & (dataframe['SET_B'] == index2)).any()) | (((dataframe['SET_A'] == index2) & (dataframe['SET_B'] == index1)).any())

def verifier_dominance(ensemble_a, ensemble_b, index_i, index_j, dataframe):
    longueur = dataframe.shape[0]
    if np.all(ensemble_a >= ensemble_b):
        print("CONFIGURATION DOMINANTE A > B TROUVÉE")
        dataframe.loc[longueur+1] = [index_i,index_j,'A']
    elif np.all(ensemble_b >= ensemble_a):
        print("CONFIGURATION DOMINANTE B > A TROUVÉE")
        dataframe.loc[longueur+1] = [index_i,index_j,'B']
    else:
        dataframe.loc[longueur+1] = [index_i,index_j,'none']

df = pd.read_csv('test.csv', sep=';')
dom_table_df = pd.DataFrame(columns=['SET_A','SET_B','DOMINANT_SET'])
df_longueur = df.shape[0]
var_num = df.shape[1]-1 

a = None
b = None

for i in range(0, df_longueur):
    a = df.iloc[i, 0:var_num].values
    for j in range(0, df_longueur):
        if j == i:
            continue
        b = df.iloc[j, 0:var_num].values
        if couple_deja_teste(i,j,dom_table_df):
            print("ATTENTION : configuration", i, j, "déjà comparée, sautant")
        else:
            print("Comparaison de la configuration à la ligne", i, "avec la configuration à la ligne", j)
            verifier_dominance(a, b, i, j, dom_table_df)

print(dom_table_df)

Le problème est que, n'étant pas très compétent en python et en pandas (je les apprends depuis environ un mois et demi), ce code est bien sûr terriblement lent (pour des jeux de données avec, disons, de 1000 à 10000 lignes) car j'utilise des itérations dans mon algorithme. Je sais que je peux utiliser quelque chose appelé vectorisation, mais en lisant à ce sujet, je ne suis pas complètement sûr que ce soit bon pour mon cas d'utilisation.

Alors, comment pourrais-je accélérer les calculs ?

1voto

Armali Points 1170

Il ne s'agit pas d'un changement majeur de l'algorithme, mais vous pouvez économiser plus de la moitié des cycles de boucle ainsi que les tests pour j == i et couple_already_tested si vous choisissez correctement la plage pour j, ainsi les boucles principales deviennent :

for i in range(0, df_length):
    a = df.iloc[i, 0:var_num].values
    for j in range(i+1, df_length): # nous pouvons sauter la plage de 0 à i
        b = df.iloc[j, 0:var_num].values
        #print("Comparing configuration at row", i, "with configuration at row", j)
        check_dominance(a, b, i, j, dom_table_df)

1voto

Armali Points 1170

Une autre accélération (surprenante) significative peut être obtenue en préallouant le DataFrame de sortie plutôt qu'en ajoutant une ligne après l'autre. Nous pouvons calculer le nombre résultant de lignes comme

  • (_df_length_2 − df_length) ÷ 2

Pour déterminer le numéro de ligne où insérer le jeu de données de sortie actuel, nous pouvons désormais maintenir un compteur au lieu de dataframe.shape[0]. Cela donne :

dom_table_df = pd.DataFrame(index=np.arange(1, 1+(df_length**2-df_length)/2).astype('i'),
                            columns=['SET_A', 'SET_B', 'DOMINANT_SET'])
length = 0  # compteur des lignes déjà remplies
for i in range(0, df_length):
    a = df.iloc[i, 0:var_num].values
    for j in range(i+1, df_length): # nous pouvons ignorer la plage de 0 à i
        b = df.iloc[j, 0:var_num].values
        #print("Comparaison de la configuration à la ligne", i, "avec la configuration à la ligne", j)
        length += 1
        if np.all(a >= b):
            #print("CONFIGURATION DOMINANTE TROUVÉE A > B")
            dom_table_df.loc[length] = [i, j, 'A']
        elif np.all(b >= a):
            #print("CONFIGURATION DOMINANTE TROUVÉE B > A")
            dom_table_df.loc[length] = [i, j, 'B']
        else:
            dom_table_df.loc[length] = [i, j, 'aucun']

1voto

Armali Points 1170

Une autre accélération peut être réalisée en remplaçant .iloc[].values ainsi que .loc[] par .values[], mais avec .loc[] nous devons ajuster le sous-script, car .values prend un sous-script à base zéro, qui est différent de notre index à base 1 dom_table_df.index.

dom_table_df = pd.DataFrame(index=np.arange(1, 1+(df_length**2-df_length)/2).astype('i'),
                            columns=['SET_A', 'SET_B', 'DOMINANT_SET'])
length = 0  # compteur des lignes déjà remplies
for i in range(0, df_length):
    a = df.values[i, 0:var_num]
    for j in range(i+1, df_length): # nous pouvons sauter la plage de 0 à i
        b = df.values[j, 0:var_num]
        #print("Comparing configuration at row", i, "with configuration at row", j)
        if np.all(a >= b):
            #print("FOUND DOMINANT CONFIGURATION A > B")
            dom_table_df.values[length] = [i, j, 'A']
        elif np.all(b >= a):
            #print("FOUND DOMINANT CONFIGURATION B > A")
            dom_table_df.values[length] = [i, j, 'B']
        else:
            dom_table_df.values[length] = [i, j, 'none']
        length += 1

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X