Поиск подстроки в строке в DataFrame pandas очень медленный

Изменить: я понял, выполняя это упражнение, что мне нужно извлекать целые слова, а не части слов. Я отредактировал исходный вопрос и свой ответ, чтобы сделать код более устойчивым к этому проекту, не меняя сути проблемы.

Мой поиск в Интернете и SO не дал результата, поэтому я обращаюсь к вам.

У меня есть DataFrame, который выглядит так:

import pandas as pd

rows = [
    ('chocolate', 'choco'),
    ('banana', pd.np.nan),
    ('hello world', 'world'),
    ('hello you', 'world'),
    ('hello you choco', 'world'),
    ('this is a very long sentence', 'very long')
]
data = pd.DataFrame.from_records(rows, columns=['origin', 'to_find'])
                         origin    to_find
0                     chocolate      choco
1                        banana        NaN
2                   hello world      world
3                     hello you      world
4               hello you choco      world
5  this is a very long sentence  very long

Моя цель - найти строку второго столбца в первом столбце и удалить ее. Если я не найду подстрокуto_find в origin, Я заменяю to_find с NaN. Поскольку это строковая операция, которую нужно выполнять построчно, я выбралapplyпуть. Это моя функция, которая * работает почти так, как ожидалось, и как яapply Это:

def find_word(row):
    # Handle the case where to_find is already NaN
    if row.to_find is pd.np.nan:
        return row

    if row.to_find in row.origin:
        row.origin = row.origin.replace(row.to_find, '').strip()
    else:
        row.to_find = pd.np.nan

    return row

new_df = data.apply(find_word, axis=1)

* этот код возвращает два пробела вместо одного между this is a а также sentence, что нежелательно.

В new_df ожидается, что это будет выглядеть так:

                origin    to_find
0                 late      choco
1               banana        NaN
2                hello      world
3            hello you        NaN
4      hello you choco        NaN
5  this is a sentence   very long

Моя проблема в том, что мой оригинал dfимеет миллионы строк, и эта конкретная операция с огромным DataFrame занимает вечность. Есть ли у кого-нибудь более производительный, может быть, векторизованный способ решения этой проблемы?

(The .containsпохоже, работает только для поиска одной конкретной строки в векторе, а не попарно. Это был мой лучший ход, но он не работал.)

2 ответа

Решение

Обновить

Читая эту и эту ветку, мне удалось до смешного сократить время процесса, используя понимание списков. Сейчас начнетсяmethod_3:

def method_3(df):
    df["to_find"] = df["to_find"].fillna('')
    df['temp_origin'] = df['origin'].copy()
    
    df['origin'] = [' '.join([x for x in a.split() if x not in set(b.split())]) for a, b in zip(df['origin'], df['to_find'])]

    df['temp_origin'] = [' '.join([x for x in a.split(' ') if x not in set(b.split(' '))]) for a, b in zip(df['temp_origin'], df['origin'])]
    df['temp_origin'] = df['temp_origin'].replace('', pd.np.nan)
    
    del df['to_find']
    df.rename(columns={'temp_origin': 'to_find'}, inplace=True)
    
    return df

Теперь с новыми таймингами:

Method 1 took 13.820100281387568 sec.
Method 2 took 2.89176794141531 sec.
Method 3 took 0.26977075077593327 sec.

Три подхода: O(n), но при использовании method_3.

Исходный пост

Во многом вдохновленный ответом @sygneto, мне удалось улучшить скорость почти в 5 раз.

Два разных метода

Я поместил свой первый метод в функцию с именем method_1 а другой в method_2:

def find_word(row):
    if row.to_find is pd.np.nan:
        return row

    if row.to_find in row.origin:
        row.origin = row.origin.replace(row.to_find, '').strip()
    else:
        row.to_find = pd.np.nan

    return row

def method_1(df):
    return df.apply(find_word, axis=1)

def method_2(df):
    df = df.fillna('')
    df['temp_origin'] = df['origin']
    
    df["origin"] = df.apply(lambda x: x["origin"].replace(x["to_find"], ""), axis=1)
    df["to_find"] = df.apply(lambda x: pd.np.nan if x["origin"] == (x["temp_origin"]) else x["to_find"], axis=1)
    
    del df['temp_origin']
    return df

Измерьте скорость для обоих методов

Чтобы сравнить затраченное время, я взял свой начальный DataFrame и concatредактировал это 10000 раз:

from timeit import default_timer

df = pd.concat([data] * 10000)

t0 = default_timer()
new_df_1 = method_1(df)
t1 = default_timer()

df = pd.concat([data] * 10000)

t2 = default_timer()
new_df_2 = method_2(df)
t3 = default_timer()

print(f"Method 1 took {t1-t0} sec.")
print(f"Method 2 took {t3-t2} sec.")

который выводит:

Method 1 took 11.803373152390122 sec.
Method 2 took 2.362371975556016 sec.

Возможно, есть место для улучшений, но все же большой шаг сделан.

Это решение должно работать для обеих сторон, если вы хотите заменить origin с участием to_find. Используется оригинальная форма'origin' столбец как temp_originно ваш ожидаемый результат не имеет смысла в последней строке, где to_find нан.

 rows = [
        ('chocolate', 'choco'),
        ('banana', np.nan),
        ('hello world', 'world'),
        ('hello you', 'world')
    ]
    df = pd.DataFrame.from_records(rows, columns=['origin', 'to_find'])
    
df=df.fillna('')
df['temp_origin']=df['origin']

df["origin"] = df.apply(
    lambda x: x["origin"].replace(x["to_find"], ""), axis=1
)

df["to_find"] = df.apply(
    lambda x: x["to_find"].replace(x["temp_origin"], ""), axis=1
)
df=df.replace('',np.nan)
del df['temp_origin']

print(df)
      origin to_find
0       late   choco
1     banana     NaN
2     hello    world
3  hello you   world
Другие вопросы по тегам