Comparaison complexe avec plusieurs colonnes simultanément
J'ai un exemple de jeu de données pandas suivant:
Dim1 Dim2 Dim3 Dim4
0 1 2 7 15
1 1 10 12 2
2 9 19 18 16
3 4 2 4 15
4 8 1 9 5
5 14 18 3 14
6 19 9 9 17
Je veux faire une comparaison complexe basée sur les 4 colonnes et générer une colonne appelée Domination_count . Pour chaque ligne, je veux calculer combien d'autres lignes dominent la ligne donnée. La domination est définie comme «être meilleur dans une dimension, sans être pire dans les autres». A est meilleur que B si la valeur de A est inférieure à B.
Le résultat final devrait devenir:
Dim1 Dim2 Dim3 Dim4 Domination_count
0 1 2 7 15 2
1 1 10 12 2 1
2 9 19 18 16 0
3 4 2 4 15 2
4 8 1 9 5 2
5 14 18 3 14 0
6 19 9 9 17 0
Quelques explications derrière les chiffres définitifs:
- l'option 0 est meilleure que les options 2 et 6
- l'option 1 est meilleure que l'option 2
- les options 2, 5,6 sont meilleures qu'aucune autre option
- les options 3 et 4 sont meilleures que les options 2, 6
Je ne pouvais penser à aucun code qui me permette de comparer plusieurs colonnes simultanément . J'ai trouvé cette approche qui ne fait pas la comparaison simultanément.
Réponses
Améliorer la réponse:
Ma première réponse a fonctionné s'il n'y avait pas de lignes égales. Dans le cas de lignes égales, elles incrémenteraient le compte de domination car elles ne sont pas pires que les autres lignes.
Cette solution un peu plus simple résout ce problème.
#create a dataframe with a duplicate row
df = pd.DataFrame([[1, 2, 7, 15],[1, 10,12,2],[9, 19,18,16],[4, 2, 4, 15],[8, 1, 9, 5],[14,18,3, 14],[19,9, 9, 17], [14,18,3, 14]], #[14,18,3, 14]
columns = ['Dim1','Dim2','Dim3','Dim4']
)
df2 = df.copy()
def domination(row,df):
#filter for all rows where none of the columns are worse
df = df[(row <= df).all(axis = 1)]
#filter for rows where any column is better.
df = df[(row < df).any(axis = 1)]
return len(df)
df['Domination_count'] = df.apply(domination, args=[df], axis = 1)
df
Cela tiendra correctement compte des critères dans le message et ne comptera pas la ligne en double dans la colonne de domination
Dim1 Dim2 Dim3 Dim4 Domination_count
0 1 2 7 15 2
1 1 10 12 2 1
2 9 19 18 16 0
3 4 2 4 15 2
4 8 1 9 5 2
5 14 18 3 14 0
6 19 9 9 17 0
7 14 18 3 14 0
Ma solution précédente compte les lignes égales:
df2['Domination_count'] = df2.apply(lambda x: (x <= df2).all(axis=1).sum() -1, axis=1)
df2
Dim1 Dim2 Dim3 Dim4 Domination_count
0 1 2 7 15 2
1 1 10 12 2 1
2 9 19 18 16 0
3 4 2 4 15 2
4 8 1 9 5 2
5 14 18 3 14 1
6 19 9 9 17 0
7 14 18 3 14 1
Solution originale J'aime ceci comme solution. Il prend chaque ligne du dataframe et compare chaque élément à toutes les lignes du dataframe pour voir si cet élément est inférieur ou égal aux autres lignes (pas pire que). Ensuite, il compte les lignes où tous les éléments ne sont pas pires que les autres lignes. Cela compte la ligne actuelle qui n'est jamais pire qu'elle-même, donc nous soustrayons 1.
df['Domination_count'] = df.apply(lambda x: (x <= df).all(axis=1).sum() -1, axis=1)
Le résultat est:
Dim1 Dim2 Dim3 Dim4 Domination_count
0 1 2 7 15 2
1 1 10 12 2 1
2 9 19 18 16 0
3 4 2 4 15 2
4 8 1 9 5 2
5 14 18 3 14 0
6 19 9 9 17 0
En une ligne en utilisant la compréhension de liste:
df['Domination_count'] = [(df.loc[df.index!=row] - df.loc[row].values.squeeze() > 0).all(axis = 1).sum() for row in df.index]
Soustrayez chaque ligne de toutes les lignes restantes par élément, puis comptez les lignes avec toutes les valeurs positives (ce qui signifie que chaque valeur correspondante dans la ligne que nous avons soustraite était inférieure) dans la trame de données résultante.
J'ai peut-être mal compris votre définition de la domination, alors vous devrez peut-être changer le contrôle de positivité strict pour tout ce dont vous avez besoin.
Une solution itérative simple:
df['Domination_count']=0 #initialize column to zero
cols = df.columns[:-1] # select all columns but the domination_count
for i in range(len(df.index)): # loop through all the 4 columns
for j in range(len(df.index)):
if np.all(df.loc[i,cols]<=df.loc[j,cols]) and i!=j: # for every ith value check if its smaller than the jth value given that i!=j
df.loc[i,'Domination_count']+=1 #increment by 1