【问题标题】:How to count unique words from a text file after a specific string in every line?如何在每一行中的特定字符串之后计算文本文件中的唯一单词?
【发布时间】:2018-07-25 19:01:54
【问题描述】:

这里是 Python 新手:

我有一个如下所示的文本文件:

{'{http://www.omg.org/XMI}id': '18836', 'sofa': '12', 'begin': '27', 'end': '30', 'Character': 'Jonathan'} 
{'{http://www.omg.org/XMI}id': '18836', 'sofa': '12', 'begin': '27', 'end': '30', 'Character': 'Jonathan'} 
{'{http://www.omg.org/XMI}id': '18828', 'sofa': '12', 'begin': '31', 'end': '37', 'Character': 'Joyce'} 
{'{http://www.omg.org/XMI}id': '18828', 'sofa': '12', 'begin': '31', 'end': '37', 'Character': 'Joyce'} 
{'{http://www.omg.org/XMI}id': '18918', 'sofa': '12', 'begin': '81', 'end': '95', 'Character': 'Will'} 
{'{http://www.omg.org/XMI}id': '19012', 'sofa': '12', 'begin': '155', 'end': '158', 'Character': 'Jonathan'} 
{'{http://www.omg.org/XMI}id': '19050', 'sofa': '12', 'begin': '239', 'end': '242', 'Character': 'Nancy'} 
{'{http://www.omg.org/XMI}id': '19111', 'sofa': '12', 'begin': '845', 'end': '850', 'Character': 'Steve'} 

等等

我希望能够计算独特角色的名称并计算它们的出现次数。如:忽略每一行中的所有内容,直到字符串 'Character': ,因此只考虑字符的名称。

到目前为止,在尝试了许多其他方法(包括 RegEx)之后,我得到了这段代码,但没有得到想要的结果(它打印并计算所有内容):

import re
from collections import Counter
import tkFileDialog

filename = tkFileDialog.askopenfilename()

f = open(filename, "r")

lines = f.readlines()

f.close()


cnt = Counter()

for line in lines:
    cnt[line.split("'Character':", 2)] +=1

print cnt
print sum(cnt.values())

最佳输出应该是这样的:

Jonathan: 3
Joyce: 2
Will: 1
Nancy: 1
Steve: 1

任何形式的帮助或提示将不胜感激!

编辑:上面的文本文件是从一个 .xmi 文件生成的,该文件的信息不易阅读。正如我在对以下答案之一的评论中提到的那样:这是我第一次尝试以视觉方式表示想要的组合信息的方法。我不确定是否有更好的方法来表示这些数据,而不是在文本文件中能够使用它。可以为此创建一个新的 .xmi 文件吗?

因此,根据要求,这是我将 .xmi 文件生成到文本文件的代码:

# coding: utf-8

# In[ ]:

import xml.etree.cElementTree as ET
from xml.etree.ElementTree import (Element, ElementTree, SubElement, Comment, tostring)

ET.register_namespace("pos","http:///de/tudarmstadt/ukp/dkpro/core/api/lexmorph/type/pos.ecore")
ET.register_namespace("tcas","http:///uima/tcas.ecore")
ET.register_namespace("xmi","http://www.omg.org/XMI")
ET.register_namespace("cas","http:///uima/cas.ecore")
ET.register_namespace("tweet","http:///de/tudarmstadt/ukp/dkpro/core/api/lexmorph/type/pos/tweet.ecore")
ET.register_namespace("morph","http:///de/tudarmstadt/ukp/dkpro/core/api/lexmorph/type/morph.ecore")
ET.register_namespace("dependency","http:///de/tudarmstadt/ukp/dkpro/core/api/syntax/type/dependency.ecore")
ET.register_namespace("type5","http:///de/tudarmstadt/ukp/dkpro/core/api/semantics/type.ecore")
ET.register_namespace("type6","http:///de/tudarmstadt/ukp/dkpro/core/api/syntax/type.ecore")
ET.register_namespace("type2","http:///de/tudarmstadt/ukp/dkpro/core/api/metadata/type.ecore")
ET.register_namespace("type3","http:///de/tudarmstadt/ukp/dkpro/core/api/ner/type.ecore")
ET.register_namespace("type4","http:///de/tudarmstadt/ukp/dkpro/core/api/segmentation/type.ecore")
ET.register_namespace("type","http:///de/tudarmstadt/ukp/dkpro/core/api/coref/type.ecore")
ET.register_namespace("constituent","http:///de/tudarmstadt/ukp/dkpro/core/api/syntax/type/constituent.ecore")
ET.register_namespace("chunk","http:///de/tudarmstadt/ukp/dkpro/core/api/syntax/type/chunk.ecore")
ET.register_namespace("custom","http:///webanno/custom.ecore")

