列の文字列から不要な部分を削除するにはどうすればよいですか?
元の質問が投稿されてから6年後、pandasには、これらの文字列操作操作を簡潔に実行できる多数の「ベクトル化された」文字列関数が追加されました。
この回答では、これらの文字列関数のいくつかを探索し、より高速な代替案を提案し、最後にタイミング比較を行います。
一致する部分文字列/パターン、および置換する部分文字列を指定します。
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
保持したい部分文字列を抽出するのに便利です。
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
最初のキャプチャグループからキャプチャされたアイテムを含むシリーズを返します。
分割は、すべての文字列がこの一貫した構造に従っていることを前提として機能します。
# 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
アクセサベースのソリューションに満足している場合は、ここで終了できます。ただし、より高速でパフォーマンスの高い代替手段に関心がある場合は、読み続けてください。
最適化:リスト内包表記
状況によっては、パンダ文字列関数よりもリスト内包表記を優先する必要があります。その理由は、文字列関数は本質的にベクトル化が難しいためです(つまり、本当の意味で)。ほとんどの文字列関数と正規表現関数は、オーバーヘッドの多いループのラッパーにすぎません。
私の記事、パンダのforループは本当に悪いのですか?いつ気にすべきですか?、詳しく説明します。
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つは、すべてのリスト内包関数が、同等のパンダバリアントよりも高速または同等であることです。
関数
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']])