Вопрос или проблема
Я имею дело с серией bed файлов, которые выглядят следующим образом:
chr1 100 110 0.5
chr1 150 175 0.2
chr1 200 300 1.5
Колонки соответствуют хромосоме, началу, концу и оценке. У меня есть несколько различных файлов с разными оценками в каждом, и мне бы хотелось объединить их следующим образом:
> cat a.bed
chr1 100 110 0.5
chr1 150 175 0.2
chr1 200 300 1.5
> cat b.bed
chr1 100 110 0.4
chr1 150 175 0.7
chr1 200 300 0.9
> cat c.bed
chr1 100 110 1.5
chr1 150 175 1.2
chr1 200 300 0.1
> cat combined.bed
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
Все колонки с оценками (последняя колонка файла) добавляются в один файл. Я нашел этот ответ, который может объединить колонку из одного дополнительного файла в существующий файл, но мне хотелось бы иметь команду, которая может добавлять переменное количество колонок. Поэтому, если у меня есть 10 bed файлов для объединения, я хотел бы иметь команду, которая может обработать их все вместе и создать один файл с 10 колонками оценок.
Каждый файл должен содержать одинаковое количество строк, и у каждой записи должны быть одинаковые координаты во всех файлах, так что конфликтов здесь быть не должно. Однако в каждом из файлов может быть много записей (100К или больше в общем), поэтому я хотел бы избежать многократной обработки каждой из них.
Есть ли способ сделать это изящно? Это будет в скрипте, так что нет необходимости в одиночной команде.
Используя любые paste
и awk
, при условии, что все файлы имеют одинаковые ключевые значения и нет пустых файлов:
$ paste *.bed | awk -v ORS= '{print $1, $2, $3; for (i=4; i<=NF; i+=4) print OFS $i; print RS}'
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
Если вы хотите заголовочную строку для этого, вы можете сначала выполнить следующее:
$ printf 'Chr Start Stop'; printf ' %s' *.bed; printf '\n'
Chr Start Stop a.bed b.bed c.bed
Альтернативно, просто используя любой awk, даже если у вас нет одинаковых ключей во всех файлах и даже если некоторые файлы пустые и добавляя заголовок:
$ cat tst.awk
{ key = $1 OFS $2 OFS $3 }
!seen[key]++ {
keys[++numKeys] = key
}
{
vals[key,FILENAME] = $4
}
END {
numFiles = ARGC - 1
printf "Chr" OFS "Start" OFS "Stop"
for ( fileNr=1; fileNr<=numFiles; fileNr++ ) {
fileName = ARGV[fileNr]
printf "%s%s", OFS, fileName
}
print ""
for ( keyNr=1; keyNr<=numKeys; keyNr++ ) {
key = keys[keyNr]
printf "%s", key
for ( fileNr=1; fileNr<=numFiles; fileNr++ ) {
fileName = ARGV[fileNr]
val = vals[key,fileName]
printf "%s%s", OFS, val
}
print ""
}
}
$ awk -f tst.awk a.bed b.bed c.bed
Chr Start Stop a.bed b.bed c.bed
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
Направление вывода в column -t
было бы одним из многих альтернатив, если вы хотите табличный вывод, например:
$ awk -f tst.awk a.bed b.bed c.bed | column -t
Chr Start Stop a.bed b.bed c.bed
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
Следующее использует Miller (mlr
) для переименования первых трех колонок, не имеющих имен, в chr
, start
, и stop
, а затем добавляет дополнительное поле под названием file
, содержащее имя файла, из которого была прочитана текущая запись. Затем применяется операция reshape
“long-to-wide”, чтобы преобразовать набор данных, используя недавно добавленное поле file
как название для значения в 4-й (все еще безымянной) колонке.
mlr --nidx \
label chr,start,stop then \
put '$file = FILENAME' then \
reshape -s file,4 \
[abc].bed
Предполагается, что входные данные разделены пробелами, как в вопросе, но они не обязательно должны быть отсортированы. Команда генерирует выходящие данные, разделенные пробелами:
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
(Команда label
на самом деле совершенно необязательна, если вам не нужен вывод в каком-либо формате, поддерживающем заголовки, как показано ниже.)
Для красивого форматирования используйте --n2p
вместо --nidx
:
$ mlr --n2p label chr,start,stop then put '$file = FILENAME' then reshape -s file,4 [abc].bed
chr start stop a.bed b.bed c.bed
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
Используйте --n2c
для CSV вывода.
$ mlr --n2c label chr,start,stop then put '$file = FILENAME' then reshape -s file,4 [abc].bed
chr,start,stop,a.bed,b.bed,c.bed
chr1,100,110,0.5,0.4,1.5
chr1,150,175,0.2,0.7,1.2
chr1,200,300,1.5,0.9,0.1
Используя Raku (ранее известный как Perl_6)
Создание хэша с Chr/start/stop в качестве ключа, добавление значения:
~$ raku -ne 'BEGIN my %hash; %hash.push: .split(/ \s+ <?before <[.0..9-]>+ $ > /, 2); END .say for %hash.sort;' *.bed
chr1 100 110 => [0.5 0.4 1.5]
chr1 150 175 => [0.2 0.7 1.2]
chr1 200 300 => [1.5 0.9 0.1]
Очистка вывода (использование put
вместо say
дает ключ/значение, разделенные табуляцией):
~$ raku -ne 'BEGIN my %hash; %hash.push: .split(/ \s+ <?before <[.0..9-]>+ $ > /, 2); END .put for %hash.sort;' *.bed
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
То же самое, что и выше, за исключением объединения пар ключ/значение с использованием пробелов:
~$ raku -ne 'BEGIN my %hash; %hash.push: .split(/ \s+ <?before <[.0..9-]>+ $ > /, 2); END .kv.join(" ").put for %hash.sort;' *.bed
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
Raku – это язык программирования из семейства Perl, который имеет чистый синтаксис регулярных выражений. Выше .split
используется для разделения каждой входной строки на 2
фрагмента, удаляя пробелы перед последней колонкой. Они добавляются в хэш, поэтому значения накапливаются. При выводе вы добавляете .sort
в конец кода (сортировка по умолчанию по ключам).
В основном, функция .split
ищет шаблон / \s+ <?before <[.0..9-]>+ $ > /
, который представляет пробелы перед
любой комбинацией десятичных цифр 0..9
, точки .
, и знака минус -
в конце строки $
(опустите знак минус если отрицательные числа в этой колонке невозможны). Технически, синтаксис <?before … >
является нотацией безразмерного положительного предварительного поиска Raku (“X перед Y”). Другой подход ниже.
Отделение последней колонки без использования регулярных выражений:
~$ raku -ne 'BEGIN my %hash;
%hash.push: .flip.split(/ \s+ /, 2).reverse.map: *.flip;
END .kv.join(" ").put for %hash.sort;' *.bed
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
РЕДАКЦИЯ: Добавление строки заголовка с именами файлов в качестве столбцов значений:
~$ raku -ne 'BEGIN my (%fh, %hash);
%hash.push: "Chr start stop" => $*ARGFILES.Str unless %fh{$*ARGFILES.Str}++;
%hash.push: .split(/ \s+ <?before <[.0..9-]>+ $ > /, 2);
END .kv.join(" ").put for %hash.sort;' *.bed
Возвращает:
Chr start stop a.bed b.bed c.bed
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
https://docs.raku.org/language/regexes
https://docs.raku.org/type/Str#routine_split
https://raku.org
Ответ или решение
Вопрос о добавлении столбцов из переменного числа файлов в базовый файл представляет собой классическую задачу обработки данных, часто встречающуюся в биоинформатике и других областях, где используется формат bed (Browser Extensible Data). Формат bed-файлов обычно включает в себя информацию о расположении геномных компонентов, таких как хромосомы и их координаты, и часто используется для хранения количественных данных, таких как считывания или интенсивности сигналов.
Теория
Основная задача состоит в том, чтобы объединить несколько bed-файлов, каждый из которых содержит одинаковые геномные координаты, но различающиеся в значениях, в один файл. Это требует агрегирования данных по ключевым параметрам, таким как хромосома, начальная и конечная координаты, и добавление дополнительной информации о значениях из каждого файла. Таким образом, первый, второй и третий столбцы должны совпадать во всех файлах, а из последнего столбца каждого файла извлекается значение, которое добавляется как новый столбец в итоговый файл.
Пример
Рассмотрим пример. Допустим, у нас есть три bed-файла:
-
a.bed
:chr1 100 110 0.5 chr1 150 175 0.2 chr1 200 300 1.5
-
b.bed
:chr1 100 110 0.4 chr1 150 175 0.7 chr1 200 300 0.9
-
c.bed
:chr1 100 110 1.5 chr1 150 175 1.2 chr1 200 300 0.1
Итоговый объединенный файл combined.bed
должен выглядеть следующим образом:
chr1 100 110 0.5 0.4 1.5
chr1 150 175 0.2 0.7 1.2
chr1 200 300 1.5 0.9 0.1
Применение
Для решения этой задачи в Unix или Linux системах можно использовать командные средства, такие как paste
, awk
и другие утилиты обработки текста. В зависимости от ваших требований к эффективности и читаемости кода, вы можете выбрать различные подходы. Вот несколько примеров:
-
Использование
paste
иawk
:Объединение файлов командой
paste
, которая может соединять строки из нескольких файлов по горизонтали. Затемawk
используется, чтобы извлекать и печатать нужные столбцы:paste *.bed | awk -v ORS= '{print $1, $2, $3; for (i=4; i<=NF; i+=4) print OFS $i; print RS}'
Эта команда объединяет все bed-файлы (предполагается, что они находятся в текущем каталоге) и выводит объединенные строки, извлекая каждый четвертый столбец из вывода команды
paste
. -
Использование скрипта на
awk
:Более сложное решение с использованием
awk
позволяет обработать ситуацию, когда некоторые файлы могут быть пустыми или порядок строк может варьироваться:$ cat tst.awk { key = $1 OFS $2 OFS $3 } !seen[key]++ { keys[++numKeys] = key } { vals[key,FILENAME] = $4 } END { numFiles = ARGC - 1 printf "Chr" OFS "Start" OFS "Stop" for ( fileNr=1; fileNr<=numFiles; fileNr++ ) { fileName = ARGV[fileNr] printf "%s%s", OFS, fileName } print "" for ( keyNr=1; keyNr<=numKeys; keyNr++ ) { key = keys[keyNr] printf "%s", key for ( fileNr=1; fileNr<=numFiles; fileNr++ ) { fileName = ARGV[fileNr] val = vals[key,fileName] printf "%s%s", OFS, val } print "" } }
Запуск этого скрипта с заданными файлами (
awk -f tst.awk a.bed b.bed c.bed
) собирает данные из всех указанных файлов и печатает их в формате с заголовком. -
Использование
mlr
(Miller):mlr
, или Miller, предлагает более мощный и гибкий подход к обработке данных, позволяя манипулировать столбцами и выполнять операции "длинный-к-широкому" (long-to-wide):mlr --nidx label chr,start,stop then put '$file = FILENAME' then reshape -s file,4 [abc].bed
Этот метод наглядно демонстрирует название столбцов и обеспечивает высококачественную обработку данных с использованием понятной синтаксической структуры.
Резюме
Существуют различные подходы к задаче объединения bed-файлов в зависимости от ваших предпочтений и доступных инструментов. В предложенных примерах было использовано три основные утилиты: paste
, awk
, и mlr
, каждая из которых предлагает свой уникальный сценарий использования. Выбор конкретного инструмента будет зависеть от специфики проекта: объема данных, необходимых метаданных и требований к производительности. В конечном итоге, знание этих инструментов расширяет возможности обработки и анализа данных, делая процессы более эффективными и менее подверженными ошибкам.