Entfernen Sie unerwünschte Teile von Zeichenfolgen in einer Spalte


129

Ich suche nach einer effizienten Möglichkeit, unerwünschte Teile aus Zeichenfolgen in einer DataFrame-Spalte zu entfernen.

Daten sehen aus wie:

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

Ich muss diese Daten kürzen, um:

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

Ich habe es versucht .str.lstrip('+-')und. str.rstrip('aAbBcC'), habe aber einen Fehler bekommen:

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

Alle Hinweise wäre sehr dankbar!

Antworten:


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

Vielen Dank! das funktioniert. Ich
wickle

Ich war erfreut zu sehen, dass diese Methode auch mit der Ersetzungsfunktion funktioniert.
BKay

@eumiro Wie wenden Sie dieses Ergebnis an, wenn Sie jede Spalte iterieren?
medev21

Kann ich diese Funktion verwenden, um eine Nummer wie die Nummer 12 zu ersetzen? Wenn ich x.lstrip ('12 ') mache, werden alle 1 und 2s entfernt.
Dave

75

Wie entferne ich unerwünschte Teile aus Zeichenfolgen in einer Spalte?

6 Jahre nachdem die ursprüngliche Frage veröffentlicht wurde, verfügt Pandas nun über eine gute Anzahl von "vektorisierten" Zeichenfolgenfunktionen, mit denen diese Zeichenfolgenmanipulationsoperationen kurz und bündig ausgeführt werden können.

In dieser Antwort werden einige dieser Zeichenfolgenfunktionen untersucht, schnellere Alternativen vorgeschlagen und am Ende ein Zeitvergleich durchgeführt.


.str.replace

Geben Sie den passenden Teilstring / das passende Muster und den zu ersetzenden Teilstring an.

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

Wenn Sie das Ergebnis in eine Ganzzahl konvertieren möchten, können Sie Folgendes verwenden Series.astype:

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

df.dtypes
time      object
result     int64
dtype: object

Wenn Sie nicht direkt ändern möchten df, verwenden Sie DataFrame.assign:

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

.str.extract

Nützlich zum Extrahieren der Teilzeichenfolge (n), die Sie behalten möchten.

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

Mit extractmuss mindestens eine Erfassungsgruppe angegeben werden. expand=Falsegibt eine Serie mit den erfassten Elementen aus der ersten Erfassungsgruppe zurück.


.str.split und .str.get

Das Teilen funktioniert unter der Annahme, dass alle Ihre Zeichenfolgen dieser konsistenten Struktur folgen.

# 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

Nicht empfehlen, wenn Sie nach einer allgemeinen Lösung suchen.


Wenn Sie mit den oben genannten prägnanten und lesbaren str Accessor-basierten Lösungen zufrieden sind , können Sie hier aufhören. Wenn Sie jedoch an schnelleren und leistungsfähigeren Alternativen interessiert sind, lesen Sie weiter.


Optimieren: Listenverständnisse

Unter bestimmten Umständen sollte das Listenverständnis den Pandas-String-Funktionen vorgezogen werden. Der Grund dafür ist, dass Zeichenfolgenfunktionen von Natur aus schwer zu vektorisieren sind (im wahrsten Sinne des Wortes), sodass die meisten Zeichenfolgen- und Regex-Funktionen nur Wrapper um Schleifen mit mehr Overhead sind.

Mein Artikel: Sind For-Loops bei Pandas wirklich schlecht? Wann sollte es mich interessieren? geht näher darauf ein.

Die str.replaceOption kann mit neu geschrieben werdenre.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

Das str.extractBeispiel kann unter Verwendung eines Listenverständnisses mit re.search, neu geschrieben werden .

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

Wenn NaNs oder Nichtübereinstimmungen möglich sind, müssen Sie die obigen Informationen neu schreiben, um eine Fehlerprüfung einzuschließen. Ich mache das mit einer Funktion.

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

Wir können die Antworten von @ eumiro und @ MonkeyButter auch mithilfe von Listenverständnissen neu schreiben:

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

Und,

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

Es gelten die gleichen Regeln für den Umgang mit NaNs usw.


Leistungsvergleich

Geben Sie hier die Bildbeschreibung ein

Mit Perfplot erzeugte Diagramme . Vollständige Codeliste als Referenz. Die relevanten Funktionen sind unten aufgeführt.

Einige dieser Vergleiche sind unfair, weil sie die Struktur der OP-Daten ausnutzen, aber daraus entnehmen, was Sie wollen. Zu beachten ist, dass jede Listenverständnisfunktion entweder schneller oder vergleichbar ist als die entsprechende Pandas-Variante.

