我正在寻找一种将复杂文本文件解析为 pandas DataFrame 的简单方法。下面是一个示例文件,我希望解析后的结果看起来像什么,以及我当前的方法。
有没有办法让它更简洁/更快/更pythonic/更具可读性?
我也把这个问题放在了 Code Review 上。
我最终写了 一篇博客文章来向初学者解释这一点。
这是一个示例文件:
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)
原文由 bluprince13 发布,翻译遵循 CC BY-SA 4.0 许可协议
2019 年更新(PEG 解析器):
这个答案受到了相当多的关注,所以我想添加另一种可能性,即解析选项。在这里,我们可以使用
PEG
解析器代替(例如parsimonious
)结合NodeVisitor
类:正则表达式选项(原始答案)
好吧,第 x 次看指环王,我不得不花一些时间到最后:
分解来看,这个想法是将问题分解成几个较小的问题:
学校部分(请参阅 regex101.com 上的演示)
成绩部分( regex101.com 上的另一个演示)
学生/分数部分( regex101.com 上的最后一个演示):
其余部分是一个生成器表达式,然后将其输入
DataFrame
构造函数(连同列名)。代码:
浓缩:
这产生
至于 计时,这是运行一万次的结果: