Как я могу подсчитать строки файлов с разными именами и записать результат в CSV-файл?
Я пишу сценарий для анализа некоторых данных. У меня есть несколько подмножеств файлов, и я хотел бы посчитать строку из этих файлов и записать результат в CSV-файл. Я постараюсь сделать пример. У меня есть эти два подмножества файла:
sample1.ext
sample1.ext2
sample1.ext3
sample2.ext
sample2.ext2
sample2.ext3
Я хотел бы посчитать строки, содержащиеся во всех файлах в *.ext, *.ext2 а также *.ext3 и запишите результаты в CSV-файл, который выглядит так:
count(sample1.ext), count(sample1.ext2), count(sample1.ext3)
count(sample2.ext), count(sample2.ext2), count(sample2.ext3)
После подсчета первой серии файла в *.extЯ выводить результаты в первый столбец файла CSV. Как мне записать вывод второй серии отсчета *.ext2 во второй столбец того же CSV-файла? И то же самое для третьего столбца?
Спасибо всем за ответы, я пытался адаптировать их к своим файлам, но, к сожалению, я не могу этого сделать. Пример, который я разместил, был просто примером, где я помещал числа вместо странных расширений, чтобы было легче понять проблему. Вы все поняли, но вы слишком сосредоточились на числах, которых нет в реальности. Я объясню вам снова, используя реальные файлы. Эти файлы поступают из отображения геномных данных в эталонный геном. Я обрабатываю эти данные, чтобы очистить их, поэтому у меня есть три шага, где меняется количество строк. Итак, файл:
name.sort.bam
name.mapped.bam
name.rmdup.bam
othername.sort.bam
othername.mapped.bam
othername.rmdup.bam
Расширение bam является сжатым файлом. Для подсчета строк в этом файле есть специальная командная строка:
samtools view -c (file)
Единственный способ, который я нашел, это перебирать каждый *sort.bam, *mapped.bam, *rmdup.bam и напишите вывод txt для каждого, и вставьте их в конце в файл csv. Есть ли способ избежать этих трех циклов и сделать все вместе? Извините за недоразумение, у всех вас есть отличные идеи!
3 ответа
Вы можете использовать этот Perl-скрипт:
#! /usr/bin/perl
use strict;
use warnings;
my @names;
my @files;
@ARGV == 1 || die();
opendir(my $dir, $ARGV[0]) || die $!;
while(readdir($dir)) {
if($_ =~ /(.*)\.(sort|mapped|rmdup)\.bam$/) {
grep(/^$1$/, @names) == 0 && push(@names, $1);
}
}
close($dir);
foreach my $name (sort(@names)) {
my @fields;
push(@fields, $name);
foreach my $extension ("sort", "mapped", "rmdup") {
if(! -f "$ARGV[0]/$name.$extension.bam") {
push(@fields, 0);
print STDERR "'$ARGV[0]/$name.$extension.bam' missing\n";
next;
}
my $count = `<"$ARGV[0]/$name.$extension.bam" wc -l`;
chomp($count);
push(@fields, $count)
}
print(join(", ", @fields)."\n")
}
Сохраните его где-нибудь в вашей системе, сделайте его исполняемым и запустите его, передав каталог в качестве аргумента:
path/to/script path/to/directory
% tree directory
directory
├── name.mapped.bam
├── name.rmdup.bam
├── name.sort.bam
├── othername.mapped.bam
├── othername.rmdup.bam
└── othername.sort.bam
0 directories, 6 files
% perl script.pl directory
name, 0, 0, 0
othername, 0, 0, 0
% for f in directory/*.sort.bam; do printf 'line\n' >>"$f"; done
% perl script.pl directory
name, 1, 0, 0
othername, 1, 0, 0
Что делает скрипт:
- Перебирает все файлы в
path/to/directory; если имя файла совпадает.*\.(sort|mapped|rmdup)\.bam$, добавляет строку перед.sort.bam,.mapped.bamили же.rmdup.bamк списку@namesесли еще нет в списке; - Для каждого имени в отсортированном
@namesперечислить как$name, добавляет$nameк списку@fields; для каждого расширения вsort,mappedа такжеrmdupкак$extensionпроверяет, если$name.$extension.bamсуществует вpath/to/directory; если файл не существует, добавляет0в@fields, печатает сообщение об ошибке и переходит к следующему$extension/$name; если файл существует, добавляет вывод<"$name.$extension.bam" wc -lв@fields; раз все возможные значения для$extensionбыл повторен, печатает строку, содержащую элементы@fieldsприсоединился к,,
Опция Python
Интересный вопрос Хороший повод применить питона groupby()
Поскольку ваши файлы находятся в едином "плоском" каталоге:
#!/usr/bin/env python3
from itertools import groupby
import os
import sys
dr = sys.argv[1]
# list the files in the directory, split into "sortable" elements
flist = [[item, item.split(".", 1)] for item in os.listdir(dr)]
# sort the file list by first section (until the first found dot)
flist.sort(key=lambda x: x[1][0])
# create sub groups of the files, grouped by first section of name
for key, line in groupby(flist, lambda x: x[1][0]):
line = list(line)
# sort the files by second section of name for correct order in the csv lines
line.sort(key=lambda x: x[1][1])
# count the lines of the files, arrange the csv file
print((", ").join([str(len(open(dr+"/"+f[0]).readlines())) for f in line]))
Как это устроено
Если каталог содержит девять файлов:
sample1.ext 2 lines
sample1.ext2 3 lines
sample1.ext3 3 lines
sample2.ext 1 lines
sample2.ext2 1 lines
sample2.ext3 4 lines
sample3.ext 6 lines
sample3.ext2 1 lines
sample3.ext3 4 lines
Скрипт перечисляет файлы, разбивает каждое из имен на два раздела, например:
sample2а также
ext2поскольку порядок строк и длина файла внутри строк зависит от точной сортировки этих двух разделов.
- Затем сценарий сортирует файлы по первому разделу имени, поскольку длина каждого из файлов (с аналогичным разделом имени) должна быть сгруппирована для каждого раздела в одну строку;
sample1,sample2,sample3и так далее. - Впоследствии подгруппы (за
csvстроки), правильно отсортированные по второму разделу имени, чтобы числа (строки) отображались в правильном порядке в строке
Результат:
python3 '/home/jacob/Bureaublad/create_csv.py' '/home/jacob/Bureaublad/samples'
2, 3, 3
1, 1, 4
6, 1, 4
Как пользоваться
- Скопируйте скрипт в пустой файл, сохраните его как
create_csv.py Запустите его с каталогом с вашими файлами в качестве аргумента
python3 /path/to/create_csv.py /path/to/directory_with_files
Важная заметка
Метод, используемый для подсчета строк, подходит для тех случаев, когда файлы не очень большие. Если файлы есть, другой способ подсчета строк приведет к повышению производительности.
РЕДАКТИРОВАТЬ
В результате последней информации, добавленной к вашему вопросу, отредактированная версия скрипта. По совпадению, не так много нужно изменить: скрипт уже разделил имена файлов по первой найденной точке командой:
item.split(".", 1)
Так как последний раздел имени .bam, который одинаков для всех файлов, для порядка сортировки не имеет смысла.
Тогда нам нужно только заменить "старый" способ подсчета строк файла:
str(len(open(dr+"/"+f[0]).readlines()))
с помощью (реализация и интеграция в скрипт на языке-) команды, которую вы предоставили:
str(subprocess.check_output(["samtools", "view", "-c", dr+"/"+f[0]]).decode("utf-8").strip())
Отредактированный скрипт
#!/usr/bin/env python3
from itertools import groupby
import os
import sys
import subprocess
dr = sys.argv[1]
# list the files in the directory, split into "sortable" elements
flist = [[item, item.split(".", 1)] for item in os.listdir(dr)]
# sort the file list by first section (until the first found dot)
flist.sort(key=lambda x: x[1][0])
# create sub groups of the files, grouped by first section of name
for key, line in groupby(flist, lambda x: x[1][0]):
line = list(line)
# sort the files by second section of name for correct order in the csv lines
line.sort(key=lambda x: x[1][1])
# count the lines of the files, arrange the csv file
print((", ").join([
str(subprocess.check_output(["samtools", "view", "-c", dr+"/"+f[0]]).decode("utf-8").strip())
for f in line]))
Заметка
Обратите внимание, что порядок чисел внутри строки определяется отсортированным порядком второй части имени, например
mapped.bam, rmdup.bam, sort.bam
Предполагая, что вы хотите вывод, как 42, 19, 10207, 3 в каждой строке (без имен файлов), wcи немного BashИнг решит вашу проблему.
outfile="Result.csv"
for samplenum in $( seq 1 100 ) ; do
line=""
for file in sample${samplenum}.* ; do
numlines=$( wc -l <$file )
line="$line $numlines,"
done
# remove the final comma
line=${line%,}
# not quoting $line below will suppress the initial blank
echo $line >> $outfile
done
Читать man bash, man wc, man seq а также man bash снова
Отвечая на комментарий:
Вы читали man страницы?
$( seq 1 100) заменяется результатами seq 1 100 команда, которая просто выводит целые числа от 1 до 100 (что читает man seq сказал бы тебе). Замените его на то, что дает количество образцов, которые у вас есть.
Поместите код в файл (например, test.sh) и запустить его с bash -x test.sh чтобы увидеть детали. Заменить seq 1 100 с seq 1 2 для теста, чтобы избежать лавины вывода.
samplenum содержит номер выборки, который, для этого примера, работает от 1 до 100.
sample, в sample${samplenum}.* это просто строка. Объединяется со значением samplenum и строка .* создать шаблон имени файла, например sample1.* первый раз через for samplenum ... петли, sample2.* второй раз и т. д.
Вы прочитали и поняли, man bash, man wc, man seq а также man bash снова?