Hapus bagian yang tidak diinginkan dari string dalam kolom


129

Saya mencari cara yang efisien untuk menghapus bagian yang tidak diinginkan dari string di kolom DataFrame.

Data terlihat seperti:

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

Saya perlu memotong data ini ke:

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

Saya mencoba .str.lstrip('+-')dan. str.rstrip('aAbBcC'), tetapi mendapat kesalahan:

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

Pointer apa pun akan sangat dihargai!

Jawaban:


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

Terima kasih! itu bekerja. Saya masih membungkus pikiran saya di sekitar peta (), tidak yakin kapan harus menggunakan atau tidak menggunakannya ...
Yannan Wang

Saya senang melihat bahwa metode ini juga berfungsi dengan fungsi ganti.
BKay

@ eumiro bagaimana Anda menerapkan hasil ini jika iterasi setiap kolom?
medev21 21

Bisakah saya menggunakan fungsi ini untuk mengganti nomor seperti nomor 12? Jika saya melakukan x.lstrip ('12 ') ia mengeluarkan semua 1 dan 2s.
Dave

76

Bagaimana cara menghapus bagian yang tidak diinginkan dari string di kolom?

6 tahun setelah pertanyaan asli dikirimkan, panda sekarang memiliki sejumlah fungsi string "vectorised" yang dapat melakukan operasi manipulasi string ini secara ringkas.

Jawaban ini akan mengeksplorasi beberapa fungsi string ini, menyarankan alternatif yang lebih cepat, dan masuk ke perbandingan timing di bagian akhir.


.str.replace

Tentukan substring / pola yang cocok, dan substring untuk menggantinya.

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

Jika Anda membutuhkan hasil yang dikonversi ke integer, Anda dapat menggunakan Series.astype,

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

df.dtypes
time      object
result     int64
dtype: object

Jika Anda tidak ingin memodifikasi dfdi tempat, gunakan DataFrame.assign:

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

.str.extract

Berguna untuk mengekstraksi substring yang ingin Anda pertahankan.

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

Dengan extract, perlu menentukan setidaknya satu grup tangkap. expand=Falseakan mengembalikan Seri dengan item yang diambil dari grup tangkapan pertama.


.str.split dan .str.get

Pekerjaan pemisahan dengan asumsi semua string Anda mengikuti struktur yang konsisten ini.

# 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

Jangan rekomendasikan jika Anda mencari solusi umum.


Jika Anda puas dengan str solusi berbasis accessor yang ringkas dan mudah dibaca , Anda bisa berhenti di sini. Namun, jika Anda tertarik pada alternatif yang lebih cepat dan lebih berkinerja, teruslah membaca.


Mengoptimalkan: Daftar Pemahaman

Dalam beberapa keadaan, pemahaman daftar harus lebih disukai daripada fungsi string panda. Alasannya adalah karena fungsi string secara inheren sulit untuk di-vektorisasi (dalam arti sebenarnya dari kata itu), sehingga sebagian besar fungsi string dan regex hanya membungkus loop dengan lebih banyak overhead.

Tulisan saya, Apakah for-loop di panda benar-benar buruk? Kapan saya harus peduli? , masuk ke detail yang lebih besar.

The str.replacepilihan dapat ditulis ulang menggunakanre.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

The str.extractcontoh dapat ditulis ulang menggunakan pemahaman daftar dengan 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

Jika NaN atau tidak-cocok adalah suatu kemungkinan, Anda harus menulis ulang di atas untuk memasukkan beberapa pengecekan kesalahan. Saya melakukan ini menggunakan fungsi.

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

Kami juga dapat menulis ulang jawaban @ eumiro dan @ MonkeyButter menggunakan daftar pemahaman:

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

Dan,

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

Aturan yang sama untuk menangani NaN, dll, berlaku.


Perbandingan Kinerja

masukkan deskripsi gambar di sini

Grafik yang dihasilkan menggunakan perfplot . Daftar kode lengkap, untuk referensi Anda. Fungsi yang relevan tercantum di bawah ini.

Beberapa perbandingan ini tidak adil karena mereka mengambil keuntungan dari struktur data OP, tetapi ambil darinya apa yang Anda mau. Satu hal yang perlu diperhatikan adalah bahwa setiap fungsi pemahaman daftar lebih cepat atau sebanding dengan varian pandanya.

