Comparación compleja con múltiples columnas simultáneamente
Tengo un conjunto de datos de muestra de pandas siguiente:
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
Quiero hacer una comparación compleja basada en las 4 columnas y generar una columna llamada Domination_count . Para cada fila, quiero calcular cuántas otras filas domina la dada. La dominación se define como "ser mejor en una dimensión, sin ser peor en las demás". A es mejor que B si el valor de A es menor que B.
El resultado final debería ser:
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
Alguna explicación detrás de los números finales:
- la opción 0 es mejor que la opción 2 y 6
- la opción 1 es mejor que la opción 2
- opción 2, 5,6 son mejores que ninguna otra opción
- las opciones 3 y 4 son mejores que las opciones 2, 6
No pude pensar en ningún código que me permita comparar varias columnas simultáneamente . Encontré este enfoque que no hace la comparación simultáneamente.
Respuestas
Mejorando la respuesta:
Mi primera respuesta funcionó si no había filas iguales. En el caso de filas iguales, incrementarían el recuento de dominación porque no son peores que las otras filas.
Esta solución algo más simple se encarga de ese problema.
#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
Esto considerará correctamente los criterios en la publicación y no contará la fila duplicada en la columna de dominación.
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
Mi solución anterior cuenta las filas iguales:
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
Solución original Me gusta esto como solución. Toma cada fila del marco de datos y compara cada elemento con todas las filas del marco de datos para ver si ese elemento es menor o igual que las otras filas (no peor que). Luego, cuenta las filas donde todos los elementos no son peores que las otras filas. Esto cuenta la fila actual que nunca es peor que ella misma, entonces restamos 1.
df['Domination_count'] = df.apply(lambda x: (x <= df).all(axis=1).sum() -1, axis=1)
El resultado es:
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 una línea usando la comprensión de listas:
df['Domination_count'] = [(df.loc[df.index!=row] - df.loc[row].values.squeeze() > 0).all(axis = 1).sum() for row in df.index]
Reste cada fila de todas las filas restantes por elementos, luego cuente las filas con todos los valores positivos (lo que significa que cada valor correspondiente en la fila que restamos fue menor) en el marco de datos resultante.
Es posible que haya entendido mal su definición de dominación, por lo que tal vez deba cambiar el control estricto de positividad para lo que necesite.
Una solución iterativa 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