¿Cómo agrupo las columnas del marco de datos en función de su relación de secuencia

8

Estoy tratando de agrupar según su relación de secuencia entre las dos columnas.

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

Espero el resultado algo a continuación:

Para que quede más claro: - df1 y df2 tienen una relación basada en su secuencia. Por ejemplo, 10 tiene una relación directa con 20 y 10 tiene una relación indirecta con 30 a 20. Y también 10 tiene una relación indirecta con 40 a 20 y 30. Un ejemplo adicional, tomemos 80 tiene una relación directa con 70 y relación indirecta con 100 a 70. Esto funciona para el resto de los valores de columna.

  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

Estoy tratando de usar el script a continuación, pero no pude tener éxito.

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

¿Alguien podría ayudar en este desafío? Si hay alguna solución similar aquí en el desbordamiento de Stack, hágamelo saber.

Editar: la primera respuesta funciona perfectamente con el ejemplo anterior, pero cuando trato con los datos que quiero hacer, no funciona correctamente. mis datos reales se ven a continuación.

    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
fuente
1
Hola, ¿podría aclarar la relación entre las columnas por las que desea agrupar?
eva-vw
1
Hola Eva, gracias por tu respuesta. df1 y df2 tienen una relación basada en su secuencia. Por ejemplo, 10 tiene una relación directa con 20 y 10 tiene una relación indirecta con 30 a 20. Y también 10 tiene una relación indirecta con 40 a 20 y 30. Un ejemplo adicional, tomemos 80 tiene una relación directa con 70 y relación indirecta con 100 a 70. Esto funciona para el resto de los valores de columna.
Kapital el
¿Por qué hay 90 en la secuencia en la primera fila? No hay 50 en la primera columna, por lo que la secuencia debe terminar allí. Tal vez no entendí algo.
treskov
@treskov Gracias por la respuesta. Como puede ver en el índice número 6, 30 tiene una relación directa con 90. y sabemos que 10 tiene una relación indirecta con 30 a 20. De modo que 10 tiene una relación indirecta con 90 a 30. Podríamos decir que esto es una especie de transición propiedad pero es más que eso.
Kapital

Respuestas:

3

Una posible solución:

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)

Huellas dactilares:

   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

EDITAR: Otra solución basada en nuevos datos de entrada. Ahora estoy buscando posibles círculos en la ruta:

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)

Huellas dactilares:

    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

O 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]}

EDITAR 2: Si dfes su marco de datos de entrada con columnas "df1" y "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
fuente
¿Podría explicar cómo d[k].extend(chain.from_iterable(d.get(v, []) for v in d[k]))funciona esto? Miré el documento, pero no pude seguirlo.
sathyz
@sathyz Yo uso chain.from_iterablepara aplanar el iterable: en este caso, el iterable consiste en listas del diccionario d(o listas vacías, si la clave vno existe en d- d.get(v, [])). Luego uso estos valores para extender la lista almacenada en d[k].
Andrej Kesely
@AndrejKesely if not (line := line.strip().split()):es decir if not (line != line.strip().split()):? o algo mas. Estoy recibiendo un error con :. Cuando lo hago, !=recibo un IndexError: string index out of range error en la línea df1.append(int(line[1])).
Kapital
1
@AndrejKesely Perfecto. ¡¡Muchas gracias señor!!
Kapital
1
@Kapital Estás haciendo algo mal, el resultado de los datos (según lo publicado en tu pregunta) es el mismo que en mi primer EDIT. ¿Utilizas el cuaderno Jupyter? En caso afirmativo, vuelva a cargarlo / reinícielo ... No hay forma de obtener este resultado con mi código actualizado.
Andrej Kesely
1

Hola, gracias por la aclaración, tengo una solución con una función recursiva que puedes probar. Puede no ser eficiente para grandes marcos de datos, pero parece funcionar bien. La función devuelve una lista, pero puede editar la serie resultante para unir la lista en una cadena como desee.

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
fuente
¿Podría explicar qué es "relacionado" del argumento de la función?
Kapital
0

Esto debería funcionar:

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)

Salida:

   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]

(*) También verifiqué con el marco de datos extendido: es bastante rápido, no compartiré el resultado, ya que mi IDE lo está truncando;)

Grzegorz Skibinski
fuente
Perdón por la respuesta tardía, pero estaba buscando su solución y no podía entender qué quiere decir con node(uno de sus parámetros en la función). ¿Podría decirme?
Kapital
nodees el valor en el que se encuentra actualmente. Por lo tanto, lo devuelve y, en caso de que tenga padres, diferentes de sí mismo (llamada referencia circular), itera sobre sus padres y ejecuta la misma función para ellos.
Grzegorz Skibinski
Cuando solo doy un número aleatorio para el nodo, obtengo algo como esto. <generator object recursive_walk at 0x0000022A67551D48>. Lo cual es que no sé lo que significa.
Kapital
Pruebe: list(recursive_walk(...))o la [el for el in recursive_walk(...)]función devuelve generator, lo que esencialmente significa que no todos los elementos a la vez, como por ejemplo, listo tupleproporciona iterables que puede usar para devolver todos los valores uno por uno.
Grzegorz Skibinski