2012-06-01 34 views
2

我需要從前列腺切除術最終診斷書寫的平面文件中提取格里森分數。這些分數總是有格里森這個詞,兩個數字加起來就是另一個數字。人類在二十多年中打入了這些。包括了空白和修飾符的各種約定。以下是我的Backus-Naur表單以及兩個示例記錄。僅用於前列腺切除術,我們正在查看上千例。Pyparsing:提取可變長度,可變內容,可變空白子串

我正在使用pyparsing,因爲我正在學習python,並沒有我非常有限的暴露於正則表達式寫作的美好回憶。

我的問題:如何在不解析可能會或可能不會在這些最終診斷中的每一個其他可選數據的情況下挑出這些格里森成績?

num = Word(nums) 
record ::= accessionDate + accessionNumber + patMedicalRecordNum + finalDxText 
accessionDate ::= num + "/" + num + "/" num 
accessionNumber ::= "S" + num + "-" + num 
patMedicalRecordNum ::= num + "/" + num + "-" + num + "-" + num 
finalDxText ::= listOfParts + optionalComment + optionalpTNMStage 
listOfParts ::= OneOrMore(part) 
part ::= <multiline idiosyncratic freetext which may contain a Gleason score I want> + optionalpTNMStage 
optionalComment ::= <multiline idiosyncratic freetext which may contain a Gleason score I don't want> 
optionalpTNMStage ::= <multiline idiosyncratic freetext which may contain a Gleason score I don't want> 


01/01/11 S11-55555 20/444-55-6666 A. PROSTATE AND SEMINAL VESICLES, PROSTATECTOMY:       
            - ADENOCARCINOMA.              

            TOTAL GLEASON SCORE: GLEASON 5+4=9          
            TUMOR LOCATION: BILATERAL            
            TUMOR QUANTITATION: 15% OF PROSTATE INVOLVED BY TUMOR     
            EXTRAPROSTATIC EXTENSION: PRESENT AT RIGHT POSTERIOR     
            SEMINAL VESICLE INVASION: PRESENT          
            MARGINS: UNINVOLVED              
            LYMPHOVASCULAR INVASION: PRESENT          
            PERINEURAL INVASION: PRESENT           
            LYMPH NODES (SPECIMENS B AND C):           
             NUMBER EXAMINED: 25             
             NUMBER INVOLVED: 1             
             DIAMETER OF LARGEST METASTASIS: 1.7 mm        
            ADDITIONAL FINDINGS: HIGH-GRADE PROSTATIC INTRAEPITHELIAL NEOPLASIA, 
             ACUTE AND CHRONIC INFLAMMATION, INTRADUCTAL EXTENSION OF INVASIVE  
             CARCINOMA                

            PATHOLOGIC STAGE: pT3b N1 MX           

           B. LYMPH NODES, RIGHT PELVIC, EXCISION:          
            - ONE OF SEVENTEEN LYMPH NODES POSITIVE FOR METASTASIS (1/17).   

           C. LYMPH NODES, LEFT PELVIC, EXCISION:          
            - EIGHT LYMPH NODES NEGATIVE FOR METASTASIS (0/8).      
01/02/11 S11-4444 20/111-22-3333 PROSTATE AND SEMINAL VESICLES, PROSTATECTOMY:        
            - ADENOCARCINOMA.               
            GLEASON SCORE: 3 + 3 = 6 WITH TERTIARY PATTERN OF 5.            
            TUMOR QUANTITATION: APPROXIMATELY 10% BY VOLUME.      
            TUMOR LOCATION: BILATERAL.            
            EXTRAPROSTATIC EXTENSION: NOT IDENTIFIED.        
            MARGINS: NEGATIVE.              
            PERINEURAL INVASION: IDENTIFIED.          
            LYMPH-VASCULAR INVASION: NOT IDENTIFIED.        
            SEMINAL VESICLE/VASA DEFERENTIA INVASION: NOT IDENTIFIED.    
            LYMPH NODES: NONE SUBMITTED.           
            OTHER: HIGH GRADE PROSTATIC INTRAEPITHELIAL NEOPLASIA.     
           PATHOLOGIC STAGE (pTNM): pT2c NX.          

完全披露:我是一位正在做研究的醫師;這是我第一次使用python進行真正的工作。我已經讀過Lutz的Learning Python,Shaw的Python學習方法,並且通過各種問題集。我在這個論壇上討論了許多與pyparsing相關的問題,pyparsing wiki,並且我購買並閱讀了McGuire先生的Pyparsing入門。當我真的被告知我正站在「當你必須寫解析器時非常普遍的挫折的死亡螺旋」(McGuire,17)時,我可能會問一個問題:我不知道。到目前爲止,我只是很高興能夠開展真正的項目。

