检查 Pandas DataFrame 单元格是否包含特定字符串

23
假设我有以下 Pandas DataFrame:
     a               b
0  NAN  BABA UN EQUITY
1  NAN            2018
2  NAN            2017
3  NAN            2016
4  NAN             NAN
5  NAN   700 HK EQUITY
6  NAN            2018
7  NAN            2017
8  NAN            2016
9  NAN             NAN

对于列 b 中的每个单元格,我想要检查它是否包含字符串 EQUITY。如果是,我想要用上一行中的前一个字符串替换列 a 中的单元格,直到出现一个值为 NAN 的行,并得到编辑后的 DataFrame 如下:

                a               b
0             NAN  BABA UN EQUITY
1  BABA UN EQUITY            2018
2  BABA UN EQUITY            2017
3  BABA UN EQUITY            2016
4             NAN             NAN
5             NAN   700 HK EQUITY
6   700 HK EQUITY            2018
7   700 HK EQUITY            2017
8   700 HK EQUITY            2016
9             NAN             NAN

我的实际DataFrame比上面的要大得多,但格式类似。我遇到了一个问题,无法弄清楚如何检查单元格是否包含EQUITY。看起来应该使用str.contains,但我不清楚如何做到这一点。

2个回答

35
import numpy as np
import pandas as pd

df = pd.DataFrame({'a': ['NAN', 'NAN', 'NAN', 'NAN', 'NAN', 'NAN', 'NAN', 'NAN', 'NAN', 'NAN'],
 'b': ['BABA UN EQUITY', '2018', '2017', '2016', 'NAN', '700 HK EQUITY', '2018', '2017', '2016', 'NAN']})

# Make sure that all NaN values are `np.nan` not `'NAN'` (strings)
df = df.replace('NAN', np.nan)
mask = df['b'].str.contains(r'EQUITY', na=True)
df.loc[mask, 'a'] = df['b']
df['a'] = df['a'].ffill()
df.loc[mask, 'a'] = np.nan
产出。
                a               b
0             NaN  BABA UN EQUITY
1  BABA UN EQUITY            2018
2  BABA UN EQUITY            2017
3  BABA UN EQUITY            2016
4             NaN             NaN
5             NaN   700 HK EQUITY
6   700 HK EQUITY            2018
7   700 HK EQUITY            2017
8   700 HK EQUITY            2016
9             NaN             NaN
上面稍微有一点棘手的是如何定义mask。请注意,str.contains返回一个Series,其中包含不仅是TrueFalse值,还包括NaN:
In [114]: df['b'].str.contains(r'EQUITY')
Out[114]: 
0     True
1    False
2    False
3    False
4      NaN
5     True
6    False
7    False
8    False
9      NaN
Name: b, dtype: object

str.contains(..., na=True)用于将NaN视为True

In [116]: df['b'].str.contains(r'EQUITY', na=True)
Out[116]: 
0     True
1    False
2    False
3    False
4     True
5     True
6    False
7    False
8    False
9     True
Name: b, dtype: bool
一旦你有了掩码(mask)的概念,接下来很简单:在mask为True的地方,将b中的值复制到a中:
df.loc[mask, 'a'] = df['b']

向前填充a中的NaN值:

df['a'] = df['a'].ffill()

如果 mask 为 True,则将 a 中的值替换为 NaN:

df.loc[mask, 'a'] = np.nan

2
你可以使用 na=True 参数来替代 != Falsestr.contains 中吗?(或根据需要使用 na=False。) - jpp
2
@jpp:您说得完全正确。我忘记了该参数的存在。 - unutbu
非常感谢您的详细解释! - turtle101
我在检查一个字符串是否包含一种或多种模式时遇到了问题。请看: pattern='wiring | media | elect | tape' v=pd.Series(['electricity fault'])s=v.str.contains(pattern, flags=re.IGNORECASE, regex=True) print(s) [Out] 0 False dtype: bool 为什么呢? 如果您能帮助我,我将不胜感激。 - pink.slash
我觉得我解决了。但是不确定为什么? 可能是模式中的空格导致的吗? 请看: pattern='wiring |media|elect|tape' v=pd.Series(['electricity fault']) s=v.str.contains(pattern, flags=re.IGNORECASE, regex=True) 输出 0 True - pink.slash
显示剩余2条评论

1

1. 列表推导式比 str.contains 更快

尽管它们是矢量化的,但 Pandas 字符串方法并没有进行优化,因此即使对于大型数据框架,回退到 Python 循环通常也要快得多。

例如,以下两个示例是等效的(然而,无论列的长度如何,列表推导式都大约快 3 倍):

msk1 = df['b'].str.contains(r'EQUITY', na=True)

msk = [s != s or 'EQUITY' in s for s in df['b'].tolist()]

all(msk == msk1)   # True

话虽如此,str.contains(可以说)更易读;除此之外,两个版本都执行得非常快,不太可能成为代码性能的瓶颈。

2. 对于多个字符串,请使用"|".join

要检查某一列的行中是否存在列表中的任何一个字符串,请使用|分隔符将它们连接起来,然后调用str.contains

lst = ['EQUITY', '16', '19', '20']
msk = df['b'].str.contains(r'|'.join(lst), na=True)

3. 可以使用 where()mask() 进行过滤

通过使用布尔掩码 msk 在列 b 中屏蔽某些值,可以达到最终期望的输出。第一步是使用 where() 删除不匹配的值(例如 OP 示例中的数字);然后使用 ffill() 传播值;最后使用 mask() 删除条件为 True 的值。

msk = df['b'].str.contains(r'EQUITY', na=True)
df['a'] = df['b'].where(msk).ffill().mask(msk)

transformation


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接