簡體   English   中英

Python 正則表達式從具有各種結構的文件中提取數據

[英]Python regex to extract data from files with various structures

我有一個包含許多行的文件,我想從中提取數據。 結構類似於這個

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

我想使用正則表達式提取數據以獲得與此類似的數據數組(理想情況下是 pandas 數據幀)

樣本 檢測到 氦氣 (txt) 氦氣 (%) 氧氣 (txt) 氧氣 (%) 氮 (txt) 氮 (%) 一氧化碳 (txt) 一氧化碳 (%) 氬氣 (txt) 氬氣 (%)
0 3 非常低 1.5 中等的 20 低的 6.5 - - - -
1 0 - - - - - - - - - -
2 2 很高 80 痕跡 - - - 低的 5 - -
3 1 痕跡 - - - 中等的 18.5 - - 痕跡 -

第一列是 pandas dataframe 所固有的。 第二個可以從每行的第一句中提取,也可以通過考慮已知百分比成分的氣體數量來輕松獲得(因此可以忽略第一句)。 我給出的例子總結了所有不同的線條結構:

  • 氣體成分總和不是 100%(由於未檢測到的氣體),可以是整數或浮點數,
  • 氣體名稱可以是一個或多個單詞,但總是以大寫字符開頭,
  • 氣體比例的特征是一個小文本,“非常低”等......,也可以是一個或多個單詞,但總是以大寫字符開頭,
  • 檢測到但成分過低的氣體以“Traces of”開頭列出,不計為“檢測到”,
  • 有時檢測不到氣體
  • 氣體檢測由換行符分隔\r\n

此外,在打開文件時並不預先知道所有可能檢測到的氣體的列表,即必須從文件中的數據構建列。 我真的開始學習正則表達式,這可能有點雄心勃勃。 我想要做的是在正則表達式中翻譯類似“匹配所有以大寫開頭的序列,后跟任意數量的小寫字符或(% total)之間的序列”,這通常會給我(忽略第一個每行的句子)類似於['Very low','Helium','1.5','Medium','Oxygen','20',...] 但我真的很難將它翻譯成正則表達式,即使在 regex101.com 的幫助下,我也不確定它是如何工作的。

對於您的解決方案為何有效,我將非常高興獲得一些幫助和解釋。

這是一個非正則表達式解決方案(但它依賴於將字符串內換行符保存為文件中的字符串,請參閱 Armanli 的評論)。 不需要正則表達式,因為字符串具有相似的結構。 此解決方案循環文件中的行,在\\r\\n上拆分,並從列表中提取DetectedTraces或任何氣體。 它將值保存在可以加載到 pandas 的字典列表中:

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)

Output:

檢測到 氦氣 (txt) 氦氣 (%) 氧氣 (txt) 氧氣 (%) 氮 (txt) 氮 (%) 一氧化碳 (txt) 一氧化碳 (%) 氬氣 (txt)
0 3 非常低 1.5 中等的 20 低的 6.5
1 0
2 2 很高 80 痕跡 低的 5
3 1 痕跡 中等的 18.5 痕跡

使用沒有 static 氣體列表/觀察文本的正則表達式的方法

  • 如果每行都被解析,則更容易解析文本。 有兩種線結構
    1. 樣本 header,其中dectected被提取
    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, "")

output

樣本 檢測到 氬氣 (txt) 一氧化碳 (txt) 氦氣 (txt) 氮 (txt) 氧氣 (txt) 氬氣 (%) 一氧化碳 (%) 氦氣 (%) 氮 (%) 氧氣 (%)
0 1 3 非常低 低的 中等的 1.5 6.5 20
1 2 0
2 3 2 低的 很高 的痕跡 5 80
3 4 1 的痕跡 的痕跡 中等的 18.5

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM