Удалите строки из DataFrame pandas на основе условного выражения с использованием len (string), дающего KeyError

303

У меня есть DataFrame pandas, и я хочу удалить из него строки, где длина строки в определенном столбце больше 2.

Я ожидаю, что смогу сделать это (за этот ответ ):

df[(len(df['column name']) < 2)]

но я просто получаю ошибку:

KeyError: u'no item named False'

Что я делаю не так?

(Примечание: я знаю, что могу использовать, df.dropna()чтобы избавиться от строк, которые их содержат NaN, но я не видел, как удалить строки на основе условного выражения.)

SJS
источник

Ответы:

169

Когда вы это делаете, len(df['column name'])вы просто получаете одно число, а именно количество строк в DataFrame (то есть длину самого столбца). Если вы хотите применить lenк каждому элементу в столбце, используйте df['column name'].map(len). Поэтому постарайтесь

df[df['column name'].map(len) < 2]
BrenBarn
источник
3
Я придумала способ использования списка: df[[(len(x) < 2) for x in df['column name']]]но у тебя гораздо приятнее. Спасибо за вашу помощь!
sjs
13
Если кому-то нужно более сложное сравнение, всегда можно использовать лямбду. df[df['column name'].map(lambda x: str(x)!=".")]
июля
1
По какой-то причине ни одна из других опций не сработала для меня, кроме той, которую написал @ 4lberto. Я pandas 0.23.4и Python 3.6
goelakash
1
Я бы добавил .copy()в конце, на случай, если вы захотите позже отредактировать этот фрейм данных (например, назначение новых столбцов вызовет предупреждение «Значение пытается быть скопировано с копии среза из фрейма данных».
PlasmaBinturong
807

Чтобы напрямую ответить на оригинальный заголовок этого вопроса «Как удалить строки из DataFrame pandas на основе условного выражения» (что, как я понимаю, не обязательно является проблемой OP, но может помочь другим пользователям, сталкивающимся с этим вопросом), один из способов сделать это - использовать падение метод:

df = df.drop(some labels)

df = df.drop(df[<some boolean condition>].index)

пример

Чтобы удалить все строки, в которых столбец «Score» <50:

df = df.drop(df[df.score < 50].index)

Версия на месте (как указано в комментариях)

df.drop(df[df.score < 50].index, inplace=True)

Несколько условий

(см. логическое индексирование )

Операторы: |для or, &для andи ~дляnot . Они должны быть сгруппированы с помощью скобок.

Чтобы удалить все строки, где столбец «оценка» <50 и> 20

df = df.drop(df[(df.score < 50) & (df.score > 20)].index)

пользователь
источник
32
Я просто хочу отметить, что функция drop поддерживает замену на месте. То есть ,. ваше решение такое же, как df.drop (df [df.score <50] .index, inplace = True). Тем не менее, не знал "индексный" трюк. Мне очень помогли
Quickbeam2k1
9
Просто хочу отметить, что прежде чем использовать этот трюк с индексом, вы должны быть уверены, что ваши значения индекса уникальны (или вызов reset_index()). Я нашел это трудным путем, когда из моего кадра данных пропал путь ко многим строкам.
Джей
3
Как мне удалить все строки, где тип столбца str? Я хочу сохранить только типы столбцов списка. Я попробовал , test = df.drop(df[df['col1'].dtype == str].index)но я получаю ошибку KeyError: False я также попробовал df.drop(df[df.col1.dtype == str].index)и , df.drop(df[type(df.cleaned_norm_email) == str].index)но ничего не похоже на работу? Может кто-нибудь посоветовать. Спасибо! @User
PyRsquared
1
Это старый вопрос, но ... @ aquatical-challengeed-fish намного быстрее, чем этот. Обратите внимание, что вы рассчитываете df[(df.score < 50) & (df.score > 20)]как часть вашего ответа. Если вы измените это, df = df[(df.score >= 50) | (df.score <= 20)]вы получите свой ответ намного быстрее.
Руби Нуби
1
@RoobieNuby - это не одно и то же состояние.
Нгуай Аль
106

Вы можете назначить DataFrameотфильтрованную версию себя:

df = df[df.score > 50]

Это быстрее чем drop:

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test[test.x < 0]
# 54.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test.drop(test[test.x > 0].index, inplace=True)
# 201 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test.drop(test[test.x > 0].index)
# 194 ms ± 7.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
кабардинских
источник
Как проверить наличие нескольких столбцов или условие?
Пиюш С. Ванаре
9

Я буду расширять общее решение @ User, чтобы обеспечить drop бесплатную альтернативу. Это для людей, которых здесь направляют, основываясь на названии вопроса (не проблема ОП)

Скажем, вы хотите удалить все строки с отрицательными значениями. Одно решение лайнера является: -

df = df[(df > 0).all(axis=1)]

Пошаговое объяснение: -

Давайте сгенерируем случайный кадр данных нормального распределения 5x5

np.random.seed(0)
df = pd.DataFrame(np.random.randn(5,5), columns=list('ABCDE'))
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
1 -0.977278  0.950088 -0.151357 -0.103219  0.410599
2  0.144044  1.454274  0.761038  0.121675  0.443863
3  0.333674  1.494079 -0.205158  0.313068 -0.854096
4 -2.552990  0.653619  0.864436 -0.742165  2.269755

Пусть условие удаляет негативы. Логическое значение df, удовлетворяющее условию:

df > 0
      A     B      C      D      E
0   True  True   True   True   True
1  False  True  False  False   True
2   True  True   True   True   True
3   True  True  False   True  False
4  False  True   True  False   True

Булева серия для всех строк, удовлетворяющих условию. Примечание. Если какой-либо элемент в строке не соответствует условию, строка помечается как ложная.

(df > 0).all(axis=1)
0     True
1    False
2     True
3    False
4    False
dtype: bool

Наконец, отфильтруйте строки из фрейма данных на основе условия

df[(df > 0).all(axis=1)]
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
2  0.144044  1.454274  0.761038  0.121675  0.443863

Вы можете назначить его обратно в df, чтобы фактически удалить против фильтрации, выполненной выше
df = df[(df > 0).all(axis=1)]

Это может быть легко расширено для фильтрации строк, содержащих NaN (не числовые записи): -
df = df[(~df.isnull()).all(axis=1)]

Это также может быть упрощено для случаев, таких как: Удалить все строки, где столбец E является отрицательным

df = df[(df.E>0)]

Я хотел бы закончить некоторыми статистическими данными о том, почему dropрешение @ User медленнее, чем простая фильтрация на основе столбцов:

%timeit df_new = df[(df.E>0)]
345 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit dft.drop(dft[dft.E < 0].index, inplace=True)
890 µs ± 94.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Столбец - это массив, Seriesто есть NumPyон может быть проиндексирован без каких-либо затрат. Для людей, интересующихся тем, как основная организация памяти влияет на скорость выполнения, вот отличная ссылка на ускорение работы Pandas :

Закир
источник
6

В пандах вы можете str.lenиспользовать границы и использовать логический результат для его фильтрации.

df[df['column name'].str.len().lt(2)]
YOBEN_S
источник
3

Если вы хотите отбросить строки фрейма данных на основе некоторого сложного условия в значении столбца, то запись, как показано выше, может быть сложной. У меня есть следующее простое решение, которое всегда работает. Предположим, что вы хотите удалить столбец с заголовком, поэтому сначала поместите этот столбец в список.

text_data = df['name'].tolist()

Теперь примените некоторую функцию к каждому элементу списка и поместите его в серию панда:

text_length = pd.Series([func(t) for t in text_data])

в моем случае я просто пытался получить количество токенов:

text_length = pd.Series([len(t.split()) for t in text_data])

Теперь добавьте один дополнительный столбец с указанными выше рядами во фрейм данных:

df = df.assign(text_length = text_length .values)

Теперь мы можем применить условие к новому столбцу, например:

df = df[df.text_length  >  10]
def pass_filter(df, label, length, pass_type):

    text_data = df[label].tolist()

    text_length = pd.Series([len(t.split()) for t in text_data])

    df = df.assign(text_length = text_length .values)

    if pass_type == 'high':
        df = df[df.text_length  >  length]

    if pass_type == 'low':
        df = df[df.text_length  <  length]

    df = df.drop(columns=['text_length'])

    return df
Джаянти Прасад
источник