0

我是 Python 新手,遇到了一个在任何地方都找不到答案的问题。

我正在尝试编写代码来根据另一个文件过滤一组文件。这些文件是具有多行和多列的数组。我想要的是从数据文件中删除与某些列的过滤文件中的行匹配的行。

代码是:

paths = ('filepaths.txt')#file that has filepaths to open
filter_file = ('filter.txt')#file of items to filter
filtered = open('filtered.txt','w') #output file

filtering = open(filter_file, 'r').readlines()
for f in filtering:
    filt = f.rstrip().split('\t')

files = open(paths).read().splitlines()
for file in files:
    try:
        lines = open(file,'r').readlines()
        for l in lines:
            data = l.rstrip().split('\t')

        a = [data[0], data[5], data[6], data[10], data[11]] #data columns to match
        b= [filt[0], filt[1], filt[2], filt[3], filt[4]] #filter columns to match

        for i,j in zip(a,b): #loop through two lists to filter
            if i != j:
                matches = '\t'.join(data)
                print (matches)
                filtered.write(matches + '\n')
filtered.close()

代码执行,但没有按我的意愿工作。我得到的是每个文件的最后一行,重复 5 次。

显然,我错过了一些东西。我不确定 zip 是否是正确的功能,或者其他东西会更好。我会很感激任何建议。

编辑:

过滤器的样本输入:

HSPG2   22161380    22161380    G   A
PPTC7   110974744   110974744   G   C
OR1S2   57971546    57971546    A   C

要过滤的文件的示例输入(多余的列被保留):

TKTL1   8277    broad.mit.edu   37  X   153558089   153558089   +   3'UTR   SNP G   C   C
MPP1    4354    broad.mit.edu   37  X   154014502   154014502   +   Silent  SNP G   A   A
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T

示例输出(多余的列被保留):

BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
BRCC3   79184   broad.mit.edu   37  X   154306908   154306908   +   Silent  SNP A   T   T
4

2 回答 2

2

我将从一些简单的更改开始,然后展示如何使用内置工具(如 Python 的csv库)和any简化代码的函数。

这是一个清理了一些东西的版本,使用了正确的逻辑,但没有引入太多新的语言特性。它使用的主要新东西是with语句(退出时自动关闭文件)并直接迭代文件而不是使用readlines

paths = ('filepaths.txt')#file that has filepaths to open
filter_file = ('filter.txt')#file of items to filter
with open(filter_file, 'r') as filter_source:
    filters = []
    for line in filter_source:
        filters.append(line.rstrip().split('\t'))
with open(paths, 'r') as filename_source:
    filenames = []
    for line in filename_source:
        filenames.append(line.rstrip())
with open('filtered.txt','w') as filtered:
    for filename in filenames:
        with open(filename,'r') as datafile:
            for line in datafile:
                data = l.rstrip().split('\t')
                a = [data[0], data[5], data[6], data[10], data[11]] #data columns to match
                for filter in filters:
                    matched = True
                    for i,j in zip(a,filter):
                        if i != j:
                            matched = False
                            break
                    if matched:
                         # the data row matched a filter, stop checking for others
                         break
                if not matched:
                    filtered.write(line)

我们多次做的一件事是使用 for 循环来建立一个列表。有一个更简洁的表达式可以做同样的事情,称为列表推导。因此,使用它,我们将拥有:

with open(filter_file, 'r') as filter_source:
    filters = [line.rstrip().split('\t') for line in filter_source]
with open(paths, 'r') as filename_source:
    filenames = [line.rstrip() for line in filename_source]

但是 Python 也有一个有用的csv库,可以负责读取制表符分隔格式:

import csv

with open(filter_file, 'rb') as filter_source:
    filter_reader = csv.reader(filter_source, delimiter='\t')
    filters = list(filter_reader)

当您对其进行迭代时,它会返回由分隔符分隔的字段列表。请注意,我以b模式打开它;这是否会有所不同取决于您的平台,但如果确实如此,则 csv 文档会指出它是必需的。

