Удалите ненужные части из строк в столбце

129

Я ищу эффективный способ удаления ненужных частей из строк в столбце DataFrame.

Данные выглядят так:

    time    result
1    09:00   +52A
2    10:00   +62B
3    11:00   +44a
4    12:00   +30b
5    13:00   -110a

Мне нужно обрезать эти данные до:

    time    result
1    09:00   52
2    10:00   62
3    11:00   44
4    12:00   30
5    13:00   110

Я пробовал .str.lstrip('+-')и. str.rstrip('aAbBcC'), но возникла ошибка:

TypeError: wrapper() takes exactly 1 argument (2 given)

Будем признательны за любые указатели!

Яннань Ван
источник

Ответы:

168
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
eumiro
источник
Спасибо! это работает. Я все еще размышляю о map (), не знаю, когда использовать или не использовать ...
Яннан Ван
Мне было приятно увидеть, что этот метод также работает с функцией замены.
BKay
@eumiro, как применить этот результат при повторении каждого столбца?
medev21 01
Могу ли я использовать эту функцию для замены числа, например числа 12? Если я сделаю x.lstrip ('12 '), он уберет все 1 и 2.
Дэйв
77

Как удалить ненужные части из строк в столбце?

Через 6 лет после того, как был опубликован исходный вопрос, у pandas теперь есть большое количество «векторизованных» строковых функций, которые могут кратко выполнять эти операции манипулирования строками.

В этом ответе будут рассмотрены некоторые из этих строковых функций, предложены более быстрые альтернативы и в конце будет проведено сравнение таймингов.


.str.replace

Укажите подстроку / шаблон для сопоставления и подстроку для замены.

pd.__version__
# '0.24.1'

df    
    time result
1  09:00   +52A
2  10:00   +62B
3  11:00   +44a
4  12:00   +30b
5  13:00  -110a

df['result'] = df['result'].str.replace(r'\D', '')
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

Если вам нужно преобразовать результат в целое число, вы можете использовать Series.astype,

df['result'] = df['result'].str.replace(r'\D', '').astype(int)

df.dtypes
time      object
result     int64
dtype: object

Если вы не хотите изменять dfна месте, используйте DataFrame.assign:

df2 = df.assign(result=df['result'].str.replace(r'\D', ''))
df
# Unchanged

.str.extract

Полезно для извлечения подстрок, которые вы хотите сохранить.

df['result'] = df['result'].str.extract(r'(\d+)', expand=False)
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

С extractнеобходимо указать хотя бы одну группу захвата. expand=Falseвернет серию с захваченными элементами из первой группы захвата.


.str.split и .str.get

Разделение работает при условии, что все ваши строки следуют этой согласованной структуре.

# df['result'] = df['result'].str.split(r'\D').str[1]
df['result'] = df['result'].str.split(r'\D').str.get(1)
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

Не рекомендую, если вы ищете общее решение.


Если вас устраивают лаконичные и удобочитаемые str решения на основе аксессуаров, описанные выше, вы можете здесь остановиться. Однако, если вас интересуют более быстрые и производительные альтернативы, продолжайте читать.


Оптимизация: составление списков

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

Моя рецензия, действительно ли циклы for в пандах плохи? Когда мне нужно заботиться? , более подробно.

str.replaceОпция может быть переписана с использованиемre.sub

import re

# Pre-compile your regex pattern for more performance.
p = re.compile(r'\D')
df['result'] = [p.sub('', x) for x in df['result']]
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

str.extractПример может быть переписан с использованием списка понимание с re.search,

p = re.compile(r'\d+')
df['result'] = [p.search(x)[0] for x in df['result']]
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

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

def try_extract(pattern, string):
    try:
        m = pattern.search(string)
        return m.group(0)
    except (TypeError, ValueError, AttributeError):
        return np.nan

p = re.compile(r'\d+')
df['result'] = [try_extract(p, x) for x in df['result']]
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

Мы также можем переписать ответы @ eumiro и @ MonkeyButter, используя списки:

df['result'] = [x.lstrip('+-').rstrip('aAbBcC') for x in df['result']]

И,

df['result'] = [x[1:-1] for x in df['result']]

Применяются те же правила для обработки NaN и т. Д.


Сравнение производительности

введите описание изображения здесь

Графики, созданные с помощью perfplot . Полный список кодов для справки. Соответствующие функции перечислены ниже.

Некоторые из этих сравнений несправедливы, потому что они используют структуру данных OP, но извлекайте из нее все, что хотите. Следует отметить, что каждая функция понимания списка либо быстрее, либо сравнима, чем ее эквивалентный вариант pandas.

функции

def eumiro(df):
    return df.assign(
        result=df['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC')))

def coder375(df):
    return df.assign(
        result=df['result'].replace(r'\D', r'', regex=True))

def monkeybutter(df):
    return df.assign(result=df['result'].map(lambda x: x[1:-1]))

def wes(df):
    return df.assign(result=df['result'].str.lstrip('+-').str.rstrip('aAbBcC'))

def cs1(df):
    return df.assign(result=df['result'].str.replace(r'\D', ''))

def cs2_ted(df):
    # `str.extract` based solution, similar to @Ted Petrou's. so timing together.
    return df.assign(result=df['result'].str.extract(r'(\d+)', expand=False))

