在字符串中捕获数字并在Python中存储在数据框中

时间:2019-05-09 13:04:13

标签: python pandas numpy

我对python有点绿色,几个月来我一直在和熊猫和numpy玩弄。这是我在这里的第一篇文章,所以请告诉我我是否缺少什么。

我正在寻找从以分子列形式存储在数据框中的分子式中提取原子数的方法。字符串看起来像这样

C55H85N17O25S4

问题是,我当前的代码提取了一些很好的原子,例如C,H,N或O,但没有提取S(或Cl或Br),我不知道为什么。

我当前的代码如下:

import pandas as pd
import numpy as np

myfile = "whatever.csv"
data = pd.read_csv(myfile, sep='|', header=0)

#create the columns for atoms
atoms = ['C', 'H', 'O', 'N', 'Cl','S','Br']
for col in atoms:
    data[col] = np.nan

#parse molecular_formula for atoms using regex and add the number into the corresponding column
for col in atoms:
    data[col]= pd.np.where(data.molecular_formula.str.contains(col), data.molecular_formula.str.extract(re.escape(col) + r'(\d{1,})'), '0')

我知道,如果字符串中的字母后面没有数字,我将不会捕获数字,但会捕获到NaN,但是我可以接受。如果原子不包含在分子式中,我可以将NaN替换为“ 1”(不过,这样做可能是一种更优雅的方法)。

在此示例中,我当前的输出是:

molecular_formula   C       H       O       N       Cl      S      Br
C55H85N17O25S4      55      85      25      17      0       0      0

我想要:

molecular_formula   C       H       O       N       Cl      S      Br
C55H85N17O25S4      55      85      25      17      0       4      0

我认为问题出在我的str.extract()上,就像我将代码更改为

data[col]= pd.np.where(data.molecular_formula.str.contains(col), 1, 0)

我确实得到类似的东西:

molecular_formula   C       H       O       N       Cl      S      Br
C55H85N17O25S4      1       1       1       1       0       1      0

更新:我添加了一些额外的行来计算单个原子,该单个原子在分子式的末尾或在分子式的中部但不跟在其后时应计为“ 1”一个数字。

#When the single atom is at the end of the molecular formula:
data.loc[data.molecular_formula.str.contains(r'[C]$') == True, 'C'] = 1
data.loc[data.molecular_formula.str.contains(r'[H]$') == True, 'H'] = 1
data.loc[data.molecular_formula.str.contains(r'[S]$') == True, 'S'] = 1
data.loc[data.molecular_formula.str.contains(r'[O]$') == True, 'O'] = 1
data.loc[data.molecular_formula.str.contains(r'[N]$') == True, 'N'] = 1
data.loc[data.molecular_formula.str.contains(r'[C][l]$') == True, 'Cl'] = 1
data.loc[data.molecular_formula.str.contains(r'[N][a]$') == True, 'Na'] = 1
data.loc[data.molecular_formula.str.contains(r'[B][r]$') == True, 'Br'] = 1

#When the singe atom is somewhere inside the molecular formula:
data.loc[data.molecular_formula.str.contains(r'.*[C][l]\D') == True, 'Cl'] = 1
data.loc[data.molecular_formula.str.contains(r'.*[C]\D') == True, 'C'] = 1
data.loc[data.molecular_formula.str.contains(r'.*[B][r]\D') == True, 'Br'] = 1
data.loc[data.molecular_formula.str.contains(r'.*[N][a]\D') == True, 'Na'] = 1
data.loc[data.molecular_formula.str.contains(r'.*[N]\D') == True, 'N'] = 1
data.loc[data.molecular_formula.str.contains(r'.*[H]\D') == True, 'H'] = 1
data.loc[data.molecular_formula.str.contains(r'.*[S]\D') == True, 'S'] = 1
data.loc[data.molecular_formula.str.contains(r'.*[O]\D') == True, 'O'] = 1

#Convert the atom columns into int:
for col in atoms:
    data[col] = pd.to_numeric(data[col])

它又快又脏,我将不得不遍历这些内容,并使用惰性正则表达式来解决带有两个字母(如“ Br”或“ Na”)的原子问题。 但是这些行与@jxc的答案相结合,给出了我想要的输出。

2 个答案:

答案 0 :(得分:2)

如果您使用的是熊猫0.18.0+,则可以尝试extractall()检索所有atom + count组合,然后使用ivot()或unstack()在列中获取原子。在reindex()和fillna()之后得到丢失的原子:请参见下面的示例(在Pandas 0.23.4上测试):

更新:在Pandas 0.24+版本上,pd.pivot()函数产生 KeyError ,并且对该函数进行了一些更改使其与0.23.4版本不兼容。在新代码中改用unstack()