def sofa(annotation):
    f = open(annotation)
    tree = ET.ElementTree(file=f)
    root = tree.getroot()

    node = root.find("{http:///uima/cas.ecore}Sofa") # we remove cas:View
    return node.attrib['sofaString']

path ="valhalla.xmi"
with open(path, 'r', encoding="utf-8") as filename:
    tree = ET.ElementTree(file=filename)
    root = tree.getroot()

ns = {'emospan': 'http:///webanno/custom.ecore', 
      'id':'http://www.omg.org/XMI',
      'relspan': 'http:///webanno/custom.ecore',
      'sentence': 'http:///de/tudarmstadt/ukp/dkpro/core/api/segmentation/type.ecore',
      'annotator': "http:///de/tudarmstadt/ukp/dkpro/core/api/metadata/type.ecore"}
my_id = '{http://www.omg.org/XMI}id'


top = Element('corpus', encoding="utf-8") 
text = sofa(path).replace("\n"," ")

def stimcount():
    with open('results.txt', 'w') as f:
        for rel_node in root.findall("emospan:CharacterRelation",ns):
            if rel_node.attrib['Relation']=="Stimulus":
                source = rel_node.attrib['Governor']
                target = rel_node.attrib['Dependent']
                for span_node in root.findall("emospan:CharacterEmotion",ns):
                    if span_node.attrib[my_id]==source:

                        print(span_node.attrib['Emotion'])

                    if span_node.attrib[my_id]==target:
                        print(span_node.attrib)
                        print(span_node.attrib, file=f)

【问题讨论】:

  • 您的文本文件似乎是 json 格式。使用 json 包可能会更容易读取键/值对,然后是实际的文本数据。
  • @Karl 它不是 json,因为它使用单引号。
  • 您可以做的是使用正则表达式来捕获名称,然后尝试将名称作为键添加到字典中。如果成功,则将值设置为 1。如果失败,则找到具有该名称的键并增加值。
  • @nosklo 我真的不知道 json - 这是唯一的区别吗?那么使用json或者pandas from_json应该是简单的字符替换问题吧?
  • @SpghttCd 这是我在示例数据中可以看到的唯一区别 - 但根据字典内容可能存在其他差异。正确的解决方法是首先生成一个 json!

标签: python regex file count unique


【解决方案1】:

这是一个正则表达式解决方案:

file_stuff = """{'{http://www.omg.org/XMI}id': '18836', 'sofa': '12', 'begin': '27', 'end': '30', 'Character': 'Jonathan'}
{'{http://www.omg.org/XMI}id': '18836', 'sofa': '12', 'begin': '27', 'end': '30', 'Character': 'Jonathan'}
{'{http://www.omg.org/XMI}id': '18828', 'sofa': '12', 'begin': '31', 'end': '37', 'Character': 'Joyce'}
{'{http://www.omg.org/XMI}id': '18828', 'sofa': '12', 'begin': '31', 'end': '37', 'Character': 'Joyce'}
{'{http://www.omg.org/XMI}id': '18918', 'sofa': '12', 'begin': '81', 'end': '95', 'Character': 'Will'}
{'{http://www.omg.org/XMI}id': '19012', 'sofa': '12', 'begin': '155', 'end': '158', 'Character': 'Jonathan'}
{'{http://www.omg.org/XMI}id': '19050', 'sofa': '12', 'begin': '239', 'end': '242', 'Character': 'Nancy'}
{'{http://www.omg.org/XMI}id': '19111', 'sofa': '12', 'begin': '845', 'end': '850', 'Character': 'Steve'}"""

