Como agrupar colunas do quadro de dados com base em sua relação de sequência

8

Estou tentando agrupar com base no relacionamento de sequência entre as duas colunas.

d = {'df1':[10,20, 30, 60, 70, 40, 30, 70], 'df2':[20, 30, 40, 80, 70, 50, 90, 100]}

df = pd.DataFrame(data = d)
df

   df1  df2
0   10  20
1   20  30
2   30  40
3   60  80
4   80  70
5   40  50
6   30  90
7   70  100

Eu estou esperando o resultado algo abaixo:

Para deixar mais claro: - df1 e df2 têm um relacionamento com base em sua sequência. Por exemplo, 10 tem uma relação direta com 20 e 10 tem uma relação indireta com 30 a 20. E também 10 tem uma relação indireta com 40 a 20 e 30. Um exemplo adicional, vamos considerar 80, tem uma relação direta com 70 e relação indireta de 100 a 70. Isso funciona para o restante dos valores da coluna.

  df1  |    df2
  -----|-------------------
0   10 | 20, 30, 40, 50, 90
1   20 | 30, 40, 50, 90
2   30 | 40, 50, 90
3   60 | 80, 70, 100
4   80 | 70, 100
5   40 | 50
6   70 | 100

Estou tentando usar o script abaixo, mas não consegui.

(df.groupby('df1')
   .agg({ 'df2' : ','.join})
   .reset_index()
   .reindex(columns=df.columns))

Alguém poderia ajudar nesse desafio? Se houver alguma solução semelhante aqui no Stack overflow, por favor me avise.

Edit: A primeira resposta funciona perfeitamente com o exemplo acima, mas quando tento com os dados que desejo fazer, não funciona corretamente. meus dados reais se parecem abaixo.

    df1 df2
0   10  20
1   10  30
2   10  80
3   10  90
4   10  120
5   10  140
6   10  170
7   20  180
8   30  40
9   30  165
10  30  175
11  40  20
12  40  50
13  50  60
14  60  70
15  70  180
16  80  180
17  90  100
18  100 110
19  110 180
20  120 130
21  130 180
22  140 150
23  150 160
24  160 165
25  165 180
26  165 200
27  170 175
28  175 180
29  175 200
30  180 190
31  190 200
32  200 210
33  210 220
34  220 230
35  230 240
36  240 -
Kapital
fonte
1
Olá, você pode esclarecer a relação entre as colunas pelas quais deseja agrupar?
eva-vw
1
Oi Eva, Obrigado pela sua resposta. DF1 e DF2 têm um relacionamento com base em sua seqüência. Por exemplo, 10 tem uma relação direta com 20 e 10 tem uma relação indireta com 30 a 20. E também 10 tem uma relação indireta com 40 a 20 e 30. Um exemplo adicional, vamos considerar 80, tem uma relação direta com 70 e relação indireta de 100 a 70. Isso funciona para o restante dos valores da coluna.
Kapital
Por que há 90 na sequência na primeira linha? Como não há 50 na primeira coluna, a sequência deve terminar ali. Talvez eu tenha entendido algo errado.
Treskov 23/12/19
@treskov Obrigado pela resposta. Como você vê no índice número 6, 30 tem uma relação direta com 90. e sabemos que 10 tem uma relação indireta com 30 a 20. Portanto, 10 tem uma relação indireta com 90 a 30. Poderíamos dizer que isso é uma espécie de transitividade propriedade, mas é mais do que isso.
Kapital

Respostas:

3

Uma solução possível:

import pandas as pd
from itertools import chain

l1 = [10, 20, 30, 60, 80, 40, 30, 70]
l2 = [20, 30, 40, 80, 70, 50, 90, 100]

d = dict()
for i, j in zip(l1, l2):
    if i == j:
        continue
    d.setdefault(i, []).append(j)

for k in d:
    d[k].extend(chain.from_iterable(d.get(v, []) for v in d[k]))

df = pd.DataFrame({'df1': list(d.keys()), 'df2': [', '.join(str(v) for v in d[k]) for k in d]})
print(df)

Impressões:

   df1                 df2
0   10  20, 30, 40, 90, 50
1   20      30, 40, 90, 50
2   30          40, 90, 50
3   60         80, 70, 100
4   80             70, 100
5   40                  50
6   70                 100

EDIT: Outra solução baseada em novos dados de entrada. Agora estou verificando possíveis círculos no caminho:

import pandas as pd

