替换 pandas 数据框中的字符串

Substitute string in pandas dataframe

我有一个 pandas.DataFrame,其中包含说明酶是否表达的布尔规则。有些规则很简单(表达取决于 1 个基因),有些规则更复杂(表达取决于几个基因)

>>gprs.head()

Out[362]: 
        Rxn                             rule
0     13DAMPPOX      HGNC:549 or HGNC:550 or HGNC:80
6  24_25VITD2Hm      HGNC:2602
8     25VITD2Hm      HGNC:16354 or (HGNC:249 and HGNC:250) or (HGNC:249 and HGNC:251) or (HGNC:250 and HGNC:251) or HGNC:252 or HGNC:253 or HGNC:255 or HGNC:256

...

一个包含基因表达信息的字典对象:(1=expr, 0=not expr)

>>translation

'HGNC:80':1
'HGNC:2602':0
 etc...

我想将 'translation' 对象中包含的表达式信息替换到我的 'gprs' pandas.DataFrame 中。到目前为止我有:

for index, row in gprs.iterrows():
    row['rule']=row['rule'].replace(r'(', "")
    row['rule']=row['rule'].replace(r')', "")
    ruleGenes=re.split(" and | or ",(row['rule']))
    for gene in ruleGenes:
        if re.match("HGNC:HGNC:", gene):
            gene=gene[5:]
            try:
               gprs=gprs.replace(gene,translation[gene])
            except:
               print 'error in ', gene
        else:
            try:
                gprs=gprs.replace(gene,translation[gene])
            except:
                print 'error in ', gene

这仅在规则很简单(1 个元素)但对更复杂的规则无效时有效:

>>gprs.head()

0     13DAMPPOX  HGNC:549 or HGNC:550 or HGNC:80
6  24_25VITD2Hm                                0
7  24_25VITD3Hm  HGNC:16354 or (HGNC:249 and HGNC:250) or (HGNC:249 and HGNC:251) or (HGNC:250 and HGNC:251) or HGNC:252 or HGNC:253 or HGNC:255 or HGNC:256

最终我想用 max() 函数替换 'or',用 min() 函数替换 'and' 并评估布尔规则。

有什么建议吗?

编辑:

使用 EFT 代码时,当一个字符串是另一个字符串的子字符串时会出现问题,即:'HGNC:54' 和 'HGNC:549'

>>translation

'HGNC:54':0
'HGNC:549':1

结果:

>>gprs.head(1)

         Rxn                             rule                  translation 
0     13DAMPPOX       HGNC:549 or HGNC:550 or HGNC:80         09 or 1 or 0  

如何只替换整个字符串而不替换子字符串?

编辑编辑:

适用于:

for_eval = {k+'(?![0-9])' : str(v) for k, v in translation.items()}
gprs['translation'] = gprs['rule'].replace(for_eval, regex=True)

感谢 EFT 的建议

这是一种可用于计算酶是否表达的方法。

代码:

import re
RE_GENE_NAME = re.compile(r'(HGNC:[0-9]+)')

def calc_expressed(translation_table, rule_str):
    rule_expr = RE_GENE_NAME.sub(r'translation_table[""]', rule_str)
    return eval(rule_expr)

它是如何工作的?

这里的想法是采用如下规则:

HGNC:253 or HGNC:549

并将其更改为:

translation_table["HGNC:253"] or translation_table["HGNC:549"]

IE:HGNC:1234 等值的所有实例更改为 translation_table["HGNC:1234"]

这会产生一个字符串,它是一个合法的 python 表达式。结果表达式可以用 eval().

求值

测试代码:

translation = {
    'HGNC:80': 1,
    'HGNC:249': 1,
    'HGNC:250': 1,
    'HGNC:251': 0,
    'HGNC:252': 1,
    'HGNC:253': 0,
    'HGNC:255': 1,
    'HGNC:256': 1,
    'HGNC:549': 0,
    'HGNC:550': 1,
    'HGNC:2602': 0,
    'HGNC:16354': 1,
}

test_rules = (
    ('HGNC:550', 1),
    ('HGNC:2602', 0),
    ('HGNC:253 or HGNC:549', 0),
    ('HGNC:549 or HGNC:550 or HGNC:80', 1),
    ('HGNC:549 or (HGNC:550 and HGNC:2602)', 0),
    ('HGNC:549 or (HGNC:550 and HGNC:16354)', 1),
    ('HGNC:16354 or (HGNC:249 and HGNC:250) or (HGNC:249 and HGNC:251)', 1)
)

for rule, expected in test_rules:
    assert expected == calc_expressed(translation, rule)

输入翻译可以用

完成
>>>for_eval = {k+'(?![0-9])': str(v) for k, v in translation.items()}
>>>gprs['translation'] = gprs['rule'].replace(for_eval, regex=True)

解释:

第一行

>>>for_eval = {k+'(?![0-9])': str(v) for k, v in translation.items()}

01 分别替换为它们的字符串形式 '0''1',为将它们插入第二行的字符串做准备。将“(?![0-9])”添加到密钥会检查并忽略后面有更多数字的匹配项,从而避免仅匹配密钥的第一部分。

第二行

>>>gprs['translation'] = gprs['rule'].replace(for_eval, regex=True)

在 pandas 中将替换作为列操作执行,而不是在 python 中迭代每一行,对于较大的数据集,在这种情况下说 30 个或更多条目,速度要慢得多.

如果没有 regex=True,这将只适用于完全匹配,会出现与您在尝试实施更长规则时遇到的相同问题。

示例,感谢 u/Stephen Rauch 的测试用例:

In [3]:translation = {
    'HGNC:80': 1,
    'HGNC:249': 1,
    'HGNC:250': 1,
    'HGNC:251': 0,
    'HGNC:252': 1,
    'HGNC:253': 0,
    'HGNC:255': 1,
    'HGNC:256': 1,
    'HGNC:549': 0,
    'HGNC:550': 1,
    'HGNC:2602': 0,
    'HGNC:16354': 1,
}

In [4]:gprs = pd.DataFrame([
    ('HGNC:550', 1),
    ('HGNC:2602', 0),
    ('HGNC:253 or HGNC:549', 0),
    ('HGNC:549 or HGNC:550 or HGNC:80', 1),
    ('HGNC:549 or (HGNC:550 and HGNC:2602)', 0),
    ('HGNC:549 or (HGNC:550 and HGNC:16354)', 1),
    ('HGNC:16354 or (HGNC:249 and HGNC:250) or (HGNC:249 and HGNC:251)', 1)
], columns = ['rule', 'target'])

In [5]:for_eval = {k: str(v) for k, v in translation.items()}

In [6]:gprs['translation'] = gprs['rule'].replace(for_eval, regex=True)

In [7]:gprs['translation']

Out[7]:
0                              1
1                              0
2                         0 or 0
3                    0 or 1 or 1
4                 0 or (1 and 0)
5                 0 or (1 and 1)
6    1 or (1 and 1) or (1 and 0)
Name: translation, dtype: object

对于您稍后要看的第二部分,eval,如 u/Stephen Rauch 的回答中所述和详述,可用于评估生成的字符串中包含的表达式。为此,与使用 iterrows 相比,pd.Series.map 可用于对系列更快地应用逐元素操作。在这里,看起来像这样

In [10]:gprs['translation'].map(eval)
Out[10]: 
0    1
1    0
2    0
3    1
4    0
5    1
6    1
Name: translation, dtype: int64

或者,如果试图勉强获得最后一点性能,可以选择在输出上使用正则表达式模式匹配而不是映射。它变得更加依赖于你的规则是如何表达的,但是如果它们的格式都和你的 post 中的三个一样好,"and"s 都是成对的并且用括号括起来,没有嵌套,那么

# set any 'and' term with a zero in it to zero
>>>ands = gprs['translation'].str.replace('0 and \d|\d and 0', '0')
# if any ones remain, only 'or's and '1 and 1' statements are left
>>>ors = ands.replace('1', 1, regex=True)
# faster to force it to numeric than to search the remaining terms for zeros
>>>out = pd.to_numeric(ors, errors='coerce').fillna(0)
>>>out
0    1.0
1    0.0
2    0.0
3    1.0
4    0.0
5    1.0
6    1.0
Name: translation, dtype: float64

应该快五倍左右,使用 timeit 模块检查,超过几千行,盈亏平衡点大约为 60 或 70 个条目。