Compruebe si la columna pandas contiene todos los elementos de una lista


20

Tengo un df como este:

frame = pd.DataFrame({'a' : ['a,b,c', 'a,c,f', 'b,d,f','a,z,c']})

Y una lista de artículos:

letters = ['a','c']

Mi objetivo es obtener todas las filas frameque contienen al menos los 2 elementos enletters

Se me ocurrió esta solución:

for i in letters:
    subframe = frame[frame['a'].str.contains(i)]

Esto me da lo que quiero, pero podría no ser la mejor solución en términos de escalabilidad. ¿Hay alguna solución 'vectorizada'? Gracias


44
Le dará solo filas que contienen la última letra porque anula el subtrama en cualquier iteración
Tom Ron

@TomRon Tienes razón, qué error :)
Kauber

Respuestas:


12

Construiría una lista de Series y luego aplicaría un vectorizado np.all:

contains = [frame['a'].str.contains(i) for i in letters]
resul = frame[np.all(contains, axis=0)]

Da como se esperaba:

       a
0  a,b,c
1  a,c,f
3  a,z,c

3
¡Felicidades por 100k!
Peter Haddad

14

Una forma es dividir los valores de las columnas en listas usando str.split, y verificar si set(letters)es una subsetde las listas obtenidas:

letters_s = set(letters)
frame[frame.a.str.split(',').map(letters_s.issubset)]

     a
0  a,b,c
1  a,c,f
3  a,z,c

Punto de referencia:

def serge(frame):
    contains = [frame['a'].str.contains(i) for i in letters]
    return frame[np.all(contains, axis=0)]

def yatu(frame):
    letters_s = set(letters)
    return frame[frame.a.str.split(',').map(letters_s.issubset)]

def austin(frame):
    mask =  frame.a.apply(lambda x: np.intersect1d(x.split(','), letters).size > 0)
    return frame[mask]

def datanovice(frame):
    s = frame['a'].str.split(',').explode().isin(letters).groupby(level=0).cumsum()
    return frame.loc[s[s.ge(2)].index.unique()]

perfplot.show(
    setup=lambda n: pd.concat([frame]*n, axis=0).reset_index(drop=True), 

    kernels=[
        lambda df: serge(df),
        lambda df: yatu(df),
        lambda df: df[df['a'].apply(lambda x: np.all([*map(lambda l: l in x, letters)]))],
        lambda df: austin(df),
        lambda df: datanovice(df),
    ],

    labels=['serge', 'yatu', 'bruno','austin', 'datanovice'],
    n_range=[2**k for k in range(0, 18)],
    equality_check=lambda x, y: x.equals(y),
    xlabel='N'
)

ingrese la descripción de la imagen aquí


Me sale TypeError: unhashable type: 'set'cuando ejecuto su código? lo ejecutó en el marco proporcionado anteriormente
Datanovice

Que versión @Datanovice Comprobación doble y todo parece estar bien
yatu

mis pandas son 1.0.3y python 3.7probablemente solo soy yo
Datanovice

3
@Datanovice, creo que necesitas Python 3.8 para esto :)
anky

2
Gracias, recibo el mismo error que @Datanovice y desafortunadamente no puedo saltar a Python 3.8
Kauber

7

Puedes usar np.intersect1d:

import pandas as pd
import numpy as np

frame = pd.DataFrame({'a' : ['a,b,c', 'a,c,f', 'b,d,f','a,z,c']})
letters = ['a','c']

mask =  frame.a.apply(lambda x: np.intersect1d(x.split(','), letters).size > 0)
print(frame[mask])

    a
0  a,b,c
1  a,c,f
3  a,z,c

7

Esto también lo resuelve:

frame[frame['a'].apply(lambda x: np.all([*map(lambda l: l in x, letters)]))]

6

Utilice set.issubset :

frame = pd.DataFrame({'a' : ['a,b,c', 'a,c,f', 'b,d,f','a,z,c','x,y']})
letters = ['a','c']

frame[frame['a'].apply(lambda x: set(letters).issubset(x))]

Out:

       a
0  a,b,c
1  a,c,f
3  a,z,c

5

IIUC explodey un filtro booleano

la idea es crear una serie única, luego podemos agrupar por índice el recuento de las ocurrencias verdaderas de su lista usando una suma acumulativa

s = frame['a'].str.split(',').explode().isin(letters).groupby(level=0).cumsum()

print(s)

0    1.0
0    1.0
0    2.0
1    1.0
1    2.0
1    2.0
2    0.0
2    0.0
2    0.0
3    1.0
3    1.0
3    2.0

frame.loc[s[s.ge(2)].index.unique()]

out:

       a
0  a,b,c
1  a,c,f
3  a,z,c

1
frame.iloc[[x for x in range(len(frame)) if set(letters).issubset(frame.iloc[x,0])]]

salida:

        a
 0  a,b,c
 1  a,c,f
 3  a,z,c

cronométralo

%%timeit
#hermes
frame.iloc[[x for x in range(len(frame)) if set(letters).issubset(frame.iloc[x,0])]]

salida

300 µs ± 32.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.