Supprimer les parties indésirables des chaînes d'une colonne

129

Je recherche un moyen efficace de supprimer les parties indésirables des chaînes d'une colonne DataFrame.

Les données ressemblent à:

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

J'ai besoin de couper ces données pour:

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

J'ai essayé .str.lstrip('+-')et. str.rstrip('aAbBcC'), mais a obtenu une erreur:

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

Tous les pointeurs seraient grandement appréciés!

Yannan Wang
la source

Réponses:

167
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
eumiro
la source
THX! ça marche. Je tourne toujours mon esprit autour de la carte (), je ne sais pas quand l'utiliser ou ne pas l'utiliser ...
Yannan Wang
J'ai été ravi de voir que cette méthode fonctionne également avec la fonction de remplacement.
BKay
@eumiro comment appliquez-vous ce résultat si vous itérez chaque colonne?
medev21
Puis-je utiliser cette fonction pour remplacer un nombre tel que le nombre 12? Si je fais x.lstrip ('12 '), il supprime tous les 1 et 2.
Dave
76

Comment supprimer les parties indésirables des chaînes d'une colonne?

6 ans après la publication de la question initiale, les pandas ont maintenant un bon nombre de fonctions de chaîne "vectorisées" qui peuvent exécuter succinctement ces opérations de manipulation de chaînes.

Cette réponse explorera certaines de ces fonctions de chaîne, suggérera des alternatives plus rapides et passera à une comparaison des temps à la fin.


.str.replace

Spécifiez la sous-chaîne / modèle à faire correspondre et la sous-chaîne par laquelle le remplacer.

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

Si vous avez besoin que le résultat soit converti en entier, vous pouvez utiliser Series.astype,

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

df.dtypes
time      object
result     int64
dtype: object

Si vous ne souhaitez pas modifier dfsur place, utilisez DataFrame.assign:

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

.str.extract

Utile pour extraire la ou les sous-chaînes que vous souhaitez conserver.

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

Avec extract, il est nécessaire de spécifier au moins un groupe de capture. expand=Falserenverra une série avec les éléments capturés du premier groupe de capture.


.str.split et .str.get

Le fractionnement fonctionne en supposant que toutes vos chaînes suivent cette structure cohérente.

# 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

Ne pas recommander si vous recherchez une solution générale.


Si vous êtes satisfait des str solutions succinctes et lisibles basées sur les accesseurs ci-dessus, vous pouvez vous arrêter ici. Cependant, si vous êtes intéressé par des alternatives plus rapides et plus performantes, continuez à lire.


Optimisation: compréhension de listes

Dans certaines circonstances, les compréhensions de liste devraient être privilégiées par rapport aux fonctions de chaîne de pandas. La raison en est que les fonctions de chaîne sont intrinsèquement difficiles à vectoriser (dans le vrai sens du terme), de sorte que la plupart des fonctions de chaîne et de regex ne sont que des enveloppes autour des boucles avec plus de surcharge.

Mon article, les boucles for dans les pandas sont-elles vraiment mauvaises? Quand devrais-je m'en soucier? , entre plus en détail.

L' str.replaceoption peut être réécrite en utilisantre.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.extractexemple peut être réécrit en utilisant une compréhension de liste avec 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

Si des NaN ou des non-correspondances sont une possibilité, vous devrez réécrire ce qui précède pour inclure une vérification des erreurs. Je fais cela en utilisant une fonction.

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

Nous pouvons également réécrire les réponses de @ eumiro et @ MonkeyButter en utilisant des compréhensions de liste:

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

Et,

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

Les mêmes règles pour la gestion des NaN, etc. s'appliquent.


Comparaison des performances

entrez la description de l'image ici

Graphiques générés à l'aide de perfplot . Liste complète des codes, pour votre référence. Les fonctions pertinentes sont répertoriées ci-dessous.

Certaines de ces comparaisons sont injustes car elles tirent parti de la structure des données d'OP, mais en tirent ce que vous voulez. Une chose à noter est que chaque fonction de compréhension de liste est soit plus rapide, soit comparable à sa variante pandas équivalente.

