обработка текста из неплоского файла (для извлечения информации, как если бы это был * плоский* файл)
-
20-09-2019 - |
Вопрос
У меня есть продольный набор данных, сгенерированный с помощью компьютерного моделирования, который может быть представлен следующими таблицами ('var' - это переменные):
time subject var1 var2 var3
t1 subjectA ...
t2 subjectB ...
и
subject name
subjectA nameA
subjectB nameB
Однако сгенерированный файл записывает файл данных в формате, аналогичном следующему:
time t1
description
subjectA nameA
var1 var2 var3
subjectB nameB
var1 var2 var3
time t2
description
subjectA nameA
var1 var2 var3
subjectB nameB
var1 var2 var3
...(and so on)
Я использовал скрипт (python) для обработки этих выходных данных в плоский текстовый файл, чтобы я мог импортировать их в R, python, SQL или awk / grep для извлечения информации - пример типа информации, желаемой из одного запроса (в нотации SQL, после преобразования данных в таблицу), показан ниже:
SELECT var1, var2, var3 FROM datatable WHERE subject='subjectB'
Интересно, есть ли более эффективное решение, поскольку каждый из этих файлов данных может иметь размер ~ 100 МБ каждый (а у меня их сотни), а создание плоского текстового файла отнимает много времени и занимает дополнительное место на жестком диске с избыточной информацией.В идеале я бы взаимодействовал с исходным набором данных напрямую, чтобы извлечь нужную мне информацию, без создания дополнительного плоского текстового файла...Есть ли более простое решение awk / perl для таких задач?Я довольно опытен в обработке текста на python, но мои навыки в awk находятся в зачаточном состоянии, и у меня нет практических знаний о perl;Интересно, могут ли эти или другие инструменты, специфичные для конкретной предметной области, обеспечить лучшее решение.
Спасибо!
Постскриптум: Вау, спасибо всем!Мне жаль, что я не могу выбрать ответы для всех @FM:Спасибо.Мой скрипт на Python похож на ваш код без этапа фильтрации.Но ваша организация чиста.@ПП:Я думал, что уже хорошо разбираюсь в grep, но, видимо, нет!Это очень полезно...но я думаю, что grepping становится затруднительным при смешивании 'time' с выводом (который я не смог включить в качестве возможного сценария извлечения в моем примере!Это моя вина).@ghostdog74 - призрачный пес:Это просто фантастика...но изменить строку, чтобы получить 'subjectA', было непросто...(хотя тем временем я подробнее почитаю об awk и, надеюсь, грокну позже).@вайсмат:Хорошо изложено.@С.Лотт:Это чрезвычайно элегантно и гибко - я не просил решение на python (ic), но оно полностью вписывается в структуру синтаксического анализа, фильтрации и вывода, предложенную PP, и является достаточно гибким, чтобы приспособить ряд различных запросов для извлечения различных типов информации из этого иерархического файла.
Еще раз, я благодарен всем - большое спасибо.
Решение
Это то, для чего нужны генераторы Python.
def read_as_flat( someFile ):
line_iter= iter(someFile)
time_header= None
for line in line_iter:
words = line.split()
if words[0] == 'time':
time_header = [ words[1:] ] # the "time" line
description= line_iter.next()
time_header.append( description )
elif words[0] in subjectNameSet:
data = line_iter.next()
yield time_header + data
Вы можете использовать это как стандартный итератор Python
for time, description, var1, var2, var3 in read_as_flat( someFile ):
etc.
Другие советы
Если все, что вам нужно, это var1, var2, var3 при сопоставлении с определенной темой, то вы могли бы попробовать следующую команду:
grep -A 1 'subjectB'
В -A 1
аргумент командной строки предписывает grep распечатать совпадающую строку и одну строку после совпадающей строки (и в этом случае переменные появляются в строке после объекта).
Возможно, вы захотите использовать -E
возможность заставить grep выполнять поиск по регулярному выражению и привязать предмет поиска к началу строки (например grep -A 1 -E '^subjectB'
).
Наконец, выходные данные теперь будут состоять из строки темы и переменной строки, которые вы хотите.Возможно, вы захотите скрыть строку темы:
grep -A 1 'subjectB' |grep -v 'subjectB'
И вы, возможно, захотите обработать переменную строку:
grep -A 1 'subjectB' |grep -v 'subjectB' |perl -pe 's/ /,/g'
Лучшим вариантом было бы изменить компьютерное моделирование для получения прямоугольных выходных данных.Предполагая, что вы не можете этого сделать, вот один из подходов:
Для того, чтобы иметь возможность использовать данные в R, SQL и т.д.вам нужно так или иначе преобразовать его из иерархического в прямоугольный.Если у вас уже есть анализатор, который может преобразовать весь файл в прямоугольный набор данных, вы прошли большую часть пути.Следующий шаг - добавить дополнительную гибкость вашему анализатору, чтобы он мог отфильтровывать нежелательные записи данных.Вместо конвертера файлов у вас будет утилита для извлечения данных.
Приведенный ниже пример написан на Perl, но вы можете сделать то же самое и на Python.Общая идея состоит в том, чтобы поддерживать четкое разделение между (а) синтаксическим анализом, (б) фильтрацией и (в) выводом.Таким образом, у вас есть гибкая среда, позволяющая легко добавлять различные методы фильтрации или вывода в зависимости от ваших непосредственных потребностей в обработке данных.Вы также можете настроить методы фильтрации так, чтобы они принимали параметры (либо из командной строки, либо из файла конфигурации) для большей гибкости.
use strict;
use warnings;
read_file($ARGV[0], \&check_record);
sub read_file {
my ($file_name, $check_record) = @_;
open(my $file_handle, '<', $file_name) or die $!;
# A data structure to hold an entire record.
my $rec = {
time => '',
desc => '',
subj => '',
name => '',
vars => [],
};
# A code reference to get the next line and do some cleanup.
my $get_line = sub {
my $line = <$file_handle>;
return unless defined $line;
chomp $line;
$line =~ s/^\s+//;
return $line;
};
# Start parsing the data file.
while ( my $line = $get_line->() ){
if ($line =~ /^time (\w+)/){
$rec->{time} = $1;
$rec->{desc} = $get_line->();
}
else {
($rec->{subj}, $rec->{name}) = $line =~ /(\w+) +(\w+)/;
$rec->{vars} = [ split / +/, $get_line->() ];
# OK, we have a complete record. Now invoke our filtering
# code to decide whether to export record to rectangular format.
$check_record->($rec);
}
}
}
sub check_record {
my $rec = shift;
# Just an illustration. You'll want to parameterize this, most likely.
write_output($rec)
if $rec->{subj} eq 'subjectB'
and $rec->{time} eq 't1'
;
}
sub write_output {
my $rec = shift;
print join("\t",
$rec->{time}, $rec->{subj}, $rec->{name},
@{$rec->{vars}},
), "\n";
}
Если вы ленивы и у вас достаточно оперативной памяти, то я бы работал на диске RAM вместо файловой системы до тех пор, пока они вам нужны немедленно.
Я не думаю, что Perl или awk будут быстрее, чем Python, если вы просто перекодируете свой текущий алгоритм на другой язык.
awk '/time/{f=0}/subjectB/{f=1;next}f' file