Funktionen

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

Problemumgehung, um die Einstellung mit Try using .loc[row_indexer,col_indexer] = value instead
Kopierwarnung

@ PV8 nicht sicher über Ihren Code, aber überprüfen Sie dies aus: stackoverflow.com/questions/20625582/…
cs95

Für jeden, der wie ich neu bei REGEX ist, ist \ D dasselbe wie [^ \ d] (alles, was keine Ziffer ist) von hier . Wir ersetzen also im Grunde alle Nicht-Ziffern in der Zeichenfolge durch nichts.
Rishi Latchmepersad

56

Ich würde die Pandas-Ersetzungsfunktion verwenden, sehr einfach und leistungsstark, da Sie Regex verwenden können. Unten verwende ich den regulären Ausdruck \ D, um nichtstellige Zeichen zu entfernen, aber natürlich könnten Sie mit dem regulären Ausdruck ziemlich kreativ werden.

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

Ich habe es versucht und es funktioniert nicht. Ich frage mich, ob es nur funktioniert, wenn Sie eine ganze Zeichenfolge ersetzen möchten, anstatt nur einen Teil eines Teilstrings zu ersetzen.
Bgenchel

@bgenchel - Ich habe diese Methode verwendet, um einen Teil eines Strings in einer pd.Series zu ersetzen : df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix"). Dadurch wird eine Zeichenfolge wie "my_prefixaaa" in "new_prefixaaa" konvertiert.
Jakub

Was macht das r in to_replace = r '\ D'?
Luca Guarro

@LucaGuarro aus den Python-Dokumenten: "In diesem Beispiel wird das Präfix r benötigt, das das Literal zu einem rohen String-Literal macht, da Escape-Sequenzen in einem normalen" gekochten "String-Literal, die von Python nicht erkannt werden, im Gegensatz zu regulären Ausdrücken jetzt führen zu einer DeprecationWarning und werden schließlich zu einem SyntaxError. "
Coder375

35

In dem speziellen Fall, in dem Sie die Anzahl der Positionen kennen, die Sie aus der Datenrahmenspalte entfernen möchten, können Sie die Zeichenfolgenindizierung innerhalb einer Lambda-Funktion verwenden, um diese Teile zu entfernen:

Letzter Charakter:

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

Die ersten beiden Zeichen:

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

Ich muss die Geokoordinaten auf 8 Zeichen (einschließlich (.), (-)) zuschneiden und falls sie kleiner als 8 sind, muss ich endlich '0' einfügen, um alle Koordinaten zu 8 Zeichen zu machen. Was ist einfacher?
Sitz Blogz

Ich verstehe Ihr Problem nicht vollständig, aber Sie müssen möglicherweise die Lambda-Funktion in "{0: .8f}" ändern. Format (x)
prl900

Vielen Dank für die Antwort. In einfachen Worten, ich habe einen Datenrahmen mit Geokoordinaten - Breite und Länge als zwei Spalten. Die Zeichenlänge beträgt mehr als 8 Zeichen und ich habe von Anfang an nur 8 Zeichen beibehalten, die auch (-) und (.) Enthalten sollten.
Sitz Blogz

18

Hier gibt es einen Fehler: Derzeit können keine Argumente an str.lstripund übergeben werden str.rstrip:

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

EDIT: 2012-12-07 das funktioniert jetzt auf dem dev branch:

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

11

Eine sehr einfache Methode wäre die Verwendung der extractMethode zur Auswahl aller Ziffern. Geben Sie einfach den regulären Ausdruck ein, '\d+'der eine beliebige Anzahl von Ziffern extrahiert.

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

Ich verwende häufig Listenverständnisse für diese Art von Aufgaben, weil sie oft schneller sind.

Es kann große Leistungsunterschiede zwischen den verschiedenen Methoden geben, um solche Dinge zu tun (dh jedes Element einer Reihe innerhalb eines DataFrame zu ändern). Oft kann ein Listenverständnis am schnellsten sein - siehe Code Race unten für diese Aufgabe:

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

Angenommen, Ihr DF hat diese zusätzlichen Zeichen auch zwischen den Zahlen. Der letzte Eintrag.

  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

Sie können versuchen, str.replace zu verwenden, um Zeichen nicht nur von Anfang und Ende, sondern auch von dazwischen zu entfernen.

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

Ausgabe:

  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

Versuchen Sie dies mit einem regulären Ausdruck:

import re
data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.