Python正则表达式从具有各种结构的文件中提取数据

2024-05-16 07:24:17 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个包含许多行的文件,我想从中提取数据。结构与此类似

Detected 3 gas in sample. Composition :\r\n Very low Helium (1.5% total)\r\n Medium Oxygen (20% total)\r\n Low Nitrogen (6.5% total)\r\n
Detected 0 gas in sample. Composition :\r\n
Detected 2 gas in sample. Composition :\r\n Low Carbon monoxide (5% total)\r\n Very high Helium (80% total)\r\n Traces of Oxygen\r\n
Detected 1 gas in sample. Composition :\r\n Medium Nitrogen (18.5% total)\r\n Traces of Helium, Argon\r\n

我想使用正则表达式提取数据,以获得类似于此的数据数组(理想情况下是数据帧)

^{tb1}$

第一列是dataframe固有的。第二个可以从每行的第一句话中提取,也可以通过考虑已知成分百分比的气体数量轻松获得(因此可以忽略第一句话)。 我给出的示例总结了所有不同的线条结构:

  • 气体成分总和不等于100%(由于未检测到气体),并且可能 可以是整数或浮点数
  • 气体名称可以是一个或多个单词,但始终以大写字符开头
  • 气体比例以小文本、“非常低”等为特征,也可以是一个或多个单词,但始终以大写字母开头
  • 检测到但成分太低的气体列在末尾,以“痕量”开头,不算作“检测到”
  • 有时没有检测到气体
  • 气体检测用换行符分隔\r\n

此外,打开文件时,无法预先知道所有可能检测到的气体的列表,即,必须根据文件中的数据构建列。 我真的开始学习正则表达式,这可能是一个有点雄心勃勃的开始。 我试图在正则表达式中翻译类似“匹配所有序列,从大写开始,后跟任意数量的小写字符或介于(% total)之间的序列”,这通常会给我(忽略每行的第一句话)类似['Very low','Helium','1.5','Medium','Oxygen','20',...]的内容。但我真的很难把它翻译成regex,即使在regex101.com的帮助下,我也不确定事情是如何运作的

我真的很高兴能得到一些帮助和解释为什么你的解决方案有效


Tags: 文件数据samplein结构verytotalmedium
2条回答

使用无气体/观测文本静态列表的正则表达式的方法

  • 如果每行都被解析,那么解析文本就更简单了。有两种线结构
    1. 样本标题,其中提取了检测的
    2. 样本详细信息,包含气体名称气体文本百分比
import re

text = """Detected 3 gas in sample. Composition :\r\n Very low Helium (1.5% total)\r\n Medium Oxygen (20% total)\r\n Low Nitrogen (6.5% total)\r\n
Detected 0 gas in sample. Composition :\r\n
Detected 2 gas in sample. Composition :\r\n Low Carbon monoxide (5% total)\r\n Very high Helium (80% total)\r\n Traces of Oxygen\r\n
Detected 1 gas in sample. Composition :\r\n Medium Nitrogen (18.5% total)\r\n Traces of Helium, Argon\r\n"""

# keep all lines separate,  it's simpler to parse...
df = pd.DataFrame(re.split("\r\n\n?", text), columns=["text"]).replace("",np.nan).dropna()

# extract number of samples and assign a sample#
df = df.assign(main=df.text.str.contains("Detected"),
          sample=lambda dfa: dfa.main.cumsum(),
          detected=lambda dfa: np.where(dfa.main, dfa.text.str.extract(r'([0-9])', expand=False), np.nan),
         ).fillna(method="ffill")

# extract the gas, gas text, gas %age from each of the samples
# where gases are comma-separated generate list and explode()
df2 = (df.join(df.text.str.extract(r'(?P<txt>[V,M,L,T][a-z, ]*)(?P<gas>[A-Z,a-z \,]*)\(?(?P<pct>\d*\.?\d*)'))
       .assign(gas=lambda dfa: dfa.gas.str.strip().str.split(", "))
       .explode("gas")
      ).rename(columns={"pct":"%"})


# reshape structure of samples and name columns
df2 = df2.loc[~df2.main, ["sample","gas","txt","%"]].set_index(["sample","gas"]).unstack(1)
df2.columns= [f"{tup[1]} ({tup[0]})" for tup in df2.columns]

# finally pull it all together
df.loc[df.main, ["sample","detected"]].merge(df2, on="sample", how="left").replace(np.nan, "")

输出

^{tb1}$

这是一个非正则表达式的解决方案(但它依赖于字符串中的换行符保存为文件中的字符串,请参见Armanli的注释)。不需要正则表达式,因为字符串具有类似的结构。此解决方案循环文件中的行,在\\r\\n上拆分,并从列表中提取DetectedTraces或任何气体。它将值保存在可加载到熊猫中的DICT列表中:

import numpy as np
import pandas as pd

gasses = ['Helium', 'Oxygen', 'Nitrogen', 'Carbon monoxide', 'Argon']
def get_data(gas, line):
    return [line.split(f' {gas} (')[0].strip(), float(line.split(f' {gas} (')[1].split('%')[0])]    

all_data = []
with open("filename.txt", "r") as f:
    d = [i.split('\\r\\n') for i in f.readlines()]
    for i in d:
        tmp_dict = {}
        for z in i[:-1]:
            if 'Detected' in z:
                tmp_dict['Detected'] = int(z.split(" ")[1])
            elif 'Traces' in z:
                tr = z[10:].split(', ')
                for t in tr:
                    tmp_dict[f'{t.strip()} (txt)'] = 'Traces'
            else:
                gas = [ele for ele in gasses if(ele in z)] [0]
                r = get_data(gas, z)
                tmp_dict[f'{gas} (txt)'] = r[0]
                tmp_dict[f'{gas} (%)'] = r[1]               
        all_data.append(tmp_dict)
        
df = pd.DataFrame(all_data)

输出:

^{tb1}$

相关问题 更多 >