#python #python-3.x
#python #python-3.x
Вопрос:
У меня есть следующий файл:
chr11_pilon3.g3568.t1 transcript:OIS96097 82.2 168 30
chr11_pilon3.g3568.t2 transcript:OIS96097 82.2 169 30
gene.100079.0.5.p3 transcript:OIS96097 82.2 169 30
gene.100079.0.3.p1 transcript:OIS96097 82.2 169 30
gene.100079.0.0.p1 transcript:OIS96097 82.2 169 35
gene.100080.0.3.p1 transcript:OIS96097 82.2 169 40
gene.100080.0.0.p1 transcript:OIS96097 82.2 169 40
и я получаю следующий вывод:
chr11_pilon3.g3568.t1 transcript:OIS96097 82.2 168 30
chr11_pilon3.g3568.t2 transcript:OIS96097 82.2 169 30
gene.100079.0.0.p1 transcript:OIS96097 82.2 169 35
gene.100080.0.3.p1 transcript:OIS96097 82.2 169 40
gene.100080.0.0.p1 transcript:OIS96097 82.2 169 40
Мне просто интересно, есть ли способы упростить приведенный ниже код с точки зрения того, как обращаться с концом файла
try:
lineParts = line.rstrip().split('t')
except IndexError:
continue
и
if dataStorage: # check if Dict is **not** empty
output(tmp_id, dataStorage, out_fn)
?
def output(tmp_id, dataDict, out_fn):
for isoformID in dataDict.keys():
mx = max(dataDict[isoformID], key=lambda x: int(x[4]))[4]
mx_values = [d for d in dataDict[isoformID] if d[4] == mx]
for mx_value in mx_values:
out_fn.write('t'.join(mx_value) 'n')
del dataDict[isoformID]
dataDict[tmp_id].append(lineParts)
return dataDict
dataStorage = defaultdict(list)
with open("data/datap-bigcov.tsv") as data_fn, open("data/datap-bigcov-out.tsv", 'w') as out_fn:
for line in data_fn:
try:
lineParts = line.rstrip().split('t')
except IndexError:
continue
if lineParts[0].startswith("gene"):
split_id = lineParts[0].split('.')
tmp_id = split_id[0] "." split_id[1]
else:
tmp_id = lineParts[0]
if not dataStorage: # check if Dict is empty
dataStorage[tmp_id].append(lineParts)
elif tmp_id in dataStorage:
dataStorage[tmp_id].append(lineParts)
else:
dataStorage = output(tmp_id, dataStorage, out_fn)
if dataStorage: # check if Dict is **not** empty
output(tmp_id, dataStorage, out_fn)
Комментарии:
1. Я не думаю, что код
line.rstrip().split('t')
когда-либо вызоветIndexError
, почему вы улавливаете исключения такого типа? итерацияfor line in data_fn
просто прекратится, когда она достигнет конца файла, есть ли в конце пустые строки или что-то в этом роде?2. Раньше я
IndexError
ловил, если последняя строка пуста.3. если это так, то
.split("t")
вернет пустой список иlineParts[0]
выдаст ошибку индекса. не лучше ли просто проверитьif line.rstrip() == "": continue
, чтобы пропустить пустые строки?
Ответ №1:
Здесь вы уже имеете дело с концом файла:
for line in data_fn:
...
Это автоматически останавливается после чтения последней строки. Однако, похоже, вы хотите остановиться, когда столкнетесь с первой пустой строкой. Очень простой способ был бы:
for line in data_fn:
line = line.rstrip()
if not line:
break
...
Или, если вам не нравится использовать / злоупотреблять тем фактом, который ''
вычисляется как False
:
for line in data_fn:
if line == 'n':
break
...
Другой способ приблизиться к этому — просто пропустить пустые строки (также допуская пустые строки в середине файла:
for line in data_fn:
line = line.rstrip()
if line:
...
Кстати, вы, вероятно, не хотите вызывать переменную, ссылающуюся на файл data_fn
, поскольку имя предполагает, что это имя файла, но на самом деле это ссылка на файл.
Итак, data_f
, просто f
или data
все будут более подходящими именами.
Вы задали дополнительный вопрос в комментариях: «Спасибо, как бы вы заменили это условие, чтобы гарантировать, что последний идентификатор будет записан в file: if blastStorage: output(tmp_id, blastStorage, out_fn)
» — Я мог бы попытаться ответить на этот вопрос, но я чувствую, что это приведет к еще большему количеству вопросов. В вашем коде довольно много ошибок в программировании и стиле, и это заходит слишком далеко, чтобы решать их все по отдельности.
Ваша общая проблема заключается в том, что ваш код немного более запутанный, чем нужно. Часто, если вы однажды решили проблему, это помогает сделать шаг назад и решить, достаточно ли хорошо вы теперь понимаете проблему, чтобы написать лучшее (и более простое) решение.
Проблема, которую вы решаете: у вас есть входной файл записей; каждая запись имеет связанный идентификатор (первые две части первого столбца разделены на периоды); вы хотите создать выходной файл, содержащий для каждого идентификатора во входном файле все записи, в которых некоторое значение равномаксимальное одинаковое значение для всех записей с этим идентификатором. Похоже, вы хотите обработать файл за один проход (более простое, но, возможно, более медленное решение может выполнить двойной проход).
Ваш код, похоже, предполагает, что вы ожидаете, что записи с соответствующими идентификаторами будут последовательными, поэтому вам не нужно хранить данные для более поздних совпадений. От того, как вы работаете с максимальным значением
Рабочее (полное и гораздо более простое) решение, использующее эти подразумеваемые предположения, будет:
with open("data/datap-bigcov.tsv") as f_in, open("data/datap-bigcov-out.tsv", 'w') as f_out:
current = ('', 0)
max_lines = []
for line in f_in:
line_parts = line.rstrip().split()
rec_id = '.'.join(line_parts[0].split('.')[:2])
value = int(line_parts[4])
# id and value match, just add it
if (rec_id, value) == current:
max_lines.append(line)
else:
# id doesn't match
if rec_id != current[0]:
# whatever was collected so far should be written first
f_out.writelines(max_lines)
# value doesn't match (and is greater)
elif value > current[1]:
# if rec_id matches, but value doesn't and isn't greater, just skip it
continue
# in both other cases, rec_id and value are the values we're after for now
current = (rec_id, value)
# start a new set with the greater max
max_lines = [line]
f_out.writelines(max_lines)
Оператор f_out.writelines(max_lines)
находится там дважды, чтобы гарантировать, что окончательный идентификатор также будет записан в file, но я думаю, что общий код настолько проще, что дублирующий вызов на самом деле не вызывает беспокойства.
Также обратите внимание, что при первом вызове он вызывается с пустым max_lines
значением, но это делает то, что ожидается (ничего), и позволяет избежать необходимости проверять, является ли он пустым.
Всего 17 строк на Python, без ущерба для четкости или скорости.
И последнее замечание: библиотека обычно очень хорошо справляется с подобными вещами pandas
. Вот решение вашей проблемы всего в нескольких строках с использованием pandas
:
from pandas import read_csv
df = read_csv('data/datap-bigcov.tsv', sep='t', header=None)
df['id'] = df.apply(lambda rec: '.'.join(rec[0].split('.')[:2]), axis=1)
result = df[df[4] == df.groupby(by='id')[4].transform('max')]
result.to_csv('data/datap-bigcov-out.tsv', sep='t', header=None)
Я чувствую, что использование pandas
жертвует ясностью (по крайней мере, для получения кода требуется довольно хорошее понимание pandas
), но он намного лучше работает с большими наборами данных, и это в значительной степени отраслевой стандарт, поэтому его стоит изучить.
Комментарии:
1. Спасибо, как бы вы заменили это условие, чтобы гарантировать, что последний идентификатор будет записан в file:
if blastStorage: output(tmp_id, blastStorage, out_fn)