Rimuovere le parti indesiderate dalle stringhe in una colonna


129

Sto cercando un modo efficiente per rimuovere parti indesiderate dalle stringhe in una colonna DataFrame.

I dati sembrano:

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

Devo tagliare questi dati per:

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

Ho provato .str.lstrip('+-')e. str.rstrip('aAbBcC'), ma ho ricevuto un errore:

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

Qualsiasi suggerimento sarebbe molto apprezzato!

Risposte:


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

grazie! che funzioni. Sto ancora avvolgendo la mia mente su map (), non sono sicuro di quando usarlo o non usarlo ...
Yannan Wang

Mi ha fatto piacere vedere che questo metodo funziona anche con la funzione di sostituzione.
BKay,

@eumiro come si applica questo risultato se si scorre ogni colonna?
medev21

Posso usare questa funzione per sostituire un numero come il numero 12? Se faccio x.lstrip ('12 ') elimina tutti gli 1 e 2 secondi.
Dave,

76

Come rimuovo le parti indesiderate dalle stringhe in una colonna?

6 anni dopo la pubblicazione della domanda originale, Panda ora ha un buon numero di funzioni di stringa "vettorializzate" che possono eseguire in modo succinto queste operazioni di manipolazione delle stringhe.

Questa risposta esplorerà alcune di queste funzioni di stringa, suggerirà alternative più veloci e alla fine farà un confronto dei tempi.


.str.replace

Specificare la sottostringa / motivo da abbinare e la sottostringa con cui sostituirlo.

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

Se devi convertire il risultato in un numero intero, puoi utilizzare Series.astype,

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

df.dtypes
time      object
result     int64
dtype: object

Se non si desidera modificare dfsul posto, utilizzare DataFrame.assign:

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

.str.extract

Utile per l'estrazione delle sottostringhe che si desidera conservare.

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

Con extract, è necessario specificare almeno un gruppo di acquisizione. expand=Falserestituirà una serie con gli oggetti catturati dal primo gruppo di acquisizione.


.str.split e .str.get

La divisione delle opere presuppone che tutte le stringhe seguano questa struttura coerente.

# 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

Non raccomandare se stai cercando una soluzione generale.


Se sei soddisfatto delle soluzioni succinte e leggibili str basate su accessori di cui sopra, puoi fermarti qui. Tuttavia, se sei interessato a alternative più veloci e più performanti, continua a leggere.


Ottimizzazione: elenco delle comprensioni

In alcune circostanze, la comprensione dell'elenco dovrebbe essere favorita rispetto alle funzioni di stringa panda. Il motivo è che le funzioni di stringa sono intrinsecamente difficili da vettorializzare (nel vero senso della parola), quindi la maggior parte delle funzioni di stringa e regex sono solo avvolgenti attorno a loop con più sovraccarico.

Il mio articolo, I for-loop nei panda sono davvero cattivi? Quando dovrei preoccuparmi? , entra in maggior dettaglio.

L' str.replaceopzione può essere riscritta usandore.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

L' str.extractesempio può essere riscritto usando una comprensione dell'elenco con 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

Se NaN o nessuna corrispondenza sono possibili, dovrai riscrivere quanto sopra per includere un controllo degli errori. Lo faccio usando una funzione.

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

Possiamo anche riscrivere le risposte di @eumiro e @ MonkeyButter usando la comprensione dell'elenco:

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

E,

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

Si applicano le stesse regole per la gestione delle NaN, ecc.


Confronto delle prestazioni

inserisci qui la descrizione dell'immagine

Grafici generati usando perfplot . Elenco completo del codice, per riferimento. Le funzioni pertinenti sono elencate di seguito.

Alcuni di questi confronti sono ingiusti perché sfruttano la struttura dei dati di OP, ma ne traggono ciò che vuoi. Una cosa da notare è che ogni funzione di comprensione dell'elenco è più veloce o comparabile della sua variante di panda equivalente.

funzioni

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

qualsiasi soluzione alternativa per evitare l'impostazione con avviso:Try using .loc[row_indexer,col_indexer] = value instead
PV8

@ PV8 non sicuro circa il vostro codice, ma check this out: stackoverflow.com/questions/20625582/...
CS95

Per chiunque sia nuovo su REGEX come me, \ D è uguale a [^ \ d] (qualsiasi cosa che non sia una cifra) da qui . Quindi stiamo praticamente sostituendo tutte le non cifre nella stringa con niente.
Rishi Latchmepersad,

56

Vorrei usare la funzione di sostituzione dei panda, molto semplice e potente come puoi usare regex. Di seguito sto usando regex \ D per rimuovere tutti i caratteri non digitati ma ovviamente potresti diventare abbastanza creativo con regex.

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

Ho provato questo, e non funziona. Mi chiedo se funziona solo quando si desidera sostituire un'intera stringa anziché semplicemente sostituire una parte di sottostringa.
bgenchel,

@bgenchel - Ho usato questo metodo per sostituire una parte di una stringa in un pd.Series: df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix"). Questo convertirà una stringa come "my_prefixaaa" in "new_prefixaaa".
Jakub,

cosa fa la r in to_replace = r '\ D'?
Luca Guarro,

@LucaGuarro dai documenti di Python: "In questo esempio è necessario il prefisso r, che rende letterale una stringa non elaborata, perché in questo esempio le sequenze di escape in una stringa" cucinata "normale non riconosciute da Python, al contrario delle espressioni regolari, ora genera un DeprecationWarning e alla fine diventerà un SyntaxError. "
Coder375,

35

Nel caso particolare in cui si conosce il numero di posizioni che si desidera rimuovere dalla colonna del frame di dati, è possibile utilizzare l'indicizzazione di stringa all'interno di una funzione lambda per eliminare quelle parti:

Ultimo personaggio:

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

Primi due personaggi:

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

Ho bisogno di tagliare le coordinate geografiche a 8 caratteri (inclusi (.), (-)) e nel caso in cui siano inferiori a 8 devo inserire finalmente '0' per rendere tutte le coordinate 8 caratteri. Qual è il modo più semplice per farlo?
Sitz Blogz,

Non capisco perfettamente il tuo problema, ma potresti aver bisogno di cambiare la funzione lambda in qualcosa del tipo "{0: .8f}". Format (x)
prl900

Grazie mille per la risposta. In parole semplici ho un frame di dati con coordinate geografiche: latitudine e longitudine come due colonne. La lunghezza dei caratteri è superiore a 8 caratteri e sono rimasto solo 8 caratteri a partire dal primo che dovrebbe includere anche (-) e (.).
Sitz Blogz,

18

C'è un bug qui: al momento non è possibile passare argomenti a str.lstripe str.rstrip:

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

EDIT: 2012-12-07 funziona ora sul ramo 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

Un metodo molto semplice sarebbe quello di utilizzare il extractmetodo per selezionare tutte le cifre. Basta fornire l'espressione regolare '\d+'che estrae un numero qualsiasi di cifre.

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

Uso spesso la comprensione dell'elenco per questo tipo di attività perché sono spesso più veloci.

Ci possono essere grandi differenze nelle prestazioni tra i vari metodi per fare cose del genere (cioè modificando ogni elemento di una serie all'interno di un DataFrame). Spesso la comprensione di un elenco può essere più rapida. Per questa attività, vedere la corsa al codice di seguito:

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

Supponiamo che anche il tuo DF abbia quel carattere in più tra i numeri. L'ultima voce.

  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

Puoi provare str.replace per rimuovere i caratteri non solo dall'inizio e dalla fine, ma anche da una via di mezzo.

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

Produzione:

  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

Prova questo usando l'espressione regolare:

import re
data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.