Разбор выходных данных блочной программы с использованием Python - PullRequest
1 голос
/ 20 августа 2010

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

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

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

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

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

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

Ответы [ 5 ]

1 голос
/ 21 августа 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))
1 голос
/ 20 августа 2010

Да, это боль для разбора. Однако вам не нужно много регулярных выражений. Обычного 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

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

1 голос
/ 20 августа 2010

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

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

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

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

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

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

0 голосов
/ 21 августа 2010

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

0 голосов
/ 20 августа 2010

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

...