Проверьте, содержит ли столбец pandas все элементы из списка

20

У меня есть df, как это:

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

И список предметов:

letters = ['a','c']

Моя цель - получить все строки из frameкоторых содержат как минимум 2 элемента вletters

Я придумал это решение:

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

Это дает мне то, что я хочу, но это может быть не лучшим решением с точки зрения масштабируемости. Есть ли «векторизованное» решение? Спасибо

Kauber
источник
4
Это даст вам только те строки, которые содержат последнюю букву, потому что вы переопределяете субкадр на любой итерации
Том Рон,
@ TomRon Вы правы, какая ошибка :)
Каубер

Ответы:

12

Я бы построил список Series, а затем применил векторизацию np.all:

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

Это дает, как и ожидалось:

       a
0  a,b,c
1  a,c,f
3  a,z,c
Серж Баллеста
источник
3
поздравляю на 100к!
Питер Хаддад
14

Один из способов - разделить значения столбцов на списки, используя str.split, и проверить, set(letters)является ли один subsetиз полученных списков:

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

Ориентир:

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'
)

введите описание изображения здесь

Yatu
источник
Я получаю, TypeError: unhashable type: 'set'когда я запускаю твой код? запустил его на предоставленной раме aboe
Datanovice
Какая версия? @Datanovice Двойная проверка, и все, кажется, хорошо
Yatu
мои панды 1.0.3и питон, 3.7вероятно, только я
Datanovice
3
@Datanovice Я думаю, вам нужен Python 3.8 для этого :)
Anky
2
Спасибо, я получаю ту же ошибку, что и @Datanovice, и, к сожалению, не могу перейти на python 3.8
Kauber
7

Вы можете использовать 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

Это также решает это:

frame[frame['a'].apply(lambda x: np.all([*map(lambda l: l in x, letters)]))]
Бруно Мелло
источник
6

Используйте 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
ManojK
источник
5

IIUC explodeи логический фильтр

Идея состоит в том, чтобы создать одну серию, после чего мы можем сгруппировать по индексу, подсчитать истинные вхождения в вашем списке, используя накопленную сумму.

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
Datanovice
источник
1
frame.iloc[[x for x in range(len(frame)) if set(letters).issubset(frame.iloc[x,0])]]

вывод:

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

timeit

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

вывод

300 µs ± 32.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Гермес Моралес
источник