import re
from collections import Counter

r = re.compile("(?<=\'Character\'\:\s\')\w+(?=\')")
# EDIT: use "(?<=\'Character\'\:\s\')(.+)(?=\')" to match names with quotes...
# or other characters, as pointed out in comments.
print(Counter(r.findall(file_stuff)))
# Counter({'Jonathan': 3, 'Joyce': 2, 'Will': 1, 'Nancy': 1, 'Steve': 1})

【讨论】:

  • 如果角色名称中包含引号将失败
  • @nosklo 没错!我的猜测是我们可以将\w+ 替换为(.+),我将编辑答案,谢谢。
  • 又好又短。但是不应该可以额外检测包含除'之外的所有字符的名称吗?例如。 C3PO,E.T.或 T1000
【解决方案2】:

您的原始文本文件非常可悲,因为它似乎包含以文本格式编写的 python dicts 的表示,每行一个!

这是一种非常糟糕的生成文本数据文件的方法。您应该更改生成此文件的代码,以生成另一种格式,如 csv 或 json 文件,而不是天真地将字符串表示形式写入文本文件。如果您使用 csv 或 json,那么您已经编写并测试了一些库来帮助您解析内容并轻松提取每个元素。

如果你仍然想要,你可以使用 ast.literal_eval 来实际运行每一行的代码:

import ast
import collections
with open(filename) as infile:
     print(collections.Counter(ast.literal_eval(line)['Character'] for line in infile))

编辑:既然您添加了文件生成的示例,我可以建议您使用另一种格式,例如 json:

def stimcount():
    results = []
    for rel_node in root.findall("emospan:CharacterRelation",ns):
        if rel_node.attrib['Relation']=="Stimulus":
            source = rel_node.attrib['Governor']
            target = rel_node.attrib['Dependent']
            for span_node in root.findall("emospan:CharacterEmotion",ns):
                if span_node.attrib[my_id]==source:

                    print(span_node.attrib['Emotion'])

                if span_node.attrib[my_id]==target:
                    print(span_node.attrib)
                    results.append(span_node.attrib)

    with open('results.txt', 'w') as f:
        json.dump(results, f)

那么你读取数据的代码可以很简单:

with open('results.txt') as f:
    results = json.load(f)
r = collections.Counter(d['Character'] for d in results)
for n, (ch, number) in enumerate(r.items()): 
    print('{} - {}, {}'.format(n, ch, number))

另一种选择是使用 csv 格式。它允许您指定感兴趣的列列表并忽略其余列:

def stimcount():
    with open('results.txt', 'w') as f:
        cf = csv.DictWriter(f, ['begin', 'end', 'Character'], extrasaction='ignore')
        cf.writeheader()
        for rel_node in root.findall("emospan:CharacterRelation",ns):
            if rel_node.attrib['Relation']=="Stimulus":
                source = rel_node.attrib['Governor']
                target = rel_node.attrib['Dependent']
                for span_node in root.findall("emospan:CharacterEmotion",ns):
                    if span_node.attrib[my_id]==source:

                        print(span_node.attrib['Emotion'])

                    if span_node.attrib[my_id]==target:
                        print(span_node.attrib)
                        cf.writerow(span_node.attrib)

然后轻松阅读:

with open('results.txt') as f:
    cf = csv.DictReader(f)
    r = collections.Counter(d['Character'] for d in cf)
    for n, (ch, number) in enumerate(r.items()): 
        print('{} - {}, {}'.format(n, ch, number))

