列の文字列から不要な部分を削除する


129

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)

どんなポインタでも大歓迎です!

回答:


167
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))

どうも!それは動作します。私はまだmap()に心を包んでいますが、いつ使用するか使用しないか
わかり

このメソッドが置換機能でも機能することを確認して、私は嬉しく思います。
BKay 2013年

@eumiro各列を反復する場合、この結果をどのように適用しますか?
medev21

この関数を使用して、12などの数字を置き換えることはできますか?x.lstrip('12 ')を実行すると、1と2がすべて削除されます。
Dave

76

列の文字列から不要な部分を削除するにはどうすればよいですか?

元の質問が投稿されてから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 アクセサベースのソリューションに満足している場合は、ここで終了できます。ただし、より高速でパフォーマンスの高い代替手段に関心がある場合は、読み続けてください。


最適化:リスト内包表記

状況によっては、パンダ文字列関数よりもリスト内包表記を優先する必要があります。その理由は、文字列関数は本質的にベクトル化が難しいためです(つまり、本当の意味で)。ほとんどの文字列関数と正規表現関数は、オーバーヘッドの多いループのラッパーにすぎません。

私の記事、パンダの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']])

settingwithcopywarningを避けるために、任意の回避策:Try using .loc[row_indexer,col_indexer] = value instead
PV8

@ないでください、あなたのコードについて、このチェックアウトPV8:stackoverflow.com/questions/20625582/...
cs95

私のようにREGEXを初めて使う人にとっては、\ Dは[^ \ d](数字ではないもの)と同じです。したがって、基本的には文字列内のすべての非数字を何も置き換えません。
Rishi Latchmepersad

56

正規表現を使用できるので、非常にシンプルで強力なパンダ置換機能を使用します。以下では、正規表現\ Dを使用して数字以外の文字を削除していますが、正規表現を使用するとかなりクリエイティブになる可能性があります。

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

私はこれを試してみましたが、うまくいきません。部分文字列の部分だけを置き換えるのではなく、文字列全体を置き換える場合にのみ機能するのかと思います。
bgenchel

@bgenchel-このメソッドを使用して、pd.Series:の文字列の一部を置き換えましたdf.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix")。これにより、「my_prefixaaa」などの文字列が「new_prefixaaa」に変換されます。
jakub 2017

rはto_replace = r '\ D'で何をしますか?
Luca Guarro

python docsの@LucaGuarro:「この例では、正規表現とは対照的に、Pythonで認識されない通常の「調理された」文字列リテラルのエスケープシーケンスが原因で、リテラルを生の文字列リテラルにするrプレフィックスが必要です。その結果、DeprecationWarningが発生し、最終的にSyntaxErrorになります。」
Coder375

35

データフレーム列から削除する位置の数がわかっている特定のケースでは、ラムダ関数内で文字列インデックスを使用して、その部分を取り除くことができます。

最後の文字:

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

最初の2文字:

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

地理座標を8文字((。)、(-)を含む)にトリミングする必要があります。8未満の場合は、すべての座標を8文字にするために最後に「0」を挿入する必要があります。そうするためのより簡単な方法は何ですか?
Sitz Blogz 2017

私はあなたの問題を完全に理解していませんが、ラムダ関数を「{0:.8f}」のようなものに変更する必要があるかもしれません。format(x)
prl900

返信ありがとうございます。簡単に言うと、地理座標(2つの列として緯度と経度)を持つデータフレームがあります。文字長が8文字を超えており、(-)と(。)も含めて、最初から8文字しか保持していません。
Sitz Blogz 2017年


11

非常に簡単な方法は、この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

7

これらのタイプのタスクは、多くの場合、より高速であるため、リスト内包表記を使用します。

このようなことを行うためのさまざまな方法(つまり、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

4

あなたのDFが数字の間にそれらの余分な文字も持っていると仮定します。最後のエントリー。

  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

str.replaceを使用して、文字を最初と最後だけでなく、それらの間からも削除できます。

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

出力:

  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

0

正規表現を使用してこれを試してください:

import re
data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.