使用正则表达式有选择地将数据提取到熊猫数据框

时间:2019-05-21 13:43:06

标签: python regex pandas

我正在使用正则表达式和熊猫来读取文件中的文本行,并有选择地将数据拉入数据框。

说我有以下文本行

Name : "Bob" Occupation : "Builder" Age : "42" Name : "Jim" Occupation : "" Age : "25"

我想将所有这些信息提取到一个数据框中,使其看起来如下所示:

Name    Occupation    Age
Bob      Builder       42

由于他们的职业空白,我想忽略阅读有关第二人称的任何信息。

代码:

with open(txt, 'r') as txt
    for line in txt:
        line = line.strip
        a = re.findall(r'Name : \"(\S+)\"', line)
        if a:
            b = re.findall(r'Occupation : \"(\S+)\"', line)
            if b:
                c = re.findall(r'Age : \"(\S+)\"', line)
                if c:
                    df = df.append({'Name' : a, 'Occupation' : b, 'Age' : c}, ignore_index = True)

这将返回以下(错误的)数据帧

    Name        Occupation      Age
["Bob", "Jim"]  ["Builder"]  ["42","25"]

我想修改此代码,以使其永远不会包含“ Jim”所在的情况。即,如果此人没有“职业”,则不要将其信息读入数据框中。您还可以看到此代码是错误的,因为它现在说“ Jim”的职业为“ Builder”。

如果给我下面的文字:

Name : "Bob" Occupation : "Builder" Age : "42" Name : "Jim" Occupation : "" Age : "25" Name : "Steve" Occupation : "Clerk" Age : "110"

生成的df为:

    Name              Occupation             Age
["Bob", "Steve"]  ["Builder", "Clerk"]  ["42","110"]

这很方便,因为我不再遇到任何索引问题,因此我可以将此df扩展到我的最终目标(知道如何做):

Name  Occupation  Age
Bob   Builder     42
Steve Clerk       110

3 个答案:

答案 0 :(得分:2)

根据您的评论,三个键NameOccupationAge总是相同的顺序,因此我们可以使用单个正则表达式模式来检索字段值,同时确保匹配的值是非空的。以下是使用Series.str.extractall()的示例:

# example texts copied from your post
str="""
Name : "Bob" Occupation : "Builder" Age : "42" Name : "Jim" Occupation : "" Age : "25" Name : "Steve" Occupation : "Clerk" Age : "110"
Name : "Bob" Occupation : "Builder" Age : "42" Name : "Jim" Occupation : "" Age : "25"
"""

# read all lines into one field dataframe with column name as 'text'
df = pd.read_csv(pd.io.common.StringIO(str), squeeze=True, header=None).to_frame('text')

# 3 fields which have the same regex sub-pattern
fields = ['Name', 'Occupation', 'Age']

# regex pattern used to retrieve values of the above fields. There are 3 sub-patterns
# corresponding to the above 3 fields and joined by at least one white spaces(\s+)
ptn = r'\s+'.join([ r'{0}\s*:\s*"(?P<{0}>[^"]+)"'.format(f) for f in fields ])
print(ptn)
#Name\s*:\s*"(?P<Name>[^"]+)"\s+Occupation\s*:\s*"(?P<Occupation>[^"]+)"\s+Age\s*:\s*"(?P<Age>[^"]+)"

位置:

  • 子模式Name\s*:\s*"(?P<Name>[^"]+)" Name : "([^"]+)" 基本相同,但是可选地,可以使用 0 来实现更多冒号:和命名的捕获组周围的空白。
  • +中的加号 "([^"]+)" 是为了确保用双引号引起来的值不是EMPTY,因此将跳过Jim的个人资料,因为他的职业是空的。
  • 使用命名捕获组,以便我们在运行Series.str.extractall()之后可以拥有正确的列名,否则生成的列名将默认为01和{{1} }。

然后您可以从Series.str.extractall()检查结果:

2

删除1级索引,您将获得一个具有原始索引的数据框。如果您的任务中使用了其他列,则可以将其重新连接到原始数据框。

df['text'].str.extractall(ptn)
          Name Occupation  Age
  match
0 0        Bob    Builder   42
  1      Steve      Clerk  110
1 0        Bob    Builder   42

答案 1 :(得分:0)

使用正则表达式-> re.finditer进行正则表达式分组。

例如:

import re
import pandas as pd

s = 'Name : "Bob" Occupation : "Builder" Age : "42" Name : "Jim" Occupation : "" Age : "25"'

name = re.findall(r'Name : \"(.*)\" ', s)
occupation = re.findall(r'Occupation : \"(.*)\" ', s)
age = re.findall(r'Age : \"(.*)\" ', s)

regexPattern = re.compile(r'Name : \"(?P<name>.*?)\"\s+Occupation : \"(?P<occupation>.*?)\"\s+Age : \"(?P<age>.*?)\"')

df = pd.DataFrame([i.groupdict() for i in regexPattern.finditer(s) if len(filter(None, i.groupdict().values())) == 3])
print(df)

输出:

  age name occupation
0  42  Bob    Builder

答案 2 :(得分:0)

您说这些字符串具有固定格式,首先是Name,然后是Occupation,然后是Age。您可以使用

df = pd.DataFrame()
pat = r'Name\s*:\s*"([^"]+)"\s*Occupation\s*:\s*"([^"]+)"\s*Age\s*:\s*"(\d+)"'
s='Name : "Bob" Occupation : "Builder" Age : "42" Name : "Jim" Occupation : "" Age : "25" Name : "Steve" Occupation : "Clerk" Age : "110"'
for name, occupation, age in re.findall(pat, s):
    df = df.append({'Name' : name, 'Occupation' : occupation, 'Age' : age}, ignore_index = True)

输出:

>>> df
   Age   Name Occupation
0   42    Bob    Builder
1  110  Steve      Clerk

正则表达式为

Name\s*:\s*"([^"]+)"\s*Occupation\s*:\s*"([^"]+)"\s*Age\s*:\s*"(\d+)"

请参见regex demo。由于捕获组中的量词设置为+(一次或多次出现),因此这些值永远不会为空。为避免前两个值都是空值,可以将模式更改为Name\s*:\s*"([^"]*[^\s"][^"]*)"\s*Occupation\s*:\s*"([^"]*[^\s"][^"]*)"\s*Age\s*:\s*"(\d+)",请参见this demo

详细信息

  • Name-Name
  • \s*:\s*-:内含0+空格
  • "-双引号
  • ([^"]+)-第1组:除"之外的一个或多个字符
  • "-双引号
  • \s*-超过0个空格
  • Occupation\s*:\s*"
  • ([^"]+)-第2组:除"之外的一个或多个字符
  • "\s*Age\s*:\s*"-",0+空格,Age:,并用0+空格包围,然后用"
  • (\d+)-第3组:一个或多个数字
  • "-双引号