df = pd.DataFrame([('C55H85N17O25S4',),('C23H65',),(None,), (None,), ('C22H16ClN3OS2',)
         , ('C37H42Cl2N2O6',), ('C21H30BrNO4',), ('C11H13ClN2',), ('C34H53NaO8',), ('A0',)
    ],columns=['molecular_formula'])
#  molecular_formula
#0    C55H85N17O25S4
#1            C23H65
#2              None
#3              None
#4     C22H16ClN3OS2
#5     C37H42Cl2N2O6
#6       C21H30BrNO4
#7        C11H13ClN2
#8        C34H53NaO8
#9                A0

# list of concerned atoms 
atoms = ['C', 'H', 'O', 'N', 'Cl','S','Br']

# regeex pattern
atom_ptn = r'(?P<atom>' + r'|'.join(atoms) + r')(?P<cnt>\d+)'
print(atom_ptn)
#(?P<atom>C|H|O|N|Cl|S|Br)(?P<cnt>\d+)

# extract the combo of atom vs number and pivot them into desired table format 
df1 = df.molecular_formula.str.extractall(atom_ptn) \
        .reset_index(level=1, drop=True) \
        .set_index('atom', append=True) \
        .unstack(1)

# remove the level-0 from the column indexing
df1.columns = [ c[1] for c in df1.columns ]

# reindex df1 and join the result with the original df, then fillna() 
df.join(df1.reindex(columns=atoms)).fillna({c:0 for c in atoms}, downcast='infer')
#  molecular_formula   C   H   O   N Cl  S  Br
#0    C55H85N17O25S4  55  85  25  17  0  4   0
#1            C23H65  23  65   0   0  0  0   0
#2              None   0   0   0   0  0  0   0
#3              None   0   0   0   0  0  0   0
#4     C22H16ClN3OS2  22  16   0   3  0  2   0
#5     C37H42Cl2N2O6  37  42   6   2  2  0   0
#6       C21H30BrNO4  21  30   4   0  0  0   0
#7        C11H13ClN2  11  13   0   2  0  0   0
#8        C34H53NaO8  34  53   8   0  0  0   0
#9                A0   0   0   0   0  0  0   0

作为熊猫的 0.24.0 ,我们可以使用DataFrame.droplevel(),然后全部完成一个链:

df.join(df.molecular_formula.str.extractall(atom_ptn) 
          .droplevel(1)
          .set_index('atom', append=True) 
          .unstack(1) 
          .droplevel(0, axis=1) 
          .reindex(columns=atoms) 
   ).fillna({c:0 for c in atoms}, downcast='infer')

UPDATE-2(于5/13/2019):

每个注释中,编号缺失的原子应分配一个常量1。请参阅以下两个修改:

  1. 正则表达式:

    • cnt应该允许EMPTY字符串,因此:从(?P<cnt>\d+)(?P<cnt>\d*)
    • atom必须进行排序,以便在较短的字符串之前先测试较长的字符串,这很重要,因为正则表达式交替从左到右匹配子模式。这是为了确保 Cl C 之前已经过测试,否则 Cl 将永远不会被匹配。

      # sort the list of atoms based on their length
      atoms_sorted = [ i[0] for i in sorted([(k, len(k)) for k in atoms], key=lambda x: -x[1]) ]
      
      # the new pattern based on list of atoms_sorted and \d* on cnt
      atom_ptn = r'(?P<atom>' + r'|'.join(atoms_sorted) + r')(?P<cnt>\d*)'
      print(atom_ptn)
      #(?P<atom>Cl|Br|C|H|O|N|S)(?P<cnt>\d*)
      

    进行测试。您可以尝试使用df.molecular_formula.str.extractall(atom_ptn),方法是同时使用由排序列表和未排序列表创建的 atom_ptn

  2. fillna(1),用于匹配上述正则表达式模式中与0位数字匹配的所有原子,请参见下文:

    df.join(df.molecular_formula.str.extractall(atom_ptn)
              .fillna(1)
              .droplevel(1)
              .set_index('atom', append=True)
              .unstack(1)
              .droplevel(0, axis=1)
              .reindex(columns=atoms)
       ).fillna({c:0 for c in atoms}, downcast='infer')
    

答案 1 :(得分:1)

尝试一下:

import re

string = "C55H85N17O25S4"

ATOM_REGEX = r'([A-Z][a-z]*)(\d*)'

list_of_atoms = re.findall(ATOM_REGEX,string)
df = pd.DataFrame.from_records(list_of_atoms).T
df.index=["Elements","Coefficient"]

print(df)

输出: enter image description here

如果您的化学式是包含方括号或括号的字符串,请查看this parser