+0

自然語言處理是很難!你能做一些簡化的假設嗎? (例如,你關心的分數始終是_first_格里森分數,並且總是以格里森i + j = k'的形式出現) – katrielalex

+0

是的,那些是有效的假設。 – Niels

回答

2

這裏是拉出病人數據和任何匹配格里森數據的樣本。

from pyparsing import * 
num = Word(nums) 
accessionDate = Combine(num + "/" + num + "/" + num)("accDate") 
accessionNumber = Combine("S" + num + "-" + num)("accNum") 
patMedicalRecordNum = Combine(num + "/" + num + "-" + num + "-" + num)("patientNum") 
gleason = Group("GLEASON" + Optional("SCORE:") + num("left") + "+" + num("right") + "=" + num("total")) 
assert 'GLEASON 5+4=9' == gleason 
assert 'GLEASON SCORE: 3 + 3 = 6' == gleason 

patientData = Group(accessionDate + accessionNumber + patMedicalRecordNum) 
assert '01/02/11 S11-4444 20/111-22-3333' == patientData 

partMatch = patientData("patientData") | gleason("gleason") 

lastPatientData = None 
for match in partMatch.searchString(data): 
    if match.patientData: 
     lastPatientData = match 
    elif match.gleason: 
     if lastPatientData is None: 
      print "bad!" 
      continue 
     print "{0.accDate}: {0.accNum} {0.patientNum} Gleason({1.left}+{1.right}={1.total})".format(
         lastPatientData.patientData, match.gleason 
         ) 

打印:

01/01/11: S11-55555 20/444-55-6666 Gleason(5+4=9) 
01/02/11: S11-4444 20/111-22-3333 Gleason(3+3=6) 
+0

哦,這很令人興奮!謝謝,保羅!我無法想象這樣做沒有pyparsing。 BNF提示本身就值得你的書價格! – Niels

+0

@Niels - 感謝您的反饋,我很高興這本書對您有所幫助! – PaulMcG

2

查看pyparsing中的SkipTo parse元素。如果您爲num + num = num部分定義了一個pyparsing結構,那麼您應該能夠使用SkipTo跳過「Gleason」和那個之間的任何內容。大致是這樣的(未經測試pseuo-pyparsing):

score = num + "+" + num + "=" num 
Gleason = "Gleason" + SkipTo(score) + score 

PyParsing默認跳過空白,無論如何,與SkipTo你可以跳過任何不符合您的需要的格式。

+0

謝謝!我想我留下的問題的一大部分是,我確實希望將這個大塊的freetext作爲一個單獨的數據庫對象來捕獲。不知道如何抓住這一點。此外,這有點meta,但我什麼時候應該點擊「接受答案」的複選標記?我絕對沒有運行代碼...... – Niels

+0

你的意思是你想捕捉自由文本作爲一件事,並且還捕獲格里森得分嗎?如果是這樣的話,我建議你首先嚐試捕獲freetext,然後運行一個單獨的解析步驟來提取格里森。如果微格里森分數是你特別想從這個巨大的文本塊中唯一想要的東西,這將會更容易。當然,如何解析自由文本取決於它的格式(例如,您需要知道如何確定該部分何時開始和結束)。 – BrenBarn

+0

是的。理想情況下,我有一個數據庫行,包括assessionNumber,patMedicalRecordNum,finalDxText,Gleason,pTNMStage(應該只有一個每個案例,但他們把它放在不同的地方),以及項目進展時可能的其他事情(例如,我們可能會在將來使用不同的分級系統進行乳腺癌)。所以我認爲把finalDxText作爲一個單元是有道理的,然後在我們去的時候弄清楚如何挖掘各種事情只是學習的問題。 – Niels

1
gleason = re.compile("gleason\d+\d=\d") 
scores = set() 
for record in records: 
    for line in record.lower().split("\n"): 
     if "gleason" in line: 
      scores.add(gleason.match(line.replace(" ", "")).group(0)[7:]) 

還是什麼

+0

謝謝!這看起來像直蟒,不需要pyparsing。你認爲這將是一個更好的方式來獲得整個finalDxText嗎? – Niels

+0

這取決於!解析是一個很大的槍支。這是一個非常強大的工具,但經常是矯枉過正的(例如,如果你只是在尋找一個'gleason i + j = k'形式的子字符串)。這是一個值判斷你寫了多少代碼,然後再決定你真的想要解析輸入並使用完整的解析器。 – katrielalex