Bir sütundaki dizelerden istenmeyen parçaları kaldırın


129

Bir DataFrame sütunundaki dizelerden istenmeyen parçaları kaldırmanın etkili bir yolunu arıyorum.

Veriler şöyle görünür:

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

Bu verileri şu şekilde kırpmam gerekiyor:

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

Denedim .str.lstrip('+-')ve. str.rstrip('aAbBcC'), ancak bir hata var:

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

Herhangi bir işaretçi çok takdir edilecektir!

Yanıtlar:


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

Teşekkürler! bu işe yarıyor. Hala aklımı haritanın etrafına sarıyorum (), onu ne zaman kullanacağımdan veya kullanmayacağımdan emin değilim ...
Yannan Wang

Bu yöntemin değiştirme işlevi ile de çalıştığını görmek beni memnun etti.
BKay

@eumiro, her sütunu yinelerseniz bu sonucu nasıl uygularsınız?
medev21

Bu işlevi, 12 sayısı gibi bir sayıyı değiştirmek için kullanabilir miyim? X.lstrip ('12 ') yaparsam 1 ve 2'lerin hepsini çıkarır.
Dave

77

Bir sütundaki dizelerden istenmeyen parçaları nasıl kaldırırım?

Orijinal sorunun gönderilmesinden 6 yıl sonra, pandalar artık bu dizgi işleme işlemlerini kısa ve öz bir şekilde gerçekleştirebilen çok sayıda "vektörleştirilmiş" dizgi işlevine sahiptir.

Bu cevap, bu dizi işlevlerinden bazılarını keşfedecek, daha hızlı alternatifler önerecek ve sonunda bir zamanlama karşılaştırmasına girecektir.


.str.replace

Eşleşecek alt dizeyi / deseni ve bununla değiştirilecek alt dizeyi belirtin.

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

Sonucun bir tam sayıya dönüştürülmesine ihtiyacınız varsa, şunu kullanabilirsiniz Series.astype:

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

df.dtypes
time      object
result     int64
dtype: object

Yerinde değiştirmek istemiyorsanız df, şunu kullanın DataFrame.assign:

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

.str.extract

Saklamak istediğiniz alt dizeleri çıkarmak için kullanışlıdır.

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

İle extracten az bir yakalama grubu belirtmek gerekir. expand=Falseilk yakalama grubundan yakalanan öğelerle bir Seri döndürecektir.


.str.split ve .str.get

Bölme, tüm dizelerinizin bu tutarlı yapıyı izlediğini varsayarak çalışır.

# 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

Genel bir çözüm arıyorsanız tavsiye etmeyin.


str Yukarıdaki özlü ve okunabilir erişimci tabanlı çözümlerden memnunsanız, burada durabilirsiniz. Ancak, daha hızlı, daha performanslı alternatiflerle ilgileniyorsanız okumaya devam edin.


Optimize Etme: Anlaşmaları Listeleme

Bazı durumlarda, liste anlamaları pandaların dizgi işlevlerine tercih edilmelidir. Bunun nedeni, dize işlevlerinin doğal olarak vektörleştirilmesinin zor olmasıdır (kelimenin gerçek anlamıyla), bu nedenle çoğu dize ve düzenli ifade işlevi yalnızca daha fazla ek yüke sahip döngülerin etrafına sarılır.

Benim yazma-up, for-döngüler pandalar gerçekten kötü mü? Ne zaman umursamalıyım? , daha fazla ayrıntıya giriyor.

str.replaceSeçeneği kullanılarak yeniden yazılabilirre.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İçeren bir liste kavrayışa yazılı yeniden örnek olabilir 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'ler veya eşleşmeyenler bir olasılıksa, bazı hata kontrollerini dahil etmek için yukarıdakileri yeniden yazmanız gerekecektir. Bunu bir işlev kullanarak yapıyorum.

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

Ayrıca @ eumiro'nun ve @ MonkeyButter'ın yanıtlarını liste anlamalarını kullanarak yeniden yazabiliriz:

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

Ve,

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

NaN'leri vb. İşlemek için aynı kurallar geçerlidir.


Performans karşılaştırması

görüntü açıklamasını buraya girin

Performans grafiği kullanılarak oluşturulan grafikler . Referans için tam kod listesi. İlgili işlevler aşağıda listelenmiştir.