data = '''
0   10  20
1   10  30
2   10  80
3   10  90
4   10  120
5   10  140
6   10  170
7   20  180
8   30  40
9   30  165
10  30  175
11  40  20
12  40  50
13  50  60
14  60  70
15  70  180
16  80  180
17  90  100
18  100 110
19  110 180
20  120 130
21  130 180
22  140 150
23  150 160
24  160 165
25  165 180
26  165 200
27  170 175
28  175 180
29  175 200
30  180 190
31  190 200
32  200 210
33  210 220
34  220 230
35  230 240
36  240 -
'''

df1, df2 = [], []
for line in data.splitlines()[:-1]: # <--- get rid of last `-` character
    line = line.strip().split()
    if not line:
        continue

    df1.append(int(line[1]))
    df2.append(int(line[2]))

from pprint import pprint

d = dict()
for i, j in zip(df1, df2):
    if i == j:
        continue
    d.setdefault(i, []).append(j)

for k in d:
    seen = set()
    for v in d[k]:
        for val in d.get(v, []):
            if val not in seen:
                seen.add(val)
                d[k].append(val)


df = pd.DataFrame({'df1': list(d.keys()), 'df2': [', '.join(str(v) for v in d[k]) for k in d]})
print(df)

Impressões:

    df1                                                df2
0    10  20, 30, 80, 90, 120, 140, 170, 180, 40, 165, 1...
1    20                  180, 190, 200, 210, 220, 230, 240
2    30  40, 165, 175, 20, 50, 180, 200, 190, 210, 220,...
3    40  20, 50, 180, 190, 200, 210, 220, 230, 240, 60, 70
4    50          60, 70, 180, 190, 200, 210, 220, 230, 240
5    60              70, 180, 190, 200, 210, 220, 230, 240
6    70                  180, 190, 200, 210, 220, 230, 240
7    80                  180, 190, 200, 210, 220, 230, 240
8    90        100, 110, 180, 190, 200, 210, 220, 230, 240
9   100             110, 180, 190, 200, 210, 220, 230, 240
10  110                  180, 190, 200, 210, 220, 230, 240
11  120             130, 180, 190, 200, 210, 220, 230, 240
12  130                  180, 190, 200, 210, 220, 230, 240
13  140   150, 160, 165, 180, 200, 190, 210, 220, 230, 240
14  150        160, 165, 180, 200, 190, 210, 220, 230, 240
15  160             165, 180, 200, 190, 210, 220, 230, 240
16  165             180, 200, 190, 210, 200, 220, 230, 240
17  170             175, 180, 200, 190, 210, 220, 230, 240
18  175             180, 200, 190, 210, 200, 220, 230, 240
19  180                       190, 200, 210, 220, 230, 240
20  190                            200, 210, 220, 230, 240
21  200                                 210, 220, 230, 240
22  210                                      220, 230, 240
23  220                                           230, 240
24  230                                                240

Ou pprint(d, width=250):

{10: [20, 30, 80, 90, 120, 140, 170, 180, 40, 165, 175, 100, 130, 150, 190, 20, 50, 200, 110, 160, 60, 210, 70, 220, 230, 240],
 20: [180, 190, 200, 210, 220, 230, 240],
 30: [40, 165, 175, 20, 50, 180, 200, 190, 210, 220, 230, 240, 60, 70],
 40: [20, 50, 180, 190, 200, 210, 220, 230, 240, 60, 70],
 50: [60, 70, 180, 190, 200, 210, 220, 230, 240],
 60: [70, 180, 190, 200, 210, 220, 230, 240],
 70: [180, 190, 200, 210, 220, 230, 240],
 80: [180, 190, 200, 210, 220, 230, 240],
 90: [100, 110, 180, 190, 200, 210, 220, 230, 240],
 100: [110, 180, 190, 200, 210, 220, 230, 240],
 110: [180, 190, 200, 210, 220, 230, 240],
 120: [130, 180, 190, 200, 210, 220, 230, 240],
 130: [180, 190, 200, 210, 220, 230, 240],
 140: [150, 160, 165, 180, 200, 190, 210, 220, 230, 240],
 150: [160, 165, 180, 200, 190, 210, 220, 230, 240],
 160: [165, 180, 200, 190, 210, 220, 230, 240],
 165: [180, 200, 190, 210, 200, 220, 230, 240],
 170: [175, 180, 200, 190, 210, 220, 230, 240],
 175: [180, 200, 190, 210, 200, 220, 230, 240],
 180: [190, 200, 210, 220, 230, 240],
 190: [200, 210, 220, 230, 240],
 200: [210, 220, 230, 240],
 210: [220, 230, 240],
 220: [230, 240],
 230: [240]}

EDIT 2: Se dfé o seu quadro de dados de entrada com as colunas "df1" e "df2":