Les fonctions

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']])
cs95
la source
toute solution de contournement pour éviter le réglage avec l'avertissement de copie:Try using .loc[row_indexer,col_indexer] = value instead
PV8
@ PV8 pas sûr de votre code, mais vérifiez ceci: stackoverflow.com/questions/20625582/…
cs95
Pour quiconque est nouveau dans REGEX comme moi, \ D est le même que [^ \ d] (tout ce qui n'est pas un chiffre) à partir d'ici . Nous remplaçons donc fondamentalement tous les non-chiffres de la chaîne par rien.
Rishi Latchmepersad
56

J'utiliserais la fonction de remplacement de pandas, très simple et puissante car vous pouvez utiliser regex. Ci-dessous, j'utilise le regex \ D pour supprimer tous les caractères non numériques, mais vous pouvez évidemment être assez créatif avec regex.

data['result'].replace(regex=True,inplace=True,to_replace=r'\D',value=r'')
Codeur375
la source
J'ai essayé ceci et cela ne fonctionne pas. Je me demande si cela ne fonctionne que lorsque vous souhaitez remplacer une chaîne entière au lieu de simplement remplacer une partie de sous-chaîne.
bgenchel
@bgenchel - J'ai utilisé cette méthode pour remplacer une partie d'une chaîne dans un pd.Series: df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix"). Cela convertira une chaîne comme "my_prefixaaa" en "new_prefixaaa".
jakub
que fait le r dans to_replace = r '\ D'?
Luca Guarro le
@LucaGuarro de la documentation python: "Le préfixe r, faisant du littéral un littéral de chaîne brute, est nécessaire dans cet exemple car les séquences d'échappement dans un littéral de chaîne" cuit "normal qui ne sont pas reconnues par Python, contrairement aux expressions régulières, maintenant entraînera un DeprecationWarning et deviendra éventuellement une SyntaxError. "
Coder375 le
35

Dans le cas particulier où vous connaissez le nombre de positions que vous souhaitez supprimer de la colonne dataframe, vous pouvez utiliser l'indexation de chaîne dans une fonction lambda pour vous débarrasser de ces parties:

Dernier caractère:

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

Deux premiers caractères:

data['result'] = data['result'].map(lambda x: str(x)[2:])
prl900
la source
Je dois couper les coordonnées géographiques à 8 caractères (y compris (.), (-)) et au cas où elles sont inférieures à 8, je dois insérer '0' enfin pour que toutes les coordonnées soient 8 caractères. Quelle est la manière la plus simple de le faire?
Sitz Blogz
Je ne comprends pas parfaitement votre problème, mais vous devrez peut-être changer la fonction lambda en quelque chose comme "{0: .8f}". Format (x)
prl900
Merci beaucoup pour la réponse. En termes simples, j'ai un dataframe avec des coordonnées géographiques - latitude et longitude en deux colonnes. La longueur des caractères est supérieure à 8 caractères et je n'ai gardé que 8 caractères en commençant par le premier qui devraient également inclure (-) et (.).
Sitz Blogz
18

Il y a un bug ici: actuellement impossible de passer des arguments à str.lstripet str.rstrip:

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

EDIT: 2012-12-07 cela fonctionne maintenant sur la branche dev:

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

Une méthode très simple consisterait à utiliser la extractméthode pour sélectionner tous les chiffres. Fournissez-lui simplement l'expression régulière '\d+'qui extrait n'importe quel nombre de chiffres.

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
Ted Petrou
la source
7

J'utilise souvent des listes de compréhension pour ces types de tâches car elles sont souvent plus rapides.

Il peut y avoir de grandes différences de performances entre les différentes méthodes pour faire des choses comme celle-ci (c'est-à-dire modifier chaque élément d'une série dans un DataFrame). Souvent, la compréhension d'une liste peut être la plus rapide - voir la course au code ci-dessous pour cette tâche:

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
tim654321
la source
4

Supposons que votre DF ait également ces caractères supplémentaires entre les nombres. La dernière entrée.

  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

Vous pouvez essayer str.replace pour supprimer des caractères non seulement du début et de la fin, mais également entre les deux.

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

Production:

  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
Rishi Bansal
la source
0

Essayez ceci en utilisant une expression régulière:

import re
data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
M. Prophète
la source