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!
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
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'')
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:])
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.
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.
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']])
_
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
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
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