【问题标题】:Count matching words and assign the count to each matching word column in Python计算匹配词并将计数分配给 Python 中的每个匹配词列
【发布时间】:2021-09-15 22:37:14
【问题描述】:

我了解如何使用预定义的单词列表计算 pandas 列中匹配的单词并将计数分配到另一列(类似于this post here)。但我想知道是否有一种方法或函数可以将计数分配给按列样式匹配的单词列。

index | text
1     | "I have a pen and ipod, but I lost it today."
2     | "I have pineapple and pen, but I lost it today."
long_list = ['pen', 'pineapple', 'ipod']
index | text                                             | pen | pineapple | ipod |
1     | "I have a pen and ipod, but I lost it today."    | 1   |    0      |   1  |
2     | "I have pineapple and pen, but I lost it today." | 1   |    1      |   0  |

【问题讨论】:

  • 如果一行中有重复的单词(例如2),你要算1还是2?
  • 好问题。在这种情况下,我会将它们算作 1。@SeaBean
  • 部分单词匹配怎么样? pencil 是否应该与 pen 匹配?
  • 另一个好问题。是的,我确实想引入模糊匹配——比如pencilspencilPencils。 @SeaBean
  • 那很好。允许部分单词匹配并且多次出现只计算一次。公认的解决方案在这方面效果很好。 :-)

标签: python pandas


【解决方案1】:

这是使用extractnamed capturing groups 的简洁解决方案:

regex = '|'.join(map(lambda i: f'(?P<{i}>{i})', long_list))
df.join(df['text'].str.extract(regex).notnull().astype(int))

输出:

index                                            text  pen  pineapple  ipod
    1     I have a pen and ipod, but I lost it today.    1          0     0
    2  I have pineapple and pen, but I lost it today.    0          1     0

如果单词包含无效字符,也可以使用未命名的捕获组(它们将编号为 0/1/2/3),然后重命名列:

long_list = ['pen', 'pineapple', 'ipod', 'cheese cake']
regex = '|'.join(map(lambda x: f'({x})', long_list))
df.join(df['text'].str.extract(regex)
                  .notnull().astype(int)
                  .rename(columns=dict(enumerate(long_list)))
        )

输出:

index              text  pen  pineapple  ipod  cheese cake
    1  I have a pen ...    1          0     0            0
    2  I have pineap...    0          1     0            0

它是如何工作的

extract 将为每个捕获组创建一个列,其中组名作为列名,单元格中匹配的字符串,否则为 NaN。然后我们使用notnull+astype(int)将这个输出转换为整数

正则表达式注释

注意。正则表达式的形式为'(?P&lt;pen&gt;pen)|(?P&lt;pineapple&gt;pineapple)|(?P&lt;ipod&gt;ipod)'

为了确保整个单词都匹配(即铅笔不应该与钢笔匹配),让我们添加单词边界 (\b):

regex = '|'.join(map(lambda i: fr'(?P<{i}>\b{i}\b)', long_list))

给出:'(?P&lt;pen&gt;\\bpen\\b)|(?P&lt;pineapple&gt;\\bpineapple\\b)|(?P&lt;ipod&gt;\\bipod\\b)'

如果使用的单词包含空格(或在 python 变量中无效的字符),则应替换/删除这些单词:

regex = '|'.join(map(lambda i: fr'(?P<{i.replace(" ", "_")}>\b{i}\b)', long_list))

计数出现次数的变体

df.join(df['text']
          .str.extractall(regex)
          .notnull().astype(int)
          .groupby(level=0).sum()
       )

输出(我将输入修改为在第一行有两个“笔”):

index                                               text  pen  pineapple  ipod
    1  I have a pen and another pen an ipod, but I lo...    2          0     1
    2     I have pineapple and pen, but I lost it today.    1          1     0

【讨论】:

  • 如有必要,还可以添加单词边界\b 以排除部分单词匹配:-)
  • 好吧,这对重复出现的情况不起作用
  • @U12-Forward 我认为这不是要求,但如果需要,很容易添加此功能
  • 如果 OP 想要匹配 IGNORECASE,您可以将 (?i) 添加到正则表达式。 :-)
  • @codedancer 这是因为python变量中不允许有空格,我添加了一个解决方案来转义它,其他无效字符也需要这个,以及使用未命名捕获的一般解决方案列的组和重命名.
【解决方案2】:

试试pd.get_dummiesstr.findall

>>> df.join(pd.get_dummies(df['text'].str.findall(f'({"|".join(long_list)})').explode()).groupby(level=0).sum())
   index                                            text  ipod  pen  pineapple
0      1     I have a pen and ipod, but I lost it today.     1    1          0
1      2  I have pineapple and pen, but I lost it today.     0    1          1
>>> 

这不需要 for 循环。

【讨论】:

  • @mozway 对你也一样:P!
【解决方案3】:

您可以尝试使用str.contains

for i in long_list:
    df.loc[df.text.str.contains(i), i] = 1

【讨论】:

  • 我喜欢你的方法,因为它非常简洁。但它在那些没有计数的单元格中分配“NaN”而不是“0”。
  • @codedancer 如果需要,您可以随时使用fillna(0),但是对于长列表,这种方法可能会很慢,因为它会循环匹配要匹配的元素(它会检查每个字符串是否匹配的整个数据)
猜你喜欢
  • 1970-01-01
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
  • 2013-12-25
  • 2022-06-21
  • 2016-04-15
  • 1970-01-01
  • 2015-12-21
相关资源
最近更新 更多