如何轻松地将固定宽度的文本文件读入Pandas DataFrames中
照片:Reynier CarlonUnsplash
什么是固定宽度的文本文件?
固定宽度的文件类似于csv文件,但不是使用分隔符,而是每个字段都有一个固定的字符数。这就创造了所有数据整齐排列的文件,在文本编辑器中打开时,其外观类似于电子表格。如果你在文本编辑器中查看原始数据文件,这很方便,但当你需要以编程方式处理数据时就不太理想了。
固定宽度的文件有几个常见的怪癖要记住。
- 当数值没有达到某个字段的总字符数时,就会使用一个填充字符来使字符数达到该字段的总数。
- 任何字符都可以被用作填充字符,只要它在整个文件中是一致的。白色空间是一个常见的填充字符。
- 在一个字段中,数值可以左对齐或右对齐,文件中所有字段的对齐方式必须一致。
这里有关于固定宽度文件的详细描述。
注意:固定宽度文件中的所有字段不需要有相同的字符数。例如:在一个有三个字段的文件中,第一个字段可以是6个字符,第二个是20个,而最后一个是9个。
如何识别固定宽度的文本文件?
经初步检查,当白色空间被用作填充字符时,固定宽度的文件看起来像一个制表符分隔的文件。如果你试图把一个固定宽度的文件读成csv或tsv,并得到混乱的结果,试着用一个文本编辑器打开它。如果数据都整齐地排列着,它可能是一个固定宽度的文件。许多文本编辑器还提供了光标位置的字符数,这使得它更容易发现字符数中的模式。
如果你的文件太大,不容易在文本编辑器中打开,有各种方法可以在命令行中把它的一部分取样到一个单独的小文件中。在Unix/Linux系统中,一个简单的方法是head命令。下面的例子使用带有-n 50的head来读取large_file.txt的前50行,然后将它们复制到一个名为first_50_rows.txt的新文件中。
head -n 50 large_file.txt > first_50_rows.txt
让我们用一个现实生活中的例子文件来工作吧
UniProtKB数据库
UniProt知识库(UniProtKB)是一个可自由访问的全面的蛋白质序列和注释数据数据库,在CC-BY(4.0)许可证下提供。UniProtKB的Swiss-Prot分支对各种生物体的蛋白质信息进行了人工注释和审查。UniProt数据的完整数据集可以从ftp.uniprot.org下载。人类蛋白质的数据包含在一组固定宽度的文本文件 中**:** humchr01.txt - humchr22.txt, humchrx.txt, and humchry.txt。
在这个例子中,我们不需要全部24个文件,所以这里是这组文件中第一个文件的链接。
在用pandas读取文件之前先检查一下该文件
在文本编辑器中快速浏览一下该文件,就会发现一个我们不需要的实质性的标题,然后是6个字段的数据。
humchr01.txt的开头片段
固定宽度的文件似乎不像其他许多数据文件格式那样常见,它们乍一看就像制表符分隔的文件。在尝试用Pandas读取文件之前,用一个好的文本编辑器对文本文件进行目视检查,可以大大减少挫折感,并有助于突出格式化模式。
使用带有默认参数的pandas.read_fwf()。
注意:这个例子的所有代码都是为Python3.6和Pandas1.2.0编写的。
pandas.read_fwf()的文档中列出了5个参数。
filepath_or_buffer, colspecs, widths, infer_nrows, and **kwds
pandas.read_fwf()参数中的两个,colspecs和infer_nrows,有默认值,其作用是根据初始行的采样来推断列。
让我们利用pandas.read_fwf()的默认设置来获得我们整洁的DataFame。我们将colspecs参数保留为默认值 "infer",这又利用了infer_nrows参数的默认值(100)。这两个默认值试图在数据的前100行(在任何跳过的行之后)中找到一个模式,并使用该模式将数据分成若干列。
基本的文件清理
在我们的例子文件中,有几行文件头在表格信息之前。我们需要在读取文件时跳过它们。
在读取文件时,没有一个参数似乎是跳过行的理想选择。那么,我们如何做到这一点呢?我们利用**kwds参数。
方便的是,pandas.read_fwf()使用与pandas.read_table()相同的TextFileReader上下文管理器。结合**kwds参数,我们可以在pandas.read_table()中使用pandas.read_fwf()的参数。所以我们可以使用skiprows参数来跳过例子文件中的前35行。同样地,我们可以使用 skipfooter 参数来跳过示例文件中最后5行,这5行包含了不属于表格数据的页脚。
pandas.read_fwf('humchr01.txt', skiprows=35, skipfooter=5)
上面的尝试让DataFrame有点混乱😔。
注意:由于我们使用的是colspecs和infer_nrows的默认值,所以我们不需要声明它们。
这里的部分问题是,默认的colspecs参数试图根据前100行来推断列宽,但是在表格数据之前的一行(文件中的第36行,在上面的列名中显示)实际上并没有遵循数据表中的字符数模式,所以推断出的列宽被弄乱了。
如果我们把skiprows设置为36而不是35,我们就会把第一行的数据推到列名中,这也会弄乱推断的列宽。如果不进行一些额外的清理,这里就没有赢家。让我们用names参数来解决列名问题,看看这是否有帮助。
注意:使用名称参数意味着我们没有在文件中为列名分配一行,所以我们作为用户必须确保考虑到skiprows必须从第一行数据开始的事实。所以skiprows在下一个例子中被设置为36,但在以前的例子中,当我们没有使用names参数时,它是35。
pandas.read_fwf('humchr01.txt', skiprows=36, skipfooter=5, names=['gene_name', 'chromosomal_position', 'uniprot', 'entry_name', 'mtm_code', 'description'])
这样就好多了,但还是有点乱。Pandas正确地推断出了列的分割,但是把前两个字段推到了索引中。让我们通过设置index_col=False来解决索引的问题。
pandas.read_fwf('humchr01.txt', skiprows=36, skipfooter=5, index_col=False, names=['gene_name', 'chromosomal_position', 'uniprot', 'entry_name', 'mtm_code', 'description'])
这看起来不错!列被正确拆分,列名有意义,DataFrame中的第一行数据与示例文件中的第一行一致。
我们依靠pandas.read_fwf()的两个特定参数的默认设置来获得我们整洁的DataFame。colspecs参数被保留为默认值 "infer",这又利用了infer_nrows参数的默认值,在数据的前100行(跳过的行之后)找到一个模式,并利用这个模式将数据分成几列。默认参数在这个例子文件中运行良好,但我们也可以指定colspecs参数,而不是让pandas推断出列。
用colspecs手动设置字段宽度
就像上面的例子一样,我们需要先进行一些基本的清理工作。我们将在文件中删除页眉和页脚,并像以前一样设置列名。
下一步是用每个字段的间隔建立一个图元列表。下面的列表适合这个例子文件。
colspecs = [(0, 14), (14, 30), (30, 41), (41, 53), (53, 60), (60, -1)]
注意最后一个元组。(60, -1).我们可以用-1来表示最后一个索引值。另外,我们也可以用None代替-1来表示最后的索引值。
注意:当使用colspecs时,元组不一定是排他性的!如果需要的话,最后一列可以被设置为重叠的图元。例如,如果你希望第一个字段重复:colspecs = [(0, 14), (0, 14), ...
pandas.read_fwf('humchr01.txt', skiprows=36, skipfooter=5, colspecs=colspecs, names=['gene_name', 'chromosomal_position', 'uniprot', 'entry_name', 'mtm_code', 'description'])
我们再一次获得了一个整洁的DataFrame。这一次,我们使用colspecs参数明确地声明了我们的字段开始和停止位置,而不是让pandas推断字段。
总结
用Pandas读取固定宽度的文本文件是很容易的。pandas.read_fwf()的默认参数在大多数情况下都是有效的,而且自定义选项也有很好的记录。Pandas库有许多函数可以读取各种文件类型,而pandas.read_fwf()是一个更有用的Pandas工具,要牢记在心。
用Pandas解析固定宽度的文本文件》最初发表在《走向数据科学》杂志上,人们通过强调和回应这个故事来继续对话。