Wie gruppiere ich Datenrahmenspalten basierend auf ihrer Sequenzbeziehung?

8

Ich versuche, basierend auf ihrer Sequenzbeziehung zwischen den beiden Spalten zu gruppieren.

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

Ich erwarte das Ergebnis etwas unten:

Um es klarer zu machen: - df1 und df2 haben eine Beziehung, die auf ihrer Reihenfolge basiert. Zum Beispiel hat 10 eine direkte Beziehung zu 20 und 10 hat eine indirekte Beziehung zu 30 bis 20. Und 10 hat eine indirekte Beziehung zu 40 bis 20 und 30. Ein weiteres Beispiel, nehmen wir, 80 hat eine direkte Beziehung zu 70 und indirekte Beziehung zu 100 bis 70. Dies funktioniert für den Rest der Spaltenwerte.

  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

Ich versuche, das folgende Skript zu verwenden, aber es konnte mir nicht gelingen.

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

Könnte jemand bei dieser Herausforderung helfen? Wenn es hier bei Stack overflow eine ähnliche Lösung gibt, lassen Sie es mich bitte wissen.

Bearbeiten: Die erste Antwort funktioniert perfekt mit dem obigen Beispiel, aber wenn ich es mit den Daten versuche, die ich machen möchte, funktioniert es nicht richtig. Meine realen Daten sehen wie folgt aus.

    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
quelle
1
Hallo, können Sie die Beziehung zwischen den Spalten klären, nach denen Sie gruppieren möchten?
Eva-VW
1
Hallo Eva, Danke für deine Antwort. df1 und df2 haben eine Beziehung basierend auf ihrer Sequenz. Zum Beispiel hat 10 eine direkte Beziehung zu 20 und 10 hat eine indirekte Beziehung zu 30 bis 20. Und 10 hat eine indirekte Beziehung zu 40 bis 20 und 30. Ein weiteres Beispiel, nehmen wir, 80 hat eine direkte Beziehung zu 70 und indirekte Beziehung zu 100 bis 70. Dies funktioniert für den Rest der Spaltenwerte.
Kapital
Warum gibt es 90 in der Sequenz in der ersten Zeile? Es gibt keine 50 in der ersten Spalte, daher sollte die Sequenz genau dort enden. Vielleicht habe ich etwas falsch verstanden.
Treskov
@treskov Danke für die Antwort. Wie Sie auf Index Nummer 6 sehen, hat 30 eine direkte Beziehung zu 90. und wir wissen, dass 10 eine indirekte Beziehung zu 30 bis 20 hat. 10 hat also eine indirekte Beziehung zu 90 bis 30. Wir könnten dies als eine Art Transitiv bezeichnen Eigentum, aber es ist mehr als das.
Kapital

Antworten:

3

Eine mögliche Lösung:

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)

Drucke:

   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: Andere Lösung basierend auf neuen Eingabedaten. Jetzt suche ich nach möglichen Kreisen im Pfad:

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)

Drucke:

    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

Oder 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: If dfist Ihr Eingabedatenrahmen mit den Spalten "df1" und "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
quelle
Können Sie erklären, wie d[k].extend(chain.from_iterable(d.get(v, []) for v in d[k]))das funktioniert? Ich sah den Arzt an, konnte ihm aber nicht folgen.
Sathyz
@sathyz Ich verwende chain.from_iterable, um die Iterable zu reduzieren - in diesem Fall besteht die Iterable aus Listen aus dem Wörterbuch d(oder leeren Listen, wenn der Schlüssel vin d- nicht vorhanden ist d.get(v, [])). Dann benutze ich diese Werte, um die Liste zu erweitern, unter der gespeichert ist d[k].
Andrej Kesely
@AndrejKesely if not (line := line.strip().split()):ist zu sagen if not (line != line.strip().split()):? oder etwas anderes. Ich bekomme Fehler mit :. Wenn ich es schaffe, !=bekomme ich einen IndexError: string index out of range Fehler in der Leitung df1.append(int(line[1])).
Kapital
1
@ AndrejKesely Perfekt. Vielen vielen Dank, Sir!!
Kapital
1
@Kapital Sie machen etwas falsch, das Ergebnis der Daten (wie in Ihrer Frage angegeben) ist das gleiche wie in meiner ersten Bearbeitung. Verwenden Sie Jupyter Notebook? Wenn ja, laden Sie es neu / starten Sie es neu ... Es gibt keine Möglichkeit, dieses Ergebnis mit meinem aktualisierten Code zu erhalten.
Andrej Kesely
1

Hallo danke für die Klarstellung, ich habe eine Lösung mit einer rekursiven Funktion, die Sie ausprobieren können. Möglicherweise nicht effizient für große Datenrahmen, scheint aber gut zu funktionieren. Die Funktion gibt eine Liste zurück, aber Sie können die resultierende Serie bearbeiten, um die Liste zu einer Zeichenfolge zusammenzufügen, wie Sie möchten.

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
quelle
Können Sie erklären, was "verwandt" aus dem Funktionsargument ist?
Kapital
0

Dies sollte den Trick tun:

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)

Ausgabe:

   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]

(*) Ich habe auch mit erweitertem Datenrahmen nachgesehen - es ist ziemlich schnell, ich werde die Ausgabe nicht freigeben, da meine IDE sie abschneidet;)

Grzegorz Skibinski
quelle
Entschuldigung für die späte Antwort, aber ich habe mir Ihre Lösung angesehen und konnte nicht verstehen, was Sie damit meinen node(einer Ihrer Parameter in der Funktion)? Könntest du es mir sagen?
Kapital
nodeist der Wert, auf dem Sie sich gerade befinden. Sie geben es also zurück, und falls es andere Eltern als sich selbst hat (sog. Zirkelverweis), iterieren Sie über die Eltern und führen dieselbe Funktion für sie aus.
Grzegorz Skibinski
Wenn ich nur eine Zufallszahl für den Knoten gebe, bekomme ich so etwas. <generator object recursive_walk at 0x0000022A67551D48>. Welches ist ich weiß nicht, was es bedeutet.
Kapital
Try: list(recursive_walk(...))oder [el for el in recursive_walk(...)]Funktion gibt zurück generator- was im Wesentlichen bedeutet - nicht alle Elemente gleichzeitig, wie z. B. listoder, tupleaber iterierbar, mit denen Sie alle Werte einzeln zurückgeben können.
Grzegorz Skibinski