Панды фильтруют столбцы фрейма данных по совпадению подстрок

#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