Анализ вывода программы на основе блоков с использованием Python

Я пытаюсь проанализировать вывод статистической программы (Mplus) с помощью Python.

Формат вывода (пример здесь) структурирован по блокам, подблокам, столбцам и т. Д., Где пробелы и разрывы очень важны. В зависимости от, например. параметры запрашивали, чтобы вы получили дополнительный (под) блок или столбец здесь или там.

Подход к этому с использованием регулярных выражений был PITA и полностью неподдерживаемым. Я рассматривал парсеры как более надежное решение, но

  1. я немного перегружен всеми возможными инструментами и подходами;
  2. создается впечатление, что они не подходят для такого рода вывода.

Например. LEPL имеет так называемый синтаксический анализ строк, который, кажется, идет в правильном направлении ( пробелы, блоки, ...), но по-прежнему ориентирован на синтаксис программирования, а не на вывод.

Предложение, в каком направлении смотреть, будет оценено.


person mhermans    schedule 20.08.2010    source источник


Ответы (5)


Да, это больно разбирать. Однако на самом деле вам не нужно очень много регулярных выражений. Обычного split может быть достаточно, чтобы разбить этот документ на управляемые последовательности строк.

Это много того, что я называю блоками текста "голова-тело". У вас есть заголовки, строка "-", а затем данные.

Что вы хотите сделать, так это свернуть структуру «голова-тело» в функцию генератора, которая генерирует отдельные словари.

def get_means_intecepts_thresholds( source_iter ):
    """Precondition: Current line is a "MEANS/INTERCEPTS/THRESHOLDS" line"""
    head= source_iter.next().strip().split()
    junk= source_iter.next().strip()
    assert set( junk ) == set( [' ','-'] )
    for line in source_iter:
        if len(line.strip()) == 0: continue
        if line.strip() == "SLOPES": break
        raw_data= line.strip().split()
        data = dict( zip( head, map( float, raw_data[1:] ) ) )
        yield int(raw_data[0]), data 

def get_slopes( source_iter ):
    """Precondition: Current line is a "SLOPES" line"""
    head= source_iter.next().strip().split()
    junk= source_iter.next().strip()
    assert set( junk ) == set( [' ','-'] )
    for line in source_iter:
        if len(line.strip()) == 0: continue
        if line.strip() == "SLOPES": break
        raw_data= line.strip().split() )
        data = dict( zip( head, map( float, raw_data[1:] ) ) )
        yield raw_data[0], data

Дело в том, чтобы израсходовать голову и мусор за один набор операций.

Затем используйте следующие строки данных, используя другой набор операций.

Поскольку это генераторы, вы можете комбинировать их с другими операциями.

def get_estimated_sample_statistics( source_iter ):
    """Precondition: at the ESTIMATED SAMPLE STATISTICS line"""
    for line in source_iter:
        if len(line.strip()) == 0: continue
    assert line.strip() == "MEANS/INTERCEPTS/THRESHOLDS"
    for data in get_means_intercepts_thresholds( source_iter ):
        yield data
    while True:
        if len(line.strip()) == 0: continue
        if line.strip() != "SLOPES": break
        for data in get_slopes( source_iter ): 
            yield data

Что-то вроде этого может быть лучше, чем регулярные выражения.

person S.Lott    schedule 20.08.2010

Основываясь на вашем примере, у вас есть набор различных вложенных подформатов, которые по отдельности очень легко анализируются. Что может быть ошеломляющим, так это огромное количество форматов и тот факт, что они могут быть вложены по-разному.

На самом низком уровне у вас есть набор значений, разделенных пробелами, в одной строке. Эти линии объединяются в блоки, и то, как блоки объединяются и вкладываются друг в друга, является сложной частью. Этот тип вывода предназначен для чтения человеком и никогда не предназначался для «восстановления» в машиночитаемой форме.

Во-первых, я хотел бы связаться с автором программного обеспечения и узнать, доступен ли альтернативный выходной формат, такой как XML или CSV. Если все сделано правильно (то есть не только в формате печати, обернутом в неуклюжий XML или с запятыми, заменяющими пробелы), с этим будет намного проще справиться. Если это не удастся, я попытаюсь составить иерархический список форматов и способов их размещения. Например,

  1. ESTIMATED SAMPLE STATISTICS начинает блок
  2. Внутри этого блока MEANS/INTERCEPTS/THRESHOLDS начинается вложенный блок
  3. Следующие две строки представляют собой заголовки столбцов.
  4. За ней следует одна (или несколько?) Строк данных с заголовком строки и значениями данных.

И так далее. Если вы подойдете к каждой из этих проблем по отдельности, вы обнаружите, что это утомительно, но не сложно. Думайте о каждом из вышеперечисленных шагов как о модулях, которые проверяют ввод, чтобы увидеть, совпадает ли он, и если он совпадает, затем вызывают другие модули для дальнейшего тестирования вещей, которые могут произойти «внутри» блока, с возвратом, если вы дойдете до чего-то, что не соответствует ' t соответствует тому, что вы ожидаете (кстати, это называется «рекурсивный спуск»).

Обратите внимание, что вам все равно придется сделать что-то подобное, чтобы создать версию данных в памяти («модель данных»), с которой вы можете работать.

person Jim Garrison    schedule 20.08.2010

Я предлагаю сделать грубое массирование линий до более удобной формы. Вот несколько экспериментов с вашими данными:

from __future__ import print_function
from itertools import groupby
import string
counter = 0

statslist = [ statsblocks.split('\n')
            for statsblocks in  open('mlab.txt').read().split('\n\n')
            ]
print(len(statslist), 'blocks')

def blockcounter(line):
    global counter
    if not line[0]:
        counter += 1
    return counter

blocklist = [ [block, list(stats)] for block, stats in groupby(statslist, blockcounter)]

for blockno,block in enumerate(blocklist):
    print(120 * '=')
    for itemno,line in enumerate(block[1:][0]):
        if len(line)<4 and any(line[-1].endswith(c) for c in string.letters) :
            print('\n** DATA %i, HEADER (%r)**' % (blockno,line[-1]))
        else:
            print('\n** DATA %i, item %i, length %i **' % (blockno, itemno, len(line)))
        for ind,subdata in enumerate(line):
            if '___' in subdata:
                print(' *** Numeric data starts: ***')
            else:
                if 6 < len(subdata)<16:
                    print( '** TYPE: %s **' % subdata)
                print('%3i : %s' %( ind, subdata))
person Tony Veijalainen    schedule 20.08.2010

Вы можете попробовать PyParsing. Это позволяет вам написать грамматику для того, что вы хотите проанализировать. У него есть другие примеры, кроме синтаксического анализа языков программирования. Но я согласен с Джимом Гаррисоном в том, что в вашем случае, похоже, не нужен настоящий синтаксический анализатор, потому что написание грамматики было бы громоздким. Я бы попробовал решение грубой силы, например разделение строк на пробелы. Это не надежно, но мы можем предположить, что вывод правильный, поэтому, если строка имеет n заголовков, следующая строка будет иметь ровно n значений.

person Adam Schmideg    schedule 20.08.2010

Оказывается, табличный вывод программы, подобный этой, был одним из моих первых применений pyparsing. К сожалению, этот точный пример относится к проприетарному формату, который я не могу опубликовать, но здесь есть похожий пример: http://pyparsing.wikispaces.com/file/view/dictExample2.py.

person PaulMcG    schedule 21.08.2010
comment
Pyparsing больше не размещается на wikispaces.com. Перейдите на страницу github.com/pyparsing/pyparsing. - person PaulMcG; 27.08.2018