【讨论】:

  • 我从一个 .xmi 文件创建了这个文本文件。这是第一次尝试表示一些组合的所需数据的方法,这些数据通常是分散的,以便我至少可以直观地表示我正在寻找的结果。
  • @Waldkamel 如果您可以显示生成文本文件的代码,我们可以提供一种以通用 json 或 csv 格式生成它的方法,然后您可以更可靠、更轻松地以任何语言阅读它你想要
  • 我现在在问题部分添加了它。我只包含了一个函数作为示例;还有其他类似的形式。
  • 非常感谢!你的回答对我帮助最大:)
  • 到目前为止,它计算了每个唯一字符的出现次数。有没有办法让它把不同的独特角色也算作物品?例如#, 字符, 出现 | 1,“乔纳森”,20 | 2,“乔伊斯”,14 | 3, '意志', 5 |我试过 sum().values() 但它只是总结了出现的总数。
【解决方案3】:

使用astcollections 模块

例如:

import ast
from collections import defaultdict

d = defaultdict(int)
with open(filename) as infile:
    for line in infile:
        val = ast.literal_eval(line)
        d[val["Character"]] += 1
print(d)

输出:

defaultdict(<type 'int'>, {'Will': 1, 'Steve': 1, 'Jonathan': 3, 'Nancy': 1, 'Joyce': 2})

【讨论】:

  • 它工作了一段时间,然后我每次运行代码时都开始收到此错误消息:“val = ast.literal_eval(line) File "C:\Python27\lib\ast.py",第 80 行,在 literal_eval 中返回 _convert(nodoe_or_string) 文件“C:\Python27\lib\ast.py”,第 79 行,在 _convert 中引发 ValueError('malformed string') ValueError: malformed string"
【解决方案4】:

如果您愿意,您也可以拥有pandas 解决方案...:

txt = """{'{http://www.omg.org/XMI}id': '18836', 'sofa': '12', 'begin': '27', 'end': '30', 'Character': 'Jonathan'}
{'{http://www.omg.org/XMI}id': '18836', 'sofa': '12', 'begin': '27', 'end': '30', 'Character': 'Jonathan'}
{'{http://www.omg.org/XMI}id': '18828', 'sofa': '12', 'begin': '31', 'end': '37', 'Character': 'Joyce'}
{'{http://www.omg.org/XMI}id': '18828', 'sofa': '12', 'begin': '31', 'end': '37', 'Character': 'Joyce'}
{'{http://www.omg.org/XMI}id': '18918', 'sofa': '12', 'begin': '81', 'end': '95', 'Character': 'Will'}
{'{http://www.omg.org/XMI}id': '19012', 'sofa': '12', 'begin': '155', 'end': '158', 'Character': 'Jonathan'}
{'{http://www.omg.org/XMI}id': '19050', 'sofa': '12', 'begin': '239', 'end': '242', 'Character': 'Nancy'}
{'{http://www.omg.org/XMI}id': '19111', 'sofa': '12', 'begin': '845', 'end': '850', 'Character': 'Steve'}"""

import pandas as pd

# replace the StringIO-stuff by your file-path
df = pd.read_table(StringIO(txt), sep="'Character': '", header=None, usecols=[1])
            1
0  Jonathan'}
1  Jonathan'}
2     Joyce'}
3     Joyce'}
4      Will'}
5  Jonathan'}
6     Nancy'}
7     Steve'}

df = df[1].str.split('\'', expand=True)
          0  1
0  Jonathan  }
1  Jonathan  }
2     Joyce  }
3     Joyce  }
4      Will  }
5  Jonathan  }
6     Nancy  }
7     Steve  }

df.groupby(0).count()
          1
0          
Jonathan  3
Joyce     2
Nancy     1
Steve     1
Will      1

这个想法是将文件读取为两列separated by 'Character': ',然后仅导入第二列 (usecols)。
然后split 再次'
其余普通groupby/count

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-30
    • 2012-08-04
    • 1970-01-01
    • 2022-05-31
    • 2021-09-05
    相关资源
    最近更新 更多