Вопрос или проблема
У меня есть каталог с 200 образцами. Каждый файл содержит два столбца. Я хочу выполнить полное объединение этих файлов и создать объединенный файл.
Вот пример:
$ head 346_T1_A_deduped.bismark.cov_format
1_10525 95.2
1_10526 98.9690721649485
1_10542 80.8
1_10543 87.6288659793814
$ head 346_T1_B_deduped.bismark.cov_format
1_10525 95.7142857142857
1_10526 98.1132075471698
1_10542 78.5714285714286
1_10549 75.4716981132076
Я хочу объединить все файлы на основе первого столбца, и если какой-либо файл не имеет соответствующего первого столбца, поставить NA в объединенный файл.
Вот пример вывода:
$ head merged.csv
Prob,346_T1_A_deduped.bismark.cov_format,346_T1_B_deduped.bismark.cov_format
1_10525,95.2,95.7142857142857
1_10526,98.9690721649485,98.1132075471698
1_10542,80.8,78.5714285714286
1_10543,87.6288659793814,NA
1_10549,NA,75.4716981132076
Таким образом, я хочу, чтобы выходные данные были в формате, разделенном запятыми, первый столбец – это столбец, используемый для объединения, а остальные столбцы – это соответствующие имена файлов. Как вы видите, NA размещается, когда prob отсутствует в любом файле.
Я написал это:
# Создание пустого выходного файла
output_file="full_joined_result.txt"
> "$output_file"
# Цикл по всем файлам в каталоге
for file in *_format; do
# Если выходной файл пуст, инициализируем его данными из первого файла
if [ ! -s "$output_file" ]; then
cp "$file" "$output_file"
else
# Полное объединение с следующим файлом на основе первого столбца
awk 'FNR==NR {a[$1]=$0; next} {print a[$1], $0}' "$output_file" "$file" > temp && mv temp "$output_file"
fi
done
Но это не дает желаемого вывода. Я был бы признателен, если бы кто-то мог помочь исправить это.
Я не смог придумать более элегантное решение, однако это должно сделать работу.
Идея заключается в:
- Скачать все файлы, заканчивающиеся на
_format
, извлекая первое поле и фильтруя дубликаты, сортируя вывод; это делается для создания “базового” файла, содержащего все (уникальные и отсортированные) ключи. - Строить на основе базового файла, читая каждый файл, заканчивающийся на
_format
, один за другим и “лево объединяя” его по полю #1 базового файла; это делается с использованиемjoin
+ Awk; Awk необходим, потому что нет возможности использоватьjoin
‘s-e
опцию в этом случае, так как нет способа указать “все поля файла 1” в строкеFORMAT
join
; Awk просто добавляет,NA
, если количество полей обрабатываемой строки не соответствует количеству присоединенных файлов до сих пор. - Заголовок также постепенно строится на основе базового заголовка
Prob
, добавляя,
, за которым следует текущее имя файла на каждой итерации; после обработки каждого файла заголовок и выходной CSV объединяются и выводятся в STDOUT.
#!/usr/bin/env bash
cut -d' ' -f1 *_format |
sort -u >out
header=Prob
nf=2
for f in *_format; do
join -t, -j1 -a1 out <(sort "${f}" | tr ' ' ,) |
awk -F, -v nf=${nf} 'NF != nf {print $0",NA"; next} {print}' >temp
mv temp out
header+=,${f}
(( nf++ ))
done
<<<"${header}" cat - out
rm out
% ./script.sh
Prob,346_T1_A_deduped.bismark.cov_format,346_T1_B_deduped.bismark.cov_format
1_10525,95.2,95.7142857142857
1_10526,98.9690721649485,98.1132075471698
1_10542,80.8,78.5714285714286
1_10543,87.6288659793814,NA
1_10549,NA,75.4716981132076
Используя скрипт awk под названием script.awk
:
FNR==1{cov[++i]=FILENAME}
{
_[$1][cov[i]]=$2
}
END{
printf "Prob"
for (other_key in cov) printf ",%s", cov[other_key]
printf "\n"
for (key in _){
printf "%s", key
for (other_key in cov){
if (cov[other_key] in _[key])
printf ",%s", _[key][cov[other_key]]
else
printf ",NA"
}
printf "\n"
}
}
awk -f script.awk input*
Будьте осторожны, это использует несколько массивов, которые будут загружены в память, поэтому, если у вас слишком много данных, это может не сработать.
Как это работает :
FNR==1{cov[++i]=FILENAME}
регистрирует имя файла для каждого файла{_[$1][cov[i]]=$2}
регистрирует второе поле, используя два ключа: первое поле и имя текущего файлаEND{}
после последней записиprintf "Prob"; for (other_key in cov) printf ",%s", cov[other_key]; printf '\n'
выводит заголовок выходного файлаfor (key in _)
для всех ключей в массиве_
Ответ или решение
Для выполнения задачи объединения нескольких файлов с использованием полного соединения по первому столбцу, мы можем применить скрипт на Bash с помощью утилиты awk
. Этот подход позволит легко обработать множество файлов, а также правильно сформировать результат с использованием значений NA для отсутствующих данных.
Вот пошаговое решение, которое выполняет необходимую задачу:
-
Создание файла со всеми уникальными значениями первого столбца: Мы сначала вычленим первый столбец из всех файлов, отсортируем и удалим дубликаты.
-
Объединение файлов: Затем для каждого файла мы будем добавлять данные, используя полное соединение ("full join"), обеспечивая, чтобы если значение отсутствует, в вывод добавлялось NA.
-
Формирование заголовка: Мы добавим заголовок, включающий название всех файлов.
Ниже представлен полный скрипт на Bash:
#!/usr/bin/env bash
# Создаем временный файл для хранения уникальных значений первого столбца
cut -d' ' -f1 *_format | sort -u > temp_keys.txt
# Начальная строка заголовка
header="Prob"
# Счетчик для количества файлов в текущий момент
nf=2
# Обрабатываем каждый файл
for f in *_format; do
# Соединяем текущий файл с временным файлом ключей
join -t, -a1 temp_keys.txt <(sort "$f" | tr ' ' ',') |
awk -F, -v nf="$nf" 'NF != nf {print $0",NA"; next} {print}' > temp_output.txt
# Переносим временный вывод в файл ключей
mv temp_output.txt temp_keys.txt
# Добавляем имя файла к заголовку
header+=",${f}"
# Увеличиваем счетчик
((nf++))
done
# Печатаем заголовок и объединенные данные
{ echo "$header"; cat temp_keys.txt ; } > merged.csv
# Удаляем временный файл
rm temp_keys.txt
Объяснение данного скрипта:
- cut: Используется для выделения первого столбца из всех файлов, соответствующих шаблону
*_format
. - sort -u: Сортирует данные и убирает дубликаты, чтобы избежать повторяющихся записей.
- join: Используется для объединения текущего файла с временным файлом, помещая результаты в
temp_output.txt
. - awk: Проверяет количество полей; если строки не совпадают по количеству, добавляется ",NA".
- header: Формируется для финального вывода, включает названия всех файлов.
- Использование mv позволяет записать промежуточные результаты и подготавливает к следующему циклу.
Запуск скрипта:
Сохраните вышеуказанный код в файл (например, merge_files.sh
), сделайте его исполняемым с помощью команды:
chmod +x merge_files.sh
И затем выполните его:
./merge_files.sh
После завершения выполнения, вы получите файл merged.csv
, который будет содержать объединенные данные во требуемом формате.
Важно:
Скрипт может работать с большим количеством данных, но имейте в виду, что использование массивов в awk
потребует достаточной оперативной памяти. Всегда тестируйте на меньших объемах данных перед обработкой больших наборов.