Comparação complexa com várias colunas simultaneamente
Eu tenho o seguinte conjunto de dados de amostra do pandas:
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
Quero fazer uma comparação complexa com base em todas as 4 colunas e gerar uma coluna chamada Domination_count . Para cada linha, desejo calcular quantas outras linhas aquele dado domina. Dominação é definida como "ser melhor em uma dimensão, mas não ser pior nas outras". A é melhor que B se o valor de A for menor que B.
O resultado final deve 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
Algumas explicações por trás dos números finais:
- a opção 0 é melhor do que a opção 2 e 6
- a opção 1 é melhor do que a opção 2
- opção 2, 5,6 são melhores do que nenhuma outra opção
- as opções 3 e 4 são melhores do que as opções 2, 6
Não consegui pensar em nenhum código que me permitisse comparar várias colunas simultaneamente . Encontrei essa abordagem que não faz a comparação simultaneamente.
Respostas
Melhorando a resposta:
Minha primeira resposta funcionou se não houvesse linhas iguais. No caso de fileiras iguais, eles aumentariam a contagem de dominação porque não são piores do que as outras fileiras.
Essa solução um tanto mais simples resolve esse 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
Isso levará em conta corretamente os critérios da postagem e não contará a linha duplicada na coluna de dominação
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
Minha solução anterior conta as linhas iguais:
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
Solução Original Eu gosto disso como solução. Ele pega cada linha do dataframe e compara cada elemento com todas as linhas do dataframe para ver se esse elemento é menor ou igual às outras linhas (não pior que). Em seguida, ele conta as linhas em que todos os elementos não são piores do que as outras linhas. Isso conta a linha atual, que nunca é pior do que ela mesma, então subtraímos 1.
df['Domination_count'] = df.apply(lambda x: (x <= df).all(axis=1).sum() -1, axis=1)
O resultado é:
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
Em uma linha usando compreensão de lista:
df['Domination_count'] = [(df.loc[df.index!=row] - df.loc[row].values.squeeze() > 0).all(axis = 1).sum() for row in df.index]
Subtraia cada linha de todas as linhas restantes elemento a elemento e, em seguida, conte as linhas com todos os valores positivos (o que significa que cada valor correspondente na linha que subtraímos era menor) no dataframe resultante.
Posso ter entendido sua definição de dominação errada, então talvez você precise alterar a verificação de positividade estrita para o que precisar.
Uma solução iterativa simples:
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