给定多个条件,如何简洁地替换列值?

时间:2019-10-09 17:22:16

标签: python pandas numpy

我正在尝试使用numpy.select替换列中的字符串值;如果字符串包含一个关键字,我需要将整个字符串替换为另一个关键字(有+-25个组合)。

df["new_col"] = np.select(
    condlist=[
        df["col"].str.contains("cat1", na=False, case=False),
        df["col"].str.contains("cat2", na=False, case=False),
        df["col"].str.contains("cat3", na=False, case=False),
        df["col"].str.contains("cat4", na=False, case=False),
        # ...
        df["col"].str.contains("cat25", na=False, case=False),
    ],
    choicelist=[
        "NEW_cat1",
        "NEW_cat2",
        "NEW_cat3",
        "NEW_cat4",
        # ...
        "NEW_cat25"
    ],
    default="DEFAULT_cat",
)

是否有更简洁的方法,还是应该在str.contains(...)内重复condlist 25次? numpy.select到底是正确的方式吗?

我假设这里可以使用dict,但看不出具体如何。

df["col"].map(d),其中d是具有{"cat1":"NEW_cat1"}之类的旧值和新值的字典,因为我无法对需要替换的确切值进行硬编码(并且这就是为什么我使用str.contains)。

3 个答案:

答案 0 :(得分:2)

应该能够使用str.extract,然后映射匹配项。

设置

import pandas as pd
import re

df = pd.DataFrame({'col': ['foo', 'foOBar', 'oRange', 'manGo', 'i LIKE PIZZA', 
                           'some sentence with foo', 'foo and PizzA']})

cat_list = ['foo', 'orange', 'pizza']  # all lower case
label_l = ['Label_foo', 'Label_orange', 'Label_pizza']

代码

patt = re.compile('('+'|'.join(cat_list)+')', re.IGNORECASE)

df['new_col'] = (df.col.str.extract(patt)[0]  # First label in str if multiple
                   .str.lower()
                   .map(dict(zip(cat_list, label_l)))
                   .fillna('DEFAULT_LABEL'))

                      col        new_col
0                     foo      Label_foo
1                  foOBar      Label_foo
2                  oRange   Label_orange
3                   manGo  DEFAULT_LABEL
4            i LIKE PIZZA    Label_pizza
5  some sentence with foo      Label_foo
6           foo and PizzA      Label_foo

如果有可能出现多个匹配项,并且我们需要实现一个层次结构,在该层次结构中,“比萨饼”的优先级应高于“ foo”,我们可以使用有序类别dtype添加更多步骤。

cat_list = ['pizza', 'orange', 'foo']  # ordered in priority
label_l = ['Label_pizza', 'Label_orange', 'Label_foo']

my_cat = pd.api.types.CategoricalDtype(categories=cat_list, ordered=True)

s = (df.col.str.extractall(patt)[0]
       .str.lower()
       .astype(my_cat))

df['new_col'] = (s.to_frame().groupby(level=0).min()[0]  # min gets priority
                  .map(dict(zip(cat_list, label_l))))
df['new_col'] = df['new_col'].astype(str).replace('nan', 'DEFAULT_LABEL')
#                      col        new_col
#0                     foo      Label_foo
#1                  foOBar      Label_foo
#2                  oRange   Label_orange
#3                   manGo  DEFAULT_LABEL
#4            i LIKE PIZZA    Label_pizza
#5  some sentence with foo      Label_foo
#6           foo and PizzA    Label_pizza

答案 1 :(得分:2)

作为condlistchoicelist参数传递的内容是普通的Python列表。列表内容可以通过使用列表理解(即语法[expression_using_item for item in sequence]

)以一种简洁的方式用该语言生成。

换句话说,您的代码可以写为:

df["new_col"] = np.select(
    condlist=[
       df["col"].str.contains(f"cat{i}", na=False, case=False) for i in range(1, 26)],        
    choicelist=[f"NEW_cat{i}" for i in range(1, 26)],
    default="DEFAULT_cat",
)

(如果类别名称不是数字序列,并且您在此处给出这些名称仅作为示例,则使用所有显式类别名称创建一个序列(列表),并插入该名称代替 上方摘录中的range()调用

答案 2 :(得分:2)

基于this answer to a similar questionthis one,一个简单的解决方案:

import pandas as pd
import string

# Preparing test data
test_cont = [f"cat_{i}" for i in string.ascii_lowercase]
test_rep = [f"cat_{i}" for i in range(27)]

kv = zip(test_cont, test_rep)

test_df_data = zip(range(27), test_cont)

test_df = pd.DataFrame(data=test_df_data, columns=["some_col", "str_vals"])


# The solution itself
for (cont, rep) in kv:
    cont_mask = test_df["str_vals"].str.contains(cont, na=False, case=False)
    test_df.loc[cont_mask, "str_vals"] = rep