Видаліть із стовпців небажані частини зі стовпців


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)

Будь-які вказівники будуть дуже вдячні!

Відповіді:


167
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))

Дякую! що працює. Я все ще обертаю свою думку навколо карти (), не знаю, коли її використовувати чи не використовувати ...
Yannan Wang

Мені було приємно побачити, що цей метод також працює з функцією заміни.
BKay

@eumiro, як ви застосовуєте цей результат, якщо повторюєте кожен стовпець?
medev21

Чи можна за допомогою цієї функції замінити таке число, як число 12? Якщо я роблю x.lstrip ('12 '), він знімає всі 1 і 2 секунди.
Дейв

76

Як видалити небажані частини з рядків у стовпці?

Через 6 років після розміщення оригінального запитання в пандах тепер є велика кількість "векторизованих" рядкових функцій, які можуть лаконічно виконувати ці операції з маніпуляції з рядками.

Ця відповідь вивчить деякі з цих рядкових функцій, запропонує більш швидкі варіанти та перейде до порівняння таймінгів наприкінці.


.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. Причина полягає в тому, що рядкові функції за своєю суттю важко векторизувати (у справжньому розумінні цього слова), тому більшість функцій рядків і регулярних виразів є лише обгортками навколо циклів з більшою накладними витратами.

Моє записування: Чи справді циклі у пандах погані? Коли я повинен піклуватися? , детальніше.

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

Якщо NaNs або не збіг є можливістю, вам потрібно буде переписати вище, щоб включити деяку перевірку помилок. Я роблю це за допомогою функції.

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 . Повний список коду, для довідки. Відповідні функції перераховані нижче.

Деякі з цих порівнянь є несправедливими, оскільки вони користуються структурою даних ОП, але беруть з цього те, що ви хочете. Варто зазначити, що кожна функція розуміння списку є або швидшою, або порівнянною, ніж її еквівалентний варіант панди.

Функції

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']])

будь-яке рішення, щоб уникнути встановлення з попередженням про попередження:Try using .loc[row_indexer,col_indexer] = value instead
PV8

@ PV8 не впевнені в своєму коді, але перевірити це: stackoverflow.com/questions/20625582 / ...
cs95

Для всіх, хто є новим у REGEX, як я, \ D - це те саме, що [^ \ d] (усе, що не є цифрою) звідси . Таким чином, ми в основному замінюємо всі нецифрові рядки нічим.
Rishi Latchmepersad

56

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

data['result'].replace(regex=True,inplace=True,to_replace=r'\D',value=r'')

Я спробував це, і це не працює. Мені цікаво, чи працює вона лише тоді, коли ви хочете замінити цілий рядок, а не просто замінити частину підрядка.
bgenchel

@bgenchel - я використав цей метод для заміни частини рядка в pd.Series : df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix"). Це перетворить рядок типу "my_prefixaaa" в "new_prefixaaa".
Якуб

що робить r в to_replace = r '\ D'?
Лука Гуарро

@LucaGuarro з python docs: "Префікс r, що робить літерал необмеженим рядковим літералом, потрібен у цьому прикладі, оскільки послідовності втечі у звичайному" приготовленому "рядковому літералі, які не розпізнаються 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:])

Мені потрібно обрізати геокоординати до 8 символів (включаючи (.), (-)), а якщо їх менше 8, мені потрібно вставити "0" нарешті, щоб усі координати склали 8 символів. Який простіший спосіб зробити це?
Sitz Blogz

Я не повністю розумію вашу проблему, але вам може знадобитися змінити функцію лямбда на щось на кшталт "{0: .8f}". Формат (x)
prl900

Дуже дякую за відповідь. Простими словами, у мене є кадр даних з геокоординатами - широта та довгота як два стовпці. Довжина символів - понад 8 символів, і я мав зберігати лише 8 символів, починаючи з першого, який також повинен містити (-) і (.).
Sitz Blogz


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

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.