Bu karşılaştırmalardan bazıları haksızdır çünkü OP verilerinin yapısından yararlanırlar, ancak ondan ne isterseniz onu alırlar. Unutulmaması gereken bir nokta, her liste anlama işlevinin eşdeğer pandalar varyantından daha hızlı veya karşılaştırılabilir olmasıdır.

Fonksiyonlar

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

herhangi bir geçici çözüm settingwithcopywarning önlemek için:Try using .loc[row_indexer,col_indexer] = value instead
PV8

@
PV8

Benim gibi REGEX için yenilikler herkes için, \ D [^ \ d] (bir rakam olmayan herhangi bir şey) ile aynıdır buradan . Yani temelde dizedeki tüm rakam olmayanları hiçbir şeyle değiştiriyoruz.
Rishi Latchmepersad

56

Normal ifadeyi kullanabildiğiniz için çok basit ve güçlü olan panda değiştirme işlevini kullanırdım. Aşağıda, rakam olmayan karakterleri kaldırmak için \ D normal ifadesini kullanıyorum, ancak açıkçası normal ifadeyle oldukça yaratıcı olabilirsiniz.

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

Bunu denedim ve işe yaramıyor. Sadece bir alt dize parçasını değiştirmek yerine tüm bir dizeyi değiştirmek istediğinizde işe yarayıp yaramadığını merak ediyorum.
bgenchel

@bgenchel - Bir pd.Series bir dize parçası yerine bu yöntemi kullandı: df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix"). Bu, "my_prefixaaa" gibi bir dizeyi "new_prefixaaa" 'ya dönüştürür.
jakub

r, to_replace = r '\ D' içinde ne yapar?
Luca Guarro

Python belgelerinden @LucaGuarro: "Bu örnekte, değişmezi ham dizeyi değişmez yapan r öneki gereklidir, çünkü normal ifadelerin aksine Python tarafından tanınmayan normal bir" pişmiş "dize hazır değerindeki kaçış dizileri şimdi bir DeprecationWarning ile sonuçlanır ve sonunda bir SyntaxError haline gelir. "
Coder375

35

Veri çerçevesi sütunundan kaldırmak istediğiniz konumların sayısını bildiğiniz özel durumda, bu parçalardan kurtulmak için bir lambda işlevi içinde dize indekslemeyi kullanabilirsiniz:

Son karakter:

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

İlk iki karakter:

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

Coğrafi koordinatları 8 karaktere ((.), (-) dahil) kırpmam ve 8'den küçük olmaları durumunda tüm koordinatları 8 karakter yapmak için sonunda '0' eklemem gerekiyor. Bunu yapmanın daha basit yolu nedir?
Sitz Blogz

Sorununuzu tam olarak anlamıyorum, ancak lambda işlevini "{0: .8f}" gibi bir şeye değiştirmeniz gerekebilir. Format (x)
prl900

Yanıt için çok teşekkürler. Basit bir deyişle, coğrafi koordinatlara sahip veri çerçevem ​​var - enlem ve boylam iki sütun olarak. Karakter uzunluğu 8 karakterden fazla ve ilkinden başlayarak (-) ve (.) Da içermesi gereken sadece 8 karakter tuttum.
Sitz Blogz

18

Burada bir hata var: şu anda argümanları str.lstripve str.rstrip:

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

DÜZENLEME: 2012-12-07 bu artık geliştirme dalında çalışıyor:

In [8]: df['result'].str.lstrip('+-').str.rstrip('aAbBcC')
Out[8]: 
1     52
2     62
3     44
4     30
5    110
Name: result

11

Çok basit bir yöntem, extracttüm rakamları seçmek için yöntemi kullanmak olacaktır . '\d+'İstediğiniz sayıda basamağı çıkaran normal ifadeyi sağlamanız yeterlidir .

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

Bu tür görevler için genellikle liste anlamalarını kullanırım çünkü genellikle daha hızlıdırlar.

Bunun gibi şeyler yapmak için çeşitli yöntemler arasında performans açısından büyük farklılıklar olabilir (yani, bir Veri Çerçevesi içindeki bir serinin her öğesini değiştirmek). Genellikle bir listeyi anlama en hızlı olabilir - bu görev için aşağıdaki kod yarışına bakın:

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'nizin de sayılar arasında bu ekstra karaktere sahip olduğunu varsayalım.

  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 ile karakterleri sadece baştan ve sondan değil aynı zamanda arada da kaldırmayı deneyebilirsiniz.

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

Çıktı:

  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

Bunu normal ifadeyi kullanarak deneyin:

import re
data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.