我正在寻找一个简单的方法来解析复杂的文本文件到Pandas数据框。下面是一个样本文件,我希望解析后的结果看起来像什么,以及我目前的方法。
有没有什么方法可以让它更简洁/更快/更有说服力/更有可读性?
我也把这个问题放在Code Review上。
我最终写了一个blog article to explain this to beginners。
下面是一个示例文件:
Sample text
A selection of students from Riverdale High and Hogwarts took part in a quiz. This is a record of their scores.
School = Riverdale High
Grade = 1
Student number, Name
0, Phoebe
1, Rachel
Student number, Score
0, 3
1, 7
Grade = 2
Student number, Name
0, Angela
1, Tristan
2, Aurora
Student number, Score
0, 6
1, 3
2, 9
School = Hogwarts
Grade = 1
Student number, Name
0, Ginny
1, Luna
Student number, Score
0, 8
1, 7
Grade = 2
Student number, Name
0, Harry
1, Hermione
Student number, Score
0, 5
1, 10
Grade = 3
Student number, Name
0, Fred
1, George
Student number, Score
0, 0
1, 0
下面是我希望解析后的结果:
Name Score
School Grade Student number
Hogwarts 1 0 Ginny 8
1 Luna 7
2 0 Harry 5
1 Hermione 10
3 0 Fred 0
1 George 0
Riverdale High 1 0 Phoebe 3
1 Rachel 7
2 0 Angela 6
1 Tristan 3
2 Aurora 9
下面是我目前解析它的方法:
import re
import pandas as pd
def parse(filepath):
"""
Parse text at given filepath
Parameters
----------
filepath : str
Filepath for file to be parsed
Returns
-------
data : pd.DataFrame
Parsed data
"""
data = []
with open(filepath, 'r') as file:
line = file.readline()
while line:
reg_match = _RegExLib(line)
if reg_match.school:
school = reg_match.school.group(1)
if reg_match.grade:
grade = reg_match.grade.group(1)
grade = int(grade)
if reg_match.name_score:
value_type = reg_match.name_score.group(1)
line = file.readline()
while line.strip():
number, value = line.strip().split(',')
value = value.strip()
dict_of_data = {
'School': school,
'Grade': grade,
'Student number': number,
value_type: value
}
data.append(dict_of_data)
line = file.readline()
line = file.readline()
data = pd.DataFrame(data)
data.set_index(['School', 'Grade', 'Student number'], inplace=True)
# consolidate df to remove nans
data = data.groupby(level=data.index.names).first()
# upgrade Score from float to integer
data = data.apply(pd.to_numeric, errors='ignore')
return data
class _RegExLib:
"""Set up regular expressions"""
# use https://regexper.com to visualise these if required
_reg_school = re.compile('School = (.*)\n')
_reg_grade = re.compile('Grade = (.*)\n')
_reg_name_score = re.compile('(Name|Score)')
def __init__(self, line):
# check whether line has a positive match with all of the regular expressions
self.school = self._reg_school.match(line)
self.grade = self._reg_grade.match(line)
self.name_score = self._reg_name_score.search(line)
if __name__ == '__main__':
filepath = 'sample.txt'
data = parse(filepath)
print(data)
5条答案
按热度按时间cbjzeqam1#
更新2019(PEG解析器):
这个答案已经引起了相当多的关注,所以我想添加另一个可能性,即解析选项。这里我们可以使用
PEG
解析器(例如parsimonious
)与NodeVisitor
类组合:Regex选项(原始答案)
好吧,看了第x遍《指环王》,我不得不在最后一集前搭一段时间的桥:
分解的思想是把问题分解成几个更小的问题:
1.分开各个学校
1....每个年级
1.......学生和成绩
1.......之后在 Dataframe 中将它们绑定在一起
学校部分(参见a demo on regex101.com)
坡度部分(another demo on regex101.com)
学生/分数部分(last demo on regex101.com):
其余部分是生成器表达式,然后将其馈送到
DataFrame
构造函数(以及列名)。代码:
浓缩:
这产生
至于timing,这是运行一万次的结果:
hlswsv352#
这里是我的建议使用split和pd.concat(“txt”代表问题中原始文本的副本),基本上的想法是通过词组进行拆分,然后concat到 Dataframe 中,最内部的解析利用了名称和等级是csv格式的事实。
csga3l583#
我建议使用像parsy这样的解析器组合子库,与使用正则表达式相比,结果不会那么简洁,但可读性和健壮性要好得多,同时仍然相对较轻。
解析通常是一项非常困难的任务,而且对于一般编程的初学者来说,可能很难找到一种好的方法。
EDIT 2022:完整的示例代码,使用现代Parsy,解析您提供的示例并生成相同的输出。
分为3个阶段:
这种分离意味着在DataFrame级别需要更少的攻击。
fquxozlt4#
我以与原始代码类似的方式定义了解析正则表达式
然后循环遍历这些行,收集每个学生的信息。一旦记录完成(当我们有
Score
时,记录是完整的),我们将记录添加到列表中。一个由逐行正则表达式匹配驱动的小型状态机整理每个记录。特别是我们必须按数字保存一个年级的学生,因为他们的分数和姓名在输入文件中是分开提供的。
最后,记录列表被转换为
DataFrame
。输出:
一些优化方法是先比较最常见的正则表达式,然后显式跳过空行,这样可以避免额外的数据拷贝,但我认为附加到 Dataframe 是一个代价高昂的操作。
vbopmzt15#
这正是Pawpaw设计的问题类型。
Pawpaw是一个高性能的解析和文本分段框架,允许您快速轻松地构建复杂的流水线解析器。段自动组织成树形图,可以使用强大的结构化查询语言进行序列化、遍历和搜索
Here two different Pawpaw-based approaches解决了这个问题。我复制了下面最紧凑的版本,沿着添加了一些树可视化:
代码
产出