Je voudrais générer un ensemble de groupes basé sur un critère booléen OU dans les pandas. Un groupe se compose de membres qui correspondent à la colonne A OU à la colonne B.
Par exemple, dans ce cadre de données:
A B id 0 1 1 0 1 2 2 1 2 2 3 1 3 2 4 1 4 3 3 1 5 4 5 2
Parce que les lignes 1, 2 et 3 correspondent à la colonne A , et 2 et 4 correspondent à la colonne B, j'aimerais que les valeurs d'id soient:
df = pd.DataFrame([[1,1],[2,2],[2,3],[2,4],[3,3],[4,5]], columns = ['A','B']) A B 0 1 1 1 2 2 2 2 3 3 2 4 4 3 3 5 4 5
Je ne trouve aucune solution à part la création d'un graphique scipy NxN avec les connexions, et en utilisant scipy.sparse.csgraph.connected_components . Existe-t-il des options plus simples?
3 Réponses :
Remarquez que je pense que c'est un problème de réseau, donc nous le faisons avec networkx
s1=df.A.astype('category').cat.codes.sort_values()
s2=df.B.astype('category').cat.codes.sort_values()
s=((s1==s1.shift())|(s2==s2.shift())).eq(False).cumsum()
s
#df['new']=s
Out[25]:
0 1
1 2
2 2
3 2
4 2
5 3
dtype: int32+
Mise à jour
import networkx as nx
G=nx.from_pandas_edgelist(df, 'A', 'B')
l=list(nx.connected_components(G))
l
[{1}, {2, 3}]
from itertools import chain
l=[dict.fromkeys(y,x)for x,y in enumerate(l)]#create the list of dict for later map
d=dict(chain(*map(dict.items,l)))# flatten the list of dict to one dict
df['ID']=df.B.map(d)
df
A B ID
0 1 1 0
1 2 2 1
2 2 3 1
3 3 3 1
Je vous remercie. J'ai précisé que les identifiants des colonnes A et B ne sont pas liés - ce sont des identifiants séparés. Y a-t-il un moyen de résoudre ce problème? Les composants connectés traitent les valeurs comme liées entre les colonnes. En outre, est-ce évolutif jusqu'à> 2 colonnes?
Merci pour la mise à jour. Je ne suis pas sûr de suivre l'intuition derrière l'utilisation de df.shift () . Cela ne semble pas robuste à différents ordres, par exemple ajout d'une ligne supplémentaire qui doit avoir le même identifiant que la ligne 0 (car les colonnes A correspondent): df = pd.DataFrame ([[1,1], [2,2], [2,3], [ 2,4], [3,3], [4,5], [1,6]], col umns = ['A', 'B'])
Merci - votre première solution allait dans le bon sens. J'ai étendu le cas d'utilisation (les identifiants des colonnes A et B ne sont pas liés et il pourrait y avoir plusieurs colonnes) ci-dessous.
Nous pouvons le faire en utilisant la classe Counter . Nous comptons le nombre d'occurrences pour chaque élément de la colonne et créons une colonne temporaire avec ces valeurs. Si la valeur d'une ligne de cette colonne temporaire est supérieure à 1 (ce qui signifie que ce nombre apparaît plus d'une fois, nous changeons la colonne id .
import pandas as pd
from collections import Counter as ctr
df = pd.DataFrame([[1,1],[2,2],[2,3],[2,4],[3,3],[4,5]], columns = ['A','B'])
df['id'] = 0
for i in range(len(df.columns)):
if list(df.columns)[i] != 'id':
c = dict(ctr(df[list(df.columns)[i]]))
df[list(df.columns)[i] + '_1'] = df[list(df.columns)[i]].apply(lambda x: c[x])
df.loc[df[list(df.columns)[i] + '_1'] > 1, 'id'] = 1
df = df.drop(columns=[list(df.columns)[i] + '_1'])
df
A B id
0 1 1 0
1 2 2 1
2 2 3 1
3 2 4 1
4 3 3 1
5 4 5 0
Celui-ci doit être évolutif pour> 2 colonnes.
Je ne pense pas que cela fonctionne. Il donne à la première et à la dernière ligne le même identifiant (0), lorsqu'elles ne font pas partie du même composant connecté.
@amball Oh maintenant je comprends ce que vous vouliez dire. Je mettrai à jour ma réponse.
Merci à @ W-B de m'avoir mis sur les bonnes lignes. Voici une réponse plus générale qui fonctionne pour> 2 colonnes et où les valeurs ne sont pas liées entre les colonnes.
import pandas as pd
import networkx as nx
from itertools import chain, combinations
columns = ['A','B','C']
df = pd.DataFrame([[1,1,1],[2,2,2],[2,3,3],[2,4,4],[3,3,4],[4,5,5]], columns = columns)
# make columns unique, so that values in any column are not treated as equivalent to values in another
# if you don't want to overwrite values, create new columns instead
for col in df.columns:
df[col] = str(col)+df[col].astype(str)
colPairs = list(combinations(columns, 2)) # we could match on a subset of column pairs instead
G = nx.compose_all([nx.from_pandas_edgelist(df, colPair[0], colPair[1]) for colPair in colPairs])
l=list(nx.connected_components(G))
l=[dict.fromkeys(y,x)for x,y in enumerate(l)]
d=dict(chain(*map(dict.items,l)))
df['ID']=df.B.map(d)
print(df)
A B C ID
0 A1 B1 C1 0
1 A2 B2 C2 1
2 A2 B3 C3 1
3 A2 B4 C4 1
4 A3 B3 C4 1
5 A4 B5 C5 2
Merci pour cela! Suggestion très mineure, mais pourrait valoir la peine de mettre à jour df.B.map (d) en df [colonnes [0]]. Map (d) pour le garder général.
Que signifie le 2 dans la colonne id?
@AdityaK c'est juste l'identifiant du groupe. Dans cet exemple, il y a 3 groupes (0,1 et 2).