упрощение работы с концом файла

#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)