#python #pandas #string #dataframe #substring
#питон #панды #строка #фрейм данных #подстрока
Вопрос:
У меня есть фрейм данных с несколькими столбцами, например:
Name Age Fname 0 Alex 10 Alice 1 Bob 12 Bob 2 Clarke 13 clarke
Мое условие фильтра-проверить, является ли Name
(без учета регистра) подстрока соответствующей Fname
.
Если бы это было равенство, что-то такое простое, как:
df[df["Name"].str.lower() == df["Fname"].str.lower()]
работает. Однако я хочу, чтобы подстрока совпадала, поэтому вместо ==
этого я подумал in
, что это сработает. Но это приводит к ошибке, поскольку он интерпретирует один из аргументов как pd.Series
. Мой 1-й вопрос таков Why this difference in interpretation?
Другим способом, который я пробовал, было использование .str.contains
:
df[df["Fname"].str.contains(df["Name"], case=False)]
который также интерпретируется df["Name"]
как pd.Series
и , конечно, работает для некоторой строки const в аргументе.
eg. this works: df[df["Fname"].str.contains("a", case=False)]
Я хочу разрешить эту ситуацию, поэтому буду признателен за любую помощь в этом отношении.
Ответ №1:
Доступ к .str чрезвычайно петлевой и медленный. В большинстве случаев лучше всего использовать понимание списка.
import pandas as pd import numpy as np import timeit import matplotlib.pyplot as plt import pandas.testing as pt def list_comprehension_lower(df): return df[[len(set(i)) == 1 for i in (zip([x.lower() for x in df['Name']],[y.lower() for y in df['Fname']]))]] def apply_axis_1_lower(df): return df[df.apply(lambda x: x['Name'].lower() in x['Fname'].lower(), axis=1)] def dot_string_lower(df): return df[df["Name"].str.lower() == df["Fname"].str.lower()] fig, ax = plt.subplots() res = pd.DataFrame( index=[1, 5, 10, 30, 50, 100, 300, 500, 700, 1000, 10000], columns='list_comprehension_lower apply_axis_1_lower dot_string_lower'.split(), dtype=float ) for i in res.index: d = pd.concat([df]*i, ignore_index=True) for j in res.columns: stmt = '{}(d)'.format(j) setp = 'from __main__ import d, {}'.format(j) res.at[i, j] = timeit.timeit(stmt, setp, number=100) res.groupby(res.columns.str[4:-1], axis=1).plot(loglog=True, ax=ax);
Выход:
Теперь, возвращаясь к вашему первоначальному вопросу, вы можете использовать list_comprehension с zip
и in
:
df.loc[2, 'Fname'] = ' Adams' df[[x in y for x, y in zip([x.lower() for x in df['Name']],[y.lower() for y in df['Fname']])]]
Выход:
Name Age Fname 1 Bob 12 Bob 2 Clarke 13 clarke Adams
Комментарии:
1. Разве этот график не показывает, что его лучше использовать
.str.lower()
? Или я что-то упускаю?2. Да, в этом случае вы правы, выходя за пределы определенного количества строк .str lower out выполняет понимание списка.
3. понимание этого списка является двойным циклом для, поэтому диаграмма имеет смысл, что в какой-то момент функция Pandas str превзойдет ее
Ответ №2:
Вы можете выполнить итерацию по оси индекса:
gt;gt;gt; df[df.apply(lambda x: x['Name'].lower() in x['Fname'].lower(), axis=1)] Name Age Fname 1 Bob 12 Bob 2 Clarke 13 clarke
str.contains
принимает константу в первом аргументе pat
, а не a Series
.
Комментарии:
1. Я только что обновил свой ответ, прежде чем увидел ваш. Наши ответы теперь идентичны! 😀
2. К сожалению, на самом деле у вас здесь нет выбора.
3. Вау, тогда дай мне посмотреть, смогу ли я что-нибудь придумать.
4. Для 900 тысяч строк потребовалось 8,13 секунды. На самом деле все не так уж плохо.
5. Поскольку я выполняю большую обработку перед этой окончательной фильтрацией, 8-10 секунд для меня не имеют большого значения. Следовательно, принимаю этот ответ.
Ответ №3:
Вы можете использовать .apply()
с axis=1
для вызова функции для каждой строки:
subset = df[df.apply(lambda x: x['Name'].lower() in x['Fname'].lower(), axis=1)]
Выход:
gt;gt;gt; subset Name Age Fname 1 Bob 12 Bob 2 Clarke 13 clarke
Комментарии:
1. Моя ошибка…в той конкретной строке кода, которую я упомянул (отредактировал), была ошибка. Но, пожалуйста, прочитайте весь вопрос целиком. Это не то, чего я хочу. Мое требование — «соответствие подстроки».
2. @vish4071 проверьте еще раз; я обновил ответ 🙂
3. Не слишком ли это дорого, особенно для больших фреймов данных?
Ответ №4:
Работает ли для вас этот другой вариант с помощью понимания списка:
df.loc[[left.lower() in right.lower() for left, right in zip(df.Name, df.Fname)] ] Name Age Fname 1 Bob 12 Bob 2 Clarke 13 clarke