from pprint import pprint

d = dict()
for i, j in zip(df.df1, df.df2):
    if i == j:
        continue
    if j == '-':   # <-- this will remove the `-` character in df2
        continue
    d.setdefault(i, []).append(j)

for k in d:
    seen = set()
    for v in d[k]:
        for val in d.get(v, []):
            if val not in seen:
                seen.add(val)
                d[k].append(val)


df = pd.DataFrame({'df1': list(d.keys()), 'df2': [', '.join(str(v) for v in d[k]) for k in d]})
print(df)
Andrej Kesely
fonte
Você poderia explicar como d[k].extend(chain.from_iterable(d.get(v, []) for v in d[k]))isso funciona? Olhei para o médico, mas não consegui segui-lo.
S238 #
@sathyz Eu uso chain.from_iterablepara achatar o iterável - nesse caso, o iterável consiste em listas do dicionário d(ou listas vazias, se a chave vnão existir em d- d.get(v, [])). Então eu uso esses valores para estender a lista armazenada em d[k].
Andrej Kesely 23/12/19
@AndrejKesely if not (line := line.strip().split()):é para dizer if not (line != line.strip().split()):? ou alguma outra coisa. Estou recebendo erro com :. Quando eu faço isso !=, estou recebendo IndexError: string index out of range erro na linha df1.append(int(line[1])).
precisa
1
@AndrejKesely Perfect. Muito obrigado senhor!!
Kapital
1
@ Kapital Você está fazendo algo errado, o resultado dos dados (conforme publicado em sua pergunta) é o mesmo da minha primeira edição. Você usa o notebook Jupyter? Se sim, recarregue / reinicie ... Não há como obter esse resultado com meu código atualizado.
Andrej Kesely 24/12/19
1

Oi obrigado pelo esclarecimento, eu tenho uma solução com uma função recursiva que você pode tentar. Pode não ser eficiente para grandes quadros de dados, mas parece funcionar bem. A função retorna uma lista, mas você pode editar a série resultante para ingressar na lista em uma sequência da maneira que desejar.

def get_related(df1, related):
    # get directly related values
    next_vals = df.loc[df['df1'] == df1, 'df2'].values.tolist()
    # remove links to self (will cause recursion issues)
    next_vals = list(set(next_vals) - set([df1]))
    # add to running list
    related = related + next_vals
    # continue to next level
    if any(next_val in df['df1'].unique() for next_val in next_vals):
        for next_val in next_vals:
            related = related + get_related(next_val, related)
    # get unique list
    return list(set(related))

df['df1'].apply(lambda x: get_related(x, []))
eva-vw
fonte
Você poderia explicar o que é "relacionado" no argumento da função?
precisa
0

Isso deve fazer o truque:

def recursive_walk(df, node):
    parents=df.loc[(df['df1']==node) & (df['df2']!=node), 'df2'].tolist()
    if(len(parents)==0):
        yield node
    else:
        for parent in parents:
            yield parent
            lst=[el for el in recursive_walk(df, parent)]
            for el in lst:
                yield el

df['tree']=df.apply(lambda x: list(set([el for el in recursive_walk(df, x['df2'])]+[x['df2']])), axis=1)

Resultado:

   df1  df2                  tree
0   10   20  [40, 50, 20, 90, 30]
1   20   30      [40, 50, 90, 30]
2   30   40              [40, 50]
3   60   80                  [80]
4   70   70             [100, 70]
5   40   50                  [50]
6   30   90                  [90]
7   70  100                 [100]

(*) Eu também verifiquei com o dataframe estendido - é muito rápido, não compartilharei a saída, pois meu IDE está truncando;)

Grzegorz Skibinski
fonte
Desculpe pela resposta tardia, mas eu estava olhando para a sua solução e não conseguia entender o que você quer dizer com node(um dos seus parâmetros na função)? Você poderia me dizer?
Kapital
nodeé o valor em que você está atualmente. Então, você o devolve e, caso tenha pais, diferente de si mesmo (chamada referência circular), você itera sobre os pais e executa a mesma função para eles.
Grzegorz Skibinski 22/01
Quando eu apenas forneço um número aleatório para o nó, estou recebendo algo parecido com isto. <generator object recursive_walk at 0x0000022A67551D48>. O que é que eu não sei o que isso significa.
Kapital
Tente: list(recursive_walk(...))ou [el for el in recursive_walk(...)]função retorna generator- o que significa essencialmente - nem todos os elementos de uma vez, como por exemplo, listou, tuplemas fornece iterável que você pode usar para retornar todos os valores um por um.
Grzegorz Skibinski 23/01