Fungsi

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

solusi apa pun untuk menghindari pengaturan dengan peringatan penyalinan:Try using .loc[row_indexer,col_indexer] = value instead
PV8

@ PV8 tidak yakin tentang kode Anda, tetapi periksa ini: stackoverflow.com/questions/20625582/…
cs95

Bagi siapa pun yang baru untuk REGEX seperti saya, \ D sama dengan [^ \ d] (apa pun yang bukan angka) dari sini . Jadi pada dasarnya kami mengganti semua non-digit dalam string dengan nol.
Rishi Latchmepersad

56

Saya akan menggunakan fungsi ganti panda, sangat sederhana dan kuat karena Anda dapat menggunakan regex. Di bawah ini saya menggunakan regex \ D untuk menghapus karakter non-digit tetapi jelas Anda bisa menjadi cukup kreatif dengan regex.

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

Saya mencoba ini, dan itu tidak berhasil. Saya bertanya-tanya apakah itu hanya berfungsi ketika Anda ingin mengganti seluruh string, bukan hanya mengganti bagian substring.
bgenchel

@bgenchel - Saya menggunakan metode ini untuk mengganti bagian dari string dalam pd.Series: df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix"). Ini akan mengonversi string seperti "my_prefixaaa" ke "new_prefixaaa".
jakub

apa yang r lakukan di to_replace = r '\ D'?
Luca Guarro

@LucaGuarro dari python docs: "Awalan r, membuat literal menjadi string mentah literal, diperlukan dalam contoh ini karena urutan melarikan diri dalam string string" matang "normal yang tidak dikenali oleh Python, sebagai lawan dari ekspresi reguler, sekarang menghasilkan DeprecationWarning dan akhirnya akan menjadi SyntaxError. "
Coder375

35

Dalam kasus tertentu di mana Anda tahu jumlah posisi yang ingin Anda hapus dari kolom dataframe, Anda bisa menggunakan pengindeksan string di dalam fungsi lambda untuk menyingkirkan bagian-bagian itu:

Karakter terakhir:

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

Dua karakter pertama:

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

Saya perlu memotong koordinat geo menjadi 8 karakter (termasuk (.), (-)) dan jika mereka kurang dari 8 saya harus memasukkan '0' pada akhirnya untuk membuat semua koordinat 8 karakter. Apa cara yang lebih sederhana untuk melakukannya?
Sitz Blogz

Saya tidak sepenuhnya memahami masalah Anda, tetapi Anda mungkin perlu mengubah fungsi lambda menjadi sesuatu seperti "{0: .8f}". Format (x)
prl900

Terima kasih banyak atas jawabannya. Dengan kata sederhana saya memiliki dataframe dengan koordinat geografis - lintang & bujur sebagai dua kolom. Panjang karakter lebih dari 8 karakter dan saya hanya menyimpan 8 karakter mulai dari yang pertama yang harus menyertakan (-) dan (.) Juga.
Sitz Blogz

18

Ada bug di sini: saat ini tidak dapat meneruskan argumen ke str.lstripdan str.rstrip:

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

EDIT: 2012-12-07 ini berfungsi sekarang di cabang 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

Metode yang sangat sederhana adalah menggunakan extractmetode untuk memilih semua digit. Cukup berikan ekspresi reguler '\d+'yang mengekstraksi sejumlah digit.

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

Saya sering menggunakan daftar pemahaman untuk jenis tugas ini karena mereka sering lebih cepat.

Mungkin ada perbedaan besar dalam kinerja antara berbagai metode untuk melakukan hal-hal seperti ini (yaitu memodifikasi setiap elemen seri dalam DataFrame). Seringkali pemahaman daftar bisa paling cepat - lihat kode lomba di bawah ini untuk tugas ini:

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

Misalkan DF Anda memiliki karakter tambahan di antara angka juga. Entri terakhir.

  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

Anda dapat mencoba str.replace untuk menghapus karakter tidak hanya dari awal dan akhir tetapi juga di antaranya.

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

Keluaran:

  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

Coba ini menggunakan ekspresi reguler:

import re
data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.