DataFrame列の文字列から不要な部分を削除する効率的な方法を探しています。
データは次のようになります。
time result
1 09:00 +52A
2 10:00 +62B
3 11:00 +44a
4 12:00 +30b
5 13:00 -110a
これらのデータを次のようにトリミングする必要があります。
time result
1 09:00 52
2 10:00 62
3 11:00 44
4 12:00 30
5 13:00 110
.str.lstrip('+-')
と.str.rstrip('aAbBcC')
を試しましたが、エラーが発生しました:
TypeError: wrapper() takes exactly 1 argument (2 given)
どんなポインタでも大歓迎です!
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
pandas置換関数を使用します。これは、正規表現を使用できるため、非常にシンプルで強力です。以下では、正規表現\ Dを使用して数字以外の文字を削除していますが、明らかに、正規表現を使用すると非常に創造的です。
data['result'].replace(regex=True,inplace=True,to_replace=r'\D',value=r'')
データフレーム列から削除する位置の数がわかっている特定のケースでは、ラムダ関数内で文字列インデックスを使用して、その部分を削除できます。
最後のキャラクター:
data['result'] = data['result'].map(lambda x: str(x)[:-1])
最初の2文字:
data['result'] = data['result'].map(lambda x: str(x)[2:])
ここにはバグがあります:現在、str.lstrip
とstr.rstrip
に引数を渡すことはできません:
http://github.com/pydata/pandas/issues/2411
編集:2012-12-07これはdevブランチで動作するようになりました:
In [8]: df['result'].str.lstrip('+-').str.rstrip('aAbBcC')
Out[8]:
1 52
2 62
3 44
4 30
5 110
Name: result
列の文字列から不要な部分を削除するにはどうすればよいですか?
元の質問が投稿されてから6年後、pandasには、これらの文字列操作操作を簡単に実行できる多数の「ベクトル化された」文字列関数があります。
この回答では、これらの文字列関数のいくつかを調べ、より高速な代替案を提案し、最後にタイミング比較を行います。
.str.replace
一致させるサブストリング/パターン、および置換するサブストリングを指定します。
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
結果を整数に変換する必要がある場合は、 Series.astype
を使用できます。
df['result'] = df['result'].str.replace(r'\D', '').astype(int)
df.dtypes
time object
result int64
dtype: object
df
をインプレースで変更したくない場合は、 DataFrame.assign
を使用します。
df2 = df.assign(result=df['result'].str.replace(r'\D', ''))
df
# Unchanged
.str.extract
保持したい部分文字列を抽出するのに便利です。
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
extract
では、少なくとも1つのキャプチャグループを指定する必要があります。 expand=False
は、最初のキャプチャグループからキャプチャされたアイテムを含むシリーズを返します。
.str.split
および.str.get
すべての文字列がこの一貫した構造に従うと仮定して、分割は機能します。
# 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
一般的なソリューションを探している場合はお勧めしません。
上記の簡潔で読みやすい
str
アクセサーベースのソリューションに満足したら、ここでやめることができます。ただし、より高速でパフォーマンスの高い選択肢に興味がある場合は、読み続けてください。
状況によっては、リスト内包表記はpandas文字列関数よりも優先されるべきです。その理由は、文字列関数は本質的にベクトル化するのが難しいため(Wordの真の意味)、ほとんどの文字列関数と正規表現関数は、オーバーヘッドの多いループのラッパーにすぎないためです。
私の記事、 pandasのループの場合-いつ気にする必要がありますか? 、詳細を説明します。
str.replace
オプションは、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
str.extract
の例は、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
NaNまたは不一致が考えられる場合は、エラーチェックを含めるために上記を書き直す必要があります。関数を使用してこれを行います。
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
リスト内包表記を使用して、@ eumiroと@MonkeyButterの回答を書き直すこともできます。
df['result'] = [x.lstrip('+-').rstrip('aAbBcC') for x in df['result']]
そして、
df['result'] = [x[1:-1] for x in df['result']]
NaNなどの処理には同じルールが適用されます。
perfplot を使用して生成されたグラフ。 完全なコードリスト、参照用 関連する関数を以下にリストします。
これらの比較のいくつかは、OPのデータの構造を利用するため不公平ですが、それからあなたがするものを取り入れます。注意すべきことの1つは、すべてのリスト内包関数が、同等のpandasバリアントよりも高速または同等であることです。
関数
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']])
非常に簡単な方法は、extract
メソッドを使用してすべての数字を選択することです。任意の桁数を抽出する正規表現'\d+'
を指定するだけです。
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
これらのタイプのタスクはより高速であることが多いため、リストの内包表記をよく使用します。
このようなことを行うためのさまざまな方法(つまり、DataFrame内のシリーズのすべての要素を変更する)では、パフォーマンスに大きな違いが生じる可能性があります。多くの場合、リストの理解は最速です。このタスクについては、以下のコードレースを参照してください。
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