您可以将其类似地用于数据文件,甚至可以选择使用writer该类编写过滤后的输出。

最后,如果可迭代对象的任何或所有内容评估为,则anyall内置函数获取可迭代对象并返回。您可以使用它们来删除嵌套的 for 循环,使用生成器表达式 - 这是一个类似于列表推导的构造,除了它是延迟评估的,这很好,因为并且会短路。所以这里有一种写法:TrueTrueanyall

def match(dataline, filter):
    return all(i==j for (i, j) in zip(dataline, filter))

在这种特殊情况下,我并没有从短路中得到太多,因为我正在使用zip构建一个实际的元组列表。但是对于这样短的列表,它很好,并且在已经在内存中的列表上zip优于(惰性评估版本)。itertools.zip

然后,您可以使用any简洁地将行与所有过滤器进行比较,一旦匹配就短路:

a = [data[0], data[5], data[6], data[10], data[11]]
if not any(match(a, filter) for filter in filters):
    filtered.write(line)

除了这仍然是矫枉过正。该match函数强制其两个输入中的所有元素必须相等,但是如果您测试两个列表是否相等,这是 Python 自动执行的一部分。我编写的match函数将允许长度不等的列表匹配,只要较长列表的起始元素都匹配较短的列表,而 Python 列表相等则不匹配,但这不是问题。所以这也可以:

a = [data[0], data[5], data[6], data[10], data[11]]
if not any (a==filter for filter in filters):
    filtered.write(line)

或者,如果您可能希望容忍比正常过滤器更长的时间:

if not any (a==filter[:5] for filter in filters):

非切片版本也可以使用直接列表成员测试来编写:

if a not in filters:
    filtered.write(line)

此外,正如 Blckknght 指出的那样,Python 有一种更好的方法来快速测试诸如线条之类的东西是否与多种模式中的任何一种匹配 -set数据类型,它使用恒定时间查找。列表,如由csvlibrary 或 by返回的列表,split不能是集合的成员 - 但元组可以,只要元组的成员本身是可散列的。因此,如果您将过滤器和数据行子集转换为元组,您可以维护一个集合而不是列表,并更快地检查它。为此,您必须将每个过滤器转换为元组:

filters = set(tuple(filter) for filter in filter_reader)

然后,定义a为一个元组:

a = (data[0], data[5], data[6], data[10], data[11])
if a not in filters:
    filtered.write(line)

如果您使用csv.writer实例来编写输出,您甚至可以使用writerows方法和生成器表达式进一步合并它:

filtered_writer.writerows(data for data in data_reader if (data[0], data[5], data[6], data[10], data[11]) not in filters)

所以总结一下,我会这样做:

import csv

paths = ('filepaths.txt') #file that has filepaths to open
filter_file = ('filter.txt') #file of items to filter
with open(filter_file, 'rb') as filter_source:
    filters = set(tuple(filter) for filter in csv.reader(filter_source, delimiter='\t'))
with open(paths, 'r') as filename_source:
    filenames = [line.rstrip() for line in filename_source]
with open('filtered.txt','wb') as filtered:
    filtered_writer = csv.writer(filtered, delimiter='\t')
    for filename in filenames:
        with open(filename,'rb') as datafile:
            data_reader = csv.reader(datafile, delimiter='\t')
            filtered_writer.writerows(data for data in data_reader if (data[0], data[5], data[6], data[10], data[11]) not in filters)
于 2013-11-07T01:04:05.020 回答
0

当您创建时filt,您将创建一个字符串变量并多次覆盖它。尝试更换

for f in filtering:
    filt = f.rstrip().split('\t')

filt = [f.rstrip().split('\t') for f in filtering]

现在filt是一个列表列表,每个元素代表一行。例如,filt[0]会给你第一行,并且filt[2][3]会给你第三行的第四列。您可能必须修改程序的其余部分才能正常工作。

于 2013-11-07T00:20:02.867 回答