Объединение нескольких файлов с помощью полного соединения по первому столбцу

Вопрос или проблема

У меня есть каталог с 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

Но это не дает желаемого вывода. Я был бы признателен, если бы кто-то мог помочь исправить это.

Я не смог придумать более элегантное решение, однако это должно сделать работу.

Идея заключается в:

  1. Скачать все файлы, заканчивающиеся на _format, извлекая первое поле и фильтруя дубликаты, сортируя вывод; это делается для создания “базового” файла, содержащего все (уникальные и отсортированные) ключи.
  2. Строить на основе базового файла, читая каждый файл, заканчивающийся на _format, один за другим и “лево объединяя” его по полю #1 базового файла; это делается с использованием join + Awk; Awk необходим, потому что нет возможности использовать join‘s -e опцию в этом случае, так как нет способа указать “все поля файла 1” в строке FORMAT join; Awk просто добавляет ,NA, если количество полей обрабатываемой строки не соответствует количеству присоединенных файлов до сих пор.
  3. Заголовок также постепенно строится на основе базового заголовка 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 для отсутствующих данных.

Вот пошаговое решение, которое выполняет необходимую задачу:

  1. Создание файла со всеми уникальными значениями первого столбца: Мы сначала вычленим первый столбец из всех файлов, отсортируем и удалим дубликаты.

  2. Объединение файлов: Затем для каждого файла мы будем добавлять данные, используя полное соединение ("full join"), обеспечивая, чтобы если значение отсутствует, в вывод добавлялось NA.

  3. Формирование заголовка: Мы добавим заголовок, включающий название всех файлов.

Ниже представлен полный скрипт на 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 потребует достаточной оперативной памяти. Всегда тестируйте на меньших объемах данных перед обработкой больших наборов.

Оцените материал
Добавить комментарий

Капча загружается...