def cs1_listcomp(df):
    return df.assign(result=[p1.sub('', x) for x in df['result']])

def cs2_listcomp(df):
    return df.assign(result=[p2.search(x)[0] for x in df['result']])

def cs_eumiro_listcomp(df):
    return df.assign(
        result=[x.lstrip('+-').rstrip('aAbBcC') for x in df['result']])

def cs_mb_listcomp(df):
    return df.assign(result=[x[1:-1] for x in df['result']])
cs95
источник
любой обходной путь, чтобы избежать настройки с предупреждением о копировании:Try using .loc[row_indexer,col_indexer] = value instead
PV8 02
@ PV8 не уверен в вашем коде, но проверьте это: stackoverflow.com/questions/20625582/…
cs95 02
Для любого , который до Regex , как я, \ D такая же , как [^ \ d] (ничего , что не цифра) отсюда . Таким образом, мы по сути заменяем все нецифровые символы в строке ничем.
Риши Лачмеперсад
56

Я бы использовал функцию замены панд, очень простую и мощную, поскольку вы можете использовать регулярное выражение. Ниже я использую регулярное выражение \ D для удаления любых нецифровых символов, но, очевидно, вы можете проявить творческий подход с регулярным выражением.

data['result'].replace(regex=True,inplace=True,to_replace=r'\D',value=r'')
Coder375
источник
Я пробовал это, и это не работает. Мне интересно, работает ли это только тогда, когда вы хотите заменить всю строку, а не просто заменить часть подстроки.
bgenchel
@bgenchel - Я использовал этот метод , чтобы заменить часть строки в pd.Series: df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix"). Это преобразует строку типа «my_prefixaaa» в «new_prefixaaa».
jakub
что делает r в to_replace = r '\ D'?
Лука Гуарро,
@LucaGuarro из документации python: "Префикс r, делающий литерал необработанным строковым литералом, необходим в этом примере, потому что escape-последовательности в обычном" приготовленном "строковом литерале, которые не распознаются Python, в отличие от регулярных выражений, теперь приведет к DeprecationWarning и, в конечном итоге, станет SyntaxError. "
Coder375
35

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

Последний персонаж:

data['result'] = data['result'].map(lambda x: str(x)[:-1])

Первые два персонажа:

data['result'] = data['result'].map(lambda x: str(x)[2:])
prl900
источник
Мне нужно обрезать географические координаты до 8 символов (включая (.), (-)), и в случае, если они меньше 8, мне нужно наконец вставить '0', чтобы все координаты были 8 символов. Как это сделать проще?
Sitz Blogz
Я не совсем понимаю вашу проблему, но вам может потребоваться изменить лямбда-функцию на что-то вроде "{0: .8f}". Format (x)
prl900 01
Большое спасибо за ответ. Проще говоря, у меня есть фрейм данных с географическими координатами - широта и долгота в виде двух столбцов. Длина символов превышает 8 символов, и я оставил только 8 символов, начиная с первого, которые также должны включать (-) и (.).
Sitz Blogz
18

Здесь есть ошибка: в настоящее время нельзя передавать аргументы str.lstripи str.rstrip:

http://github.com/pydata/pandas/issues/2411

РЕДАКТИРОВАТЬ: 2012-12-07 теперь это работает в ветке dev:

In [8]: df['result'].str.lstrip('+-').str.rstrip('aAbBcC')
Out[8]: 
1     52
2     62
3     44
4     30
5    110
Name: result
Уэс МакКинни
источник
11

Очень простой метод - использовать этот extractметод для выбора всех цифр. Просто предоставьте ему регулярное выражение, '\d+'извлекающее любое количество цифр.

df['result'] = df.result.str.extract(r'(\d+)', expand=True).astype(int)
df

    time  result
1  09:00      52
2  10:00      62
3  11:00      44
4  12:00      30
5  13:00     110
Тед Петру
источник
7

Я часто использую составление списков для задач такого типа, потому что они часто быстрее.

Могут быть большие различия в производительности между различными методами для выполнения таких действий (т. Е. Изменения каждого элемента ряда в DataFrame). Часто понимание списка может быть самым быстрым - см. Гонку кода ниже для этой задачи:

import pandas as pd
#Map
data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
%timeit data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
10000 loops, best of 3: 187 µs per loop
#List comprehension
data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
%timeit data['result'] = [x.lstrip('+-').rstrip('aAbBcC') for x in data['result']]
10000 loops, best of 3: 117 µs per loop
#.str
data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
%timeit data['result'] = data['result'].str.lstrip('+-').str.rstrip('aAbBcC')
1000 loops, best of 3: 336 µs per loop
tim654321
источник
4

Предположим, ваш DF также имеет этот дополнительный символ между числами. Последняя запись.

  result   time
0   +52A  09:00
1   +62B  10:00
2   +44a  11:00
3   +30b  12:00
4  -110a  13:00
5   3+b0  14:00

Вы можете попробовать str.replace, чтобы удалить символы не только в начале и конце, но и между ними.

DF['result'] = DF['result'].str.replace('\+|a|b|\-|A|B', '')

Вывод:

  result   time
0     52  09:00
1     62  10:00
2     44  11:00
3     30  12:00
4    110  13:00
5     30  14:00
Риши Бансал
источник
0

Попробуйте это с помощью регулярного выражения:

import re
data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
Мистер Пророк
источник