Tengo un marco de datos df

Col1   Col2
A      B
C      A
B      D
E      F
G      D
G      H
K      J

Y una serie id de ID

ID
A
F

Lo que quiero es, para todas las letras en id, seleccionar otras letras que tengan algún vínculo con un máximo de 2 intermedios. Hagamos el ejemplo de A (mucho más fácil de entender con el ejemplo):

Hay 2 líneas que incluyen A, vinculadas a B y C, por lo que los vínculos directos a A son [B, C]. (No importa si A está en Col1 o Col2)

A      B
C      A

Pero B también está vinculado a D, y D está vinculado a G:

B      D
G      D

Entonces, los enlaces a A son [B, C, D, G]. Aunque G y H están vinculados, generaría más de 2 intermedios de A (A > B > D > G > H haciendo B, D y G como intermedios), por lo que no incluyo H en las listas de enlaces A.

G      H

Estoy buscando una forma de buscar todos los ID en id, la lista de enlaces, y guardarlos en id:

ID   LinksList
A    [B, C, D, G]
F    [E]

No me importa el tipo de LinksList (puede ser String) en la medida en que puedo obtener la información de una identificación específica y trabajar con ella. Tampoco me importa el orden de los ID en LinksList, siempre que esté completo.

Ya encontré una manera de resolver el problema, pero usando 3 bucles for, por lo que lleva mucho tiempo. (Para k1 en ID, para rango k2 (0,3), seleccione enlaces directos para cada elemento de LinksList + elemento inicial y colóquelos en LinksList si aún no están). ¿Alguien puede ayudarme a hacerlo solo con Pandas? Muchas gracias por adelantado !!

==== EDITAR: Aquí están los "3 bucles", después del comentario de Karl: ====

i = 0
for k in id:
    linklist = list(df[df['Col1'] == k]['Col2']) + list(df[df['Col2'] == k]['Col1'])
    new = df.copy()
    intermediate_count = 1
    while(len(new) > 0 and intermediate_count <= 2):
        nn = new.copy()
        new = []
        for n in nn:
            toadd = list(df[df['Col1'] == n]['Col2']) + list(df[df['Col2'] == n]['Col1'])
            toadd = list(set(toadd).difference(df))
            df = df + toadd
            new = new + toadd
        
    if(i==0):
        d = {'Id': k, 'Linked': linklist}
        df_result = pd.DataFrame(data=d)
        i = 1
    else:
        d = {'Id': k, 'Linked': linklist}
        df_result.append(pd.DataFrame(data=d))
1
BeamsAdept 22 mar. 2021 a las 11:35

2 respuestas

La mejor respuesta

Primero agregaría el recíproco del marco de datos para poder ir siempre de Col1 a Col2. Luego usaría fusiones para calcular los posibles resultados con 1 y 2 pasos intermedios. Finalmente, agregaría todos esos valores en conjuntos. El código podría ser:

# append the symetric (Col2 -> Col1) to the end of the dataframe
df2 = df.append(df.reindex(columns=reversed(df.columns)).rename(
    columns={df.columns[len(df.columns)-i]: col
             for i, col in enumerate(df.columns, 1)}), ignore_index=True
                ).drop_duplicates()

# add one step on Col3
df3 = df2.merge(df2, 'left', left_on='Col2', right_on='Col1',
                suffixes=('', '_')).drop(columns='Col1_').rename(
                    columns={'Col2_': 'Col3'})

# add one second stop on Col4
df4 = df3.merge(df2, 'left', left_on='Col3', right_on='Col1',
                suffixes=('', '_')).drop(columns='Col1_').rename(
                    columns={'Col2_': 'Col4'})

# aggregate Col2 to Col4 into a set
df4['Links'] = df4.iloc[:, 1:].agg(set, axis=1)

# aggregate that new column grouped by Col1
result = df4.groupby('Col1')['Links'].agg(lambda x: set.union(*x)).reset_index()

# remove the initial value if present in Links
result['Links'] = result['Links'] - result['Col1'].apply(set)

# and display the result restricted to id
print(result[result['Col1'].isin(id)])

Con los datos de muestra, da como se esperaba:

  Col1         Links
0    A  {D, C, B, G}
5    F           {E}
2
Serge Ballesta 24 mar. 2021 a las 14:49

Podemos usar la biblioteca Networkx:

import networkx as nx
import pandas as pd
import matplotlib.pyplot as plt

# Read in pandas dataframe using copy and paste
df = pd.read_clipboard()

# Create graph network from pandas dataframe
G = nx.from_pandas_edgelist(df, 'Col1', 'Col2')

# Create id, Series
id = pd.Series(['A', 'F'])

# Move values in the index of the Series
id.index=id

# Use `single_source_shortest_path` method in nx for each value in, id, Series
id.apply(lambda x: list(nx.single_source_shortest_path(G, x, 3).keys())[1:])

Salida:

A    [B, C, D, G]
F             [E]
dtype: object

Representación gráfica de impresión:

enter image description here

2
Scott Boston 24 mar. 2021 a las 15:51