我有一个带有一列字符串值的 pandas DataFrame。我需要根据部分字符串匹配选择行。
像这样的成语:
re.search(pattern, cell_in_question)
返回一个布尔值。我熟悉的语法,df[df['A'] == "hello world"]
但似乎无法找到对部分字符串匹配执行相同操作的方法,例如'hello'
.
根据 github issue #620,看起来您很快就可以执行以下操作:
df[df['A'].str.contains("hello")]
更新:矢量化字符串方法(即 Series.str)在 pandas 0.8.1 及更高版本中可用。
我在 ipython 笔记本的 macos 上使用 pandas 0.14.1。我尝试了上面建议的行:
df[df["A"].str.contains("Hello|Britain")]
并得到一个错误:
无法使用包含 NA / NaN 值的向量进行索引
但是当添加“==True”条件时它工作得很好,如下所示:
df[df['A'].str.contains("Hello|Britain")==True]
如何从 pandas DataFrame 中选择部分字符串?
这篇文章是为那些想要
isin
)...并且想更多地了解哪些方法应该优于其他方法。
(PS:我看过很多关于类似主题的问题,我认为把这个留在这里会很好。)
友好的免责声明,这篇文章很长。
# setup
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1
col
0 foo
1 foobar
2 bar
3 baz
str.contains
可用于执行子字符串搜索或基于正则表达式的搜索。搜索默认为基于正则表达式,除非您明确禁用它。
这是一个基于正则表达式的搜索示例,
# find rows in `df1` which contain "foo" followed by something
df1[df1['col'].str.contains(r'foo(?!$)')]
col
1 foobar
有时不需要正则表达式搜索,因此请指定regex=False
禁用它。
#select all rows containing "foo"
df1[df1['col'].str.contains('foo', regex=False)]
# same as df1[df1['col'].str.contains('foo')] but faster.
col
0 foo
1 foobar
性能方面,正则表达式搜索比子字符串搜索慢:
df2 = pd.concat([df1] * 1000, ignore_index=True)
%timeit df2[df2['col'].str.contains('foo')]
%timeit df2[df2['col'].str.contains('foo', regex=False)]
6.31 ms ± 126 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.8 ms ± 241 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
如果不需要,请避免使用基于正则表达式的搜索。
寻址ValueError
s
有时,对结果执行子字符串搜索和过滤会导致
ValueError: cannot index with vector containing NA / NaN values
这通常是因为对象列中的混合数据或 NaN,
s = pd.Series(['foo', 'foobar', np.nan, 'bar', 'baz', 123])
s.str.contains('foo|bar')
0 True
1 True
2 NaN
3 True
4 False
5 NaN
dtype: object
s[s.str.contains('foo|bar')]
# ---------------------------------------------------------------------------
# ValueError Traceback (most recent call last)
任何不是字符串的东西都不能应用字符串方法,所以结果是 NaN(自然)。在这种情况下,指定na=False
忽略非字符串数据,
s.str.contains('foo|bar', na=False)
0 True
1 True
2 False
3 True
4 False
5 False
dtype: bool
如何一次将其应用于多个列?
答案就在问题中。使用DataFrame.apply
:
# `axis=1` tells `apply` to apply the lambda function column-wise.
df.apply(lambda col: col.str.contains('foo|bar', na=False), axis=1)
A B
0 True True
1 True False
2 False True
3 True False
4 False False
5 False False
下面的所有解决方案都可以使用按列的apply
方法“应用”到多个列(这在我的书中是可以的,只要你没有太多的列)。
如果您有一个包含混合列的 DataFrame,并且只想选择对象/字符串列,请查看select_dtypes
.
这最容易通过使用正则表达式 OR 管道的正则表达式搜索来实现。
# Slightly modified example.
df4 = pd.DataFrame({'col': ['foo abc', 'foobar xyz', 'bar32', 'baz 45']})
df4
col
0 foo abc
1 foobar xyz
2 bar32
3 baz 45
df4[df4['col'].str.contains(r'foo|baz')]
col
0 foo abc
1 foobar xyz
3 baz 45
您还可以创建术语列表,然后加入它们:
terms = ['foo', 'baz']
df4[df4['col'].str.contains('|'.join(terms))]
col
0 foo abc
1 foobar xyz
3 baz 45
有时,明智的做法是避开您的术语,以防它们包含可以解释为正则表达式元字符的字符。如果您的条款包含以下任何字符...
. ^ $ * + ? { } [ ] \ | ( )
然后,您需要使用re.escape
来逃避它们:
import re
df4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]
col
0 foo abc
1 foobar xyz
3 baz 45
re.escape
具有转义特殊字符的效果,因此它们被逐字处理。
re.escape(r'.foo^')
# '\\.foo\\^'
默认情况下,子字符串搜索搜索指定的子字符串/模式,无论它是否是全字。为了只匹配完整的单词,我们需要在这里使用正则表达式——特别是,我们的模式需要指定单词边界(\b
)。
例如,
df3 = pd.DataFrame({'col': ['the sky is blue', 'bluejay by the window']})
df3
col
0 the sky is blue
1 bluejay by the window
现在考虑,
df3[df3['col'].str.contains('blue')]
col
0 the sky is blue
1 bluejay by the window
v/s
df3[df3['col'].str.contains(r'\bblue\b')]
col
0 the sky is blue
与上面类似,除了我们\b
在连接模式中添加了一个单词边界( )。
p = r'\b(?:{})\b'.format('|'.join(map(re.escape, terms)))
df4[df4['col'].str.contains(p)]
col
0 foo abc
3 baz 45
哪里p
像这样,
p
# '\\b(?:foo|baz)\\b'
因为你能!你应该!它们通常比字符串方法快一点,因为字符串方法很难向量化并且通常具有循环实现。
代替,
df1[df1['col'].str.contains('foo', regex=False)]
在列表组合中使用in
运算符,
df1[['foo' in x for x in df1['col']]]
col
0 foo abc
1 foobar
代替,
regex_pattern = r'foo(?!$)'
df1[df1['col'].str.contains(regex_pattern)]
在列表组合中使用re.compile
(缓存您的正则表达式)+ ,Pattern.search
p = re.compile(regex_pattern, flags=re.IGNORECASE)
df1[[bool(p.search(x)) for x in df1['col']]]
col
1 foobar
如果“col”有 NaN,那么代替
df1[df1['col'].str.contains(regex_pattern, na=False)]
利用,
def try_search(p, x):
try:
return bool(p.search(x))
except TypeError:
return False
p = re.compile(regex_pattern)
df1[[try_search(p, x) for x in df1['col']]]
col
1 foobar
np.char.find
, np.vectorize
, DataFrame.query
.除了str.contains
和列表推导之外,您还可以使用以下替代方法。
np.char.find
仅支持子字符串搜索(阅读:无正则表达式)。
df4[np.char.find(df4['col'].values.astype(str), 'foo') > -1]
col
0 foo abc
1 foobar xyz
np.vectorize
这是一个循环的包装器,但比大多数 pandasstr
方法的开销要小。
f = np.vectorize(lambda haystack, needle: needle in haystack)
f(df1['col'], 'foo')
# array([ True, True, False, False])
df1[f(df1['col'], 'foo')]
col
0 foo abc
1 foobar
可能的正则表达式解决方案:
regex_pattern = r'foo(?!$)'
p = re.compile(regex_pattern)
f = np.vectorize(lambda x: pd.notna(x) and bool(p.search(x)))
df1[f(df1['col'])]
col
1 foobar
DataFrame.query
通过 python 引擎支持字符串方法。这并没有提供明显的性能优势,但对于了解您是否需要动态生成查询仍然很有用。
df1.query('col.str.contains("foo")', engine='python')
col
0 foo
1 foobar
query
有关方法和eval
方法系列的更多信息,请参阅使用 pd.eval() 在 pandas 中进行动态表达式评估。
str.contains
,因为它的简单性和易于处理 NaN 和混合数据np.vectorize
df.query
如果有人想知道如何执行相关问题:“按部分字符串选择列”
利用:
df.filter(like='hello') # select columns which contain the word hello
并通过部分字符串匹配选择行,传递axis=0
给过滤器:
# selects rows which contain the word hello in their index label
df.filter(like='hello', axis=0)
快速说明:如果您想根据索引中包含的部分字符串进行选择,请尝试以下操作:
df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]
假设您有以下内容DataFrame
:
>>> df = pd.DataFrame([['hello', 'hello world'], ['abcd', 'defg']], columns=['a','b'])
>>> df
a b
0 hello hello world
1 abcd defg
您始终可以in
在 lambda 表达式中使用运算符来创建过滤器。
>>> df.apply(lambda x: x['a'] in x['b'], axis=1)
0 True
1 False
dtype: bool
这里的技巧是使用 中的axis=1
选项apply
将元素逐行传递给 lambda 函数,而不是逐列传递。
如果您需要对pandas 数据框列中的字符串进行不区分大小写的搜索:
df[df['A'].str.contains("hello", case=False)]
您可以尝试将它们视为字符串:
df[df['A'].astype(str).str.contains("Hello|Britain")]
假设我们在 dataframe 中有一个名为“ENTITY”的列df
。我们可以过滤我们的df
, 以获得整个数据框df
,其中“实体”列的行不包含“DM”,方法是使用如下掩码:
mask = df['ENTITY'].str.contains('DM')
df = df.loc[~(mask)].copy(deep=True)
这是我最终为部分字符串匹配所做的。如果有人有更有效的方法,请告诉我。
def stringSearchColumn_DataFrame(df, colName, regex):
newdf = DataFrame()
for idx, record in df[colName].iteritems():
if re.search(regex, record):
newdf = concat([df[df[colName] == record], newdf], ignore_index=True)
return newdf
一个更通用的示例 - 如果在字符串中查找单词的一部分或特定单词:
df = pd.DataFrame([('cat andhat', 1000.0), ('hat', 2000000.0), ('the small dog', 1000.0), ('fog', 330000.0),('pet', 330000.0)], columns=['col1', 'col2'])
句子或单词的特定部分:
searchfor = '.*cat.*hat.*|.*the.*dog.*'
创建显示受影响行的列(可以随时根据需要过滤掉)
df["TrueFalse"]=df['col1'].str.contains(searchfor, regex=True)
col1 col2 TrueFalse
0 cat andhat 1000.0 True
1 hat 2000000.0 False
2 the small dog 1000.0 True
3 fog 330000.0 False
4 pet 3 30000.0 False
对于带有特殊字符的字符串,使用 contains 效果不佳。虽然找到工作。
df[df['A'].str.find("hello") != -1]
也许您想在 Pandas 数据框的所有列中搜索一些文本,而不仅仅是在它们的子集中。在这种情况下,以下代码将有所帮助。
df[df.apply(lambda row: row.astype(str).str.contains('String To Find').any(), axis=1)]
警告。这种方法虽然很方便,但速度相对较慢。
我的 2c 价值:
我做了以下事情:
sale_method = pd.DataFrame(model_data['Sale Method'].str.upper())
sale_method['sale_classification'] = \
np.where(sale_method['Sale Method'].isin(['PRIVATE']),
'private',
np.where(sale_method['Sale Method']
.str.contains('AUCTION'),
'auction',
'other'
)
)
在此之前有一些答案可以完成所要求的功能,无论如何我想展示最普遍的方式:
df.filter(regex=".*STRING_YOU_LOOK_FOR.*")
通过这种方式,无论以何种方式编写,您都可以获得您要查找的列。
(显然,您必须为每种情况编写正确的正则表达式)
有点类似于@cs95 的答案,但在这里您不需要指定引擎:
df.query('A.str.contains("hello").values')