【问题标题】:Python 3.5 - Get counter to report zero-frequency itemsPython 3.5 - 获取计数器以报告零频率项目
【发布时间】:2017-12-03 09:12:49
【问题描述】:

由于 PDF 到 txt 的转换错误,我正在对文本进行文本分析,有时会将单词混在一起。所以我不想匹配单词,而是匹配字符串。

例如,我有字符串:

mystring='The lossof our income made us go into debt but this is not too bad as we like some debts.'

然后我搜索

key_words=['loss', 'debt', 'debts', 'elephant']

输出的格式应该是:

Filename Debt Debts Loss Elephant
mystring  2    1     1    0

我的代码运行良好,除了一些小故障:1)它不报告零频率词的频率(所以“大象”不会出现在输出中:2)key_words 中的单词顺序似乎很重要(即,我有时会为“债务”和“债务”分别计算 1 个计数,有时它只报告 2 个“债务”计数,而“未报告债务”。如果我能做到,我可以接受第二点将变量名称“打印”到数据集......但不确定如何。

下面是相关代码。谢谢! PS。不用说,它不是最优雅的一段代码,但我正在慢慢学习。

bad=set(['debts', 'debt'])

csvfile=open("freq_10k_test.csv", "w", newline='', encoding='cp850', errors='replace')
writer=csv.writer(csvfile)
for filename in glob.glob('*.txt'):

    with open(filename, encoding='utf-8', errors='ignore') as f:
      file_name=[]
      file_name.append(filename)

      new_review=[f.read()]
      freq_all=[]
      rev=[]

      from collections import Counter

      for review in new_review:
        review_processed=review.lower()
        for p in list(punctuation):
           review_processed=review_processed.replace(p,'')
           pattern = re.compile("|".join(bad), flags = re.IGNORECASE)
           freq_iter=collections.Counter(pattern.findall(review_processed))           

        frequency=[value for (key,value) in sorted(freq_iter.items())]
        freq_all.append(frequency)
        freq=[v for v in freq_all]

    fulldata = [ [file_name[i]] + freq  for i, freq in enumerate(freq)]  

    writer=csv.writer(open("freq_10k_test.csv",'a',newline='', encoding='cp850', errors='replace'))
    writer.writerows(fulldata)
    csvfile.flush()

【问题讨论】:

  • 我可以指出“Python 3.5 - 获取计数器以报告零频率项目”具有误导性,因为 python 在它的集合中有一个 Counter 并且这个问题与它无关。(参见例如我的回答) 一个更好的问题标题将是例如“Python 3 - 计算子串集的出现次数 - 具有重叠的子串”
  • 要让计数器报告零频率项目(将我带到这里),您需要使用零频率项目对其进行初始化,例如Counter({x:0 for x in list})

标签: python counter word-frequency


【解决方案1】:

您可以预先初始化计数器,如下所示:

freq_iter = collections.Counter()
freq_iter.update({x:0 for x in bad})
freq_iter.update(pattern.findall(review_processed))   

Counter 的一个好处是您实际上不必预先初始化它 - 您可以执行 c = Counter(); c['key'] += 1,但如果您愿意,没有什么可以阻止您将某些值预先初始化为 0。

对于debt/debts 的事情 - 这只是一个没有充分说明的问题。在这种情况下,您想要代码做什么?如果你想让它匹配最长的匹配模式,你需要对列表进行最长排序,这样就可以解决它。如果您想同时报告两者,您可能需要进行多次搜索并保存所有结果。

更新添加了一些关于为什么找不到 debts 的信息:这与正则表达式 findall 的关系比其他任何事情都重要。 re.findall 总是寻找最短的匹配,但一旦找到,它不会将其包含在后续匹配中:

In [2]: re.findall('(debt|debts)', 'debtor debts my debt')
Out[2]: ['debt', 'debt', 'debt']

如果你真的想找到每个单词的所有个实例,你需要分别做:

In [3]: re.findall('debt', 'debtor debts my debt')
Out[3]: ['debt', 'debt', 'debt']

In [4]: re.findall('debts', 'debtor debts my debt')
Out[4]: ['debts']

但是,也许您真正要寻找的是单词。在这种情况下,请使用 \b 运算符来要求分词:

In [13]: re.findall(r'\bdebt\b', 'debtor debts my debt')
Out[13]: ['debt']

In [14]: re.findall(r'(\b(?:debt|debts)\b)', 'debtor debts my debt')
Out[14]: ['debts', 'debt']

我不知道这是否是你想要的......在这种情况下,它能够正确区分debtdebts,但它错过了debtor,因为它只匹配一个子字符串,我们要求它不要这样做。

根据您的用例,您可能想要研究对文本进行词干处理...我相信 nltk 中有一个非常简单(仅使用过一次,因此我不会尝试发布示例... . 这个问题Combining text stemming and removal of punctuation in NLTK and scikit-learn 可能有用),它应该将debtdebtsdebtor 都减少到同一个词根debt,并对其他词做类似的事情。这可能有帮助,也可能没有帮助;我不知道你在用它做什么。

【讨论】:

  • 在计数器中使用零值时要小心。如果你用计数器做一些算术运算,那么keys and values can be silently lost.
  • 谢谢。我必须查看完整列表,看看我是否保留单数/复数。为了我自己的利益,为什么 Counter 没有找到列表中所有字符串的出现,而只保留最短的字符串(即 'debt' 与 'debts')?
  • 我会很好地解决这个解决方案,因为它的工作原理就像一个极少编辑的魅力。不过,我会注意到@wim 强调的警告。
  • 用关于为什么正则表达式不起作用的信息更新了问题。我也不知道 Counter - 很高兴知道! (我一度以为它只是一个defaultdict(int),但它有一些自定义行为,比如能够从列表中更新/初始化,这是 defaultdict 无法做到的......也很高兴知道......
【解决方案2】:

如你所愿:

mystring='The lossof our income made us go into debt but this is not too bad as we like some debts.'
key_words=['loss', 'debt', 'debts', 'elephant']
for kw in key_words:
  count = mystring.count(kw)
  print('%s %s' % (kw, count))

或者说:

from collections import defaultdict
words = set(mystring.split())
key_words=['loss', 'debt', 'debts', 'elephant']
d = defaultdict(int)
for word in words:
  d[word] += 1

for kw in key_words:
  print('%s %s' % (kw, d[kw]))

【讨论】:

  • 在第 2 部分中,您可以将 'dict()' 更改为 'defaultdict(int)' 以摆脱内部的 'if' 语句。
  • 谢谢。我一回到我的电脑就测试一下。
  • 它确实工作得很好,但是,我需要编辑我的代码,而不是使用替代方法。谢谢!
  • 我认为Counter 过去基本上只是一个defaultdict(int),但它还有一些默认字典不支持的其他行为(例如使用列表更新/初始化)。因此,当您不想要这种特殊行为时,这可能是必要的。
【解决方案3】:

一个简洁的解决方案是使用正则表达式:

import regex
mystring='The lossof our income made us go into debt but this is not too bad as we like some debts.'
key_words=['loss', 'debt', 'debts', 'elephant']
print ({k:len(regex.findall(k,mystring,overlapped=True)) for k in key_words})

结果:

{'loss': 1, 'debt': 2, 'debts': 1, 'elephant': 0}

【讨论】:

    【解决方案4】:

    计算出现次数可以用一个简单的单行来完成:

    counts = {k: mystring.count(k) for k in key_words}
    

    将其与 csv.DictWriter 放在一起会产生:

    import csv
    
    mystring = 'The lossof our income made us go into debt but this is not too bad as we like some debts.'
    key_words = ['loss', 'debt', 'debts', 'elephant']
    
    counts = {k: mystring.count(k) for k in key_words}
    print(counts) # {'loss': 1, 'debt': 2, 'debts': 1, 'elephant': 0}
    
    # write out
    with open('out.csv', 'w', newline='') as csv_file:
        writer = csv.DictWriter(csv_file, fieldnames=counts, delimiter=' ')
        # key_words
        writer.writeheader()
        # counts
        writer.writerow(counts)
    
    # out.csv:
    # loss debt debts elephant
    # 1 2 1 0
    

    【讨论】:

      猜你喜欢
      • 2010-10-27
      • 1970-01-01
      • 2022-10-14
      • 2021-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-09
      • 1970-01-01
      相关资源
      最近更新 更多