web-dev-qa-db-fra.com

Supprimer les parties non désirées des chaînes dans une colonne

Je recherche un moyen efficace de supprimer les parties non désirées 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 j'ai eu une erreur:

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

Tous les pointeurs seraient grandement appréciés!

91
Yannan Wang
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
128
eumiro

j'utiliserais la fonction pandas replace, très simple et puissante, car vous pouvez utiliser regex. Ci-dessous, je me sers de regex\D pour supprimer tous les caractères autres que des chiffres, mais il est évident que vous pourriez être assez créatif avec regex.

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

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 supprimer ces parties:

Dernier caractère:

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

Deux premiers personnages:

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

Comment puis-je supprimer les parties non désirées des chaînes dans une colonne?

Six ans après la publication de la question initiale, pandas dispose désormais d'un bon nombre de fonctions de chaîne "vectorisées" permettant d'effectuer ces opérations de manipulation de manière succincte.

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


.str.replace

Spécifiez la sous-chaîne/le motif à comparer et la sous-chaîne avec laquelle la 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 voulez convertir le résultat 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 df sur place, utilisez DataFrame.assign :

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

.str.extract

Utile pour extraire la (les) sous-chaîne (s) 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=False_ renverra une série avec les éléments capturés du premier groupe de capture.


.str.split et .str.get

Fractionner 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 le recommande pas si vous recherchez une solution générale.


Si vous êtes satisfait des solutions d’accessoires str succinctes et lisibles 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: liste des compréhensions

Dans certaines circonstances, la compréhension de la liste devrait être privilégiée par rapport à pandas fonctions de chaîne. La raison en est que les fonctions de chaîne sont par nature difficiles à vectoriser (au sens vrai du mot), de sorte que la plupart des fonctions de chaîne et d'expression régulière ne sont que des wrappers autour des boucles avec plus de surcharge.

Mon écriture, Pour les boucles avec pandas - Quand devrais-je m'en soucier? , va plus en détail.

L'option _str.replace_ peut être réécrite à l'aide de _re.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’exemple _str.extract_ peut être ré-écrit en utilisant une liste de compréhension 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 possibles, 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 les 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 de traitement des NaN, etc. s'appliquent.


Comparaison de performance

enter image description here

Graphes générés en utilisant perfplot . Liste complète du code, pour votre référence. Les fonctions correspondantes sont listées ci-dessous.

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

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']])
_
21
cs95

Il y a un bogue ici: pour l'instant, les arguments ne peuvent pas être passés à str.lstrip et 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
16
Wes McKinney

Une méthode très simple consisterait à utiliser la méthode extract pour sélectionner tous les chiffres. Il suffit de lui fournir l'expression régulière '\d+' qui extrait un nombre quelconque 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
10
Ted Petrou

J'utilise souvent la compréhension de liste 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 permettant de faire ce genre de choses (c'est-à-dire modifier chaque élément d'une série dans un DataFrame). Une compréhension de liste peut souvent être la plus rapide - voir code race 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
7
tim654321