Поиск подстроки в строке в кадре данных pandas выполняется очень медленно - PullRequest
0 голосов
/ 07 августа 2020

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

Мой Inte rnet и поиск 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 занимает вечность. Есть ли у кого-нибудь более производительный, может быть, векторизованный способ решения этой проблемы?

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

Ответы [ 2 ]

0 голосов
/ 11 августа 2020

Обновление

Чтение этой ветки и этой , мне удалось до смешного сократить время процесса, используя понимание списков. Вот 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. * 1016 это до 50 раз быстрее. *

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

Во многом вдохновленный ответом @ 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 ed это 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.

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

0 голосов
/ 07 августа 2020

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

 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...