将CountVectorizer和TfidfTransformer稀疏矩阵转换为单独的Pandas数据帧行

6

问题: 什么是将从sklearn的CountVectorizer和TfidfTransformer得出的稀疏矩阵转换为Pandas DataFrame列的最佳方法,每个bigram及其相应的频率和tf-idf得分都有单独的行?

流程: 从SQL数据库中获取文本数据,将文本拆分成bigrams并计算每个文档的频率和每个bigram在每个文档中的tf-idf,然后将结果加载回SQL数据库。

当前状态:

带入两列数据(numbertext)。通过清理text来产生第三列cleanText

   number                               text              cleanText
0     123            The farmer plants grain    farmer plants grain
1     234  The farmer and his son go fishing  farmer son go fishing
2     345            The fisher catches tuna    fisher catches tuna

这个数据框被输入到sklearn的特征提取器:

cv = CountVectorizer(token_pattern=r"(?u)\b\w+\b", stop_words=None, ngram_range=(2,2), analyzer='word')
dt_mat = cv.fit_transform(data.cleanText)

tfidf_transformer = TfidfTransformer()
tfidf_mat = tfidf_transformer.fit_transform(dt_mat)

然后将这些矩阵反馈到原始的数据框中,并在转换为数组后再进行操作:

data['frequency'] = list(dt_mat.toarray())
data['tfidf_score']=list(tfidf_mat.toarray())

输出:

   number                               text              cleanText  \
0     123            The farmer plants grain    farmer plants grain   
1     234  The farmer and his son go fishing  farmer son go fishing   
2     345            The fisher catches tuna    fisher catches tuna   

               frequency                                        tfidf_score  

0  [0, 1, 0, 0, 0, 1, 0]  [0.0, 0.707106781187, 0.0, 0.0, 0.0, 0.7071067...  
1  [0, 0, 1, 0, 1, 0, 1]  [0.0, 0.0, 0.57735026919, 0.0, 0.57735026919, ...  
2  [1, 0, 0, 1, 0, 0, 0]  [0.707106781187, 0.0, 0.0, 0.707106781187, 0.0... 

问题:

  1. 特征名称(即二元语法)不在数据框中
  2. frequencytfidf_score没有分别列出每个二元语法的行

期望输出:

       number                    bigram         frequency      tfidf_score
0     123            farmer plants                 1              0.70  
0     123            plants grain                  1              0.56
1     234            farmer son                    1              0.72
1     234            son go                        1              0.63
1     234            go fishing                    1              0.34
2     345            fisher catches                1              0.43
2     345            catches tuna                  1              0.43

我使用以下代码将一个数字列分配到DataFrame的不同行:

data.reset_index(inplace=True)
rows = []
_ = data.apply(lambda row: [rows.append([row['number'], nn]) 
                         for nn in row.tfidf_score], axis=1)
df_new = pd.DataFrame(rows, columns=['number', 'tfidf_score'])

输出:

    number  tfidf_score
0      123     0.000000
1      123     0.707107
2      123     0.000000
3      123     0.000000
4      123     0.000000
5      123     0.707107
6      123     0.000000
7      234     0.000000
8      234     0.000000
9      234     0.577350
10     234     0.000000
11     234     0.577350
12     234     0.000000
13     234     0.577350
14     345     0.707107
15     345     0.000000
16     345     0.000000
17     345     0.707107
18     345     0.000000
19     345     0.000000
20     345     0.000000

然而,我不确定如何针对两个数字列执行此操作,并且这不会涉及到 bigram(特征名称)本身。此外,此方法需要一个数组(这就是我在第一次将稀疏矩阵转换为数组时的原因),如果可能的话,我希望避免使用此方法,因为它会存在性能问题,而且我还需要去除无意义的行。
非常感谢您抽出时间来阅读此问题并提供任何见解!如果有任何改进此问题或澄清我的过程的方法,请告诉我。
1个回答

4
大的二元组名称可以使用 CountVectorizerget_feature_names() 方法获取。从那里开始,只需要进行一系列的 meltmerge 操作即可:
print(data)

   number                               text              cleanText
0     123            The farmer plants grain    farmer plants grain
1     234  The farmer and his son go fishing  farmer son go fishing
2     345            The fisher catches tuna    fisher catches tuna

from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer

cv = CountVectorizer(token_pattern=r"(?u)\b\w+\b", stop_words=None, ngram_range=(2,2), analyzer='word')
dt_mat = cv.fit_transform(data.cleanText)

tfidf_transformer = TfidfTransformer()
tfidf_mat = tfidf_transformer.fit_transform(dt_mat)

在这种情况下,CountVectorizer 的特征名称是双词组。
print(cv.get_feature_names())

[u'catches tuna',
 u'farmer plants',
 u'farmer son',
 u'fisher catches',
 u'go fishing',
 u'plants grain',
 u'son go']
CountVectorizer.fit_transform() 返回一个稀疏矩阵。我们可以将其转换为密集表示,放入 DataFrame 中,并将特征名称作为列添加进去。
bigrams = pd.DataFrame(dt_mat.todense(), index=data.index, columns=cv.get_feature_names())
bigrams['number'] = data.number
print(bigrams)

   catches tuna  farmer plants  farmer son  fisher catches  go fishing  \
0             0              1           0               0           0   
1             0              0           1               0           1   
2             1              0           0               1           0   

   plants grain  son go  number  
0             1       0     123  
1             0       1     234  
2             0       0     345  

使用 melt() 将数据从宽格式变为长格式。
然后使用 (query()) 函数筛选出二元匹配结果。
bigrams_long = (pd.melt(bigrams.reset_index(), 
                       id_vars=['index','number'],
                       value_name='bigram_ct')
                 .query('bigram_ct > 0')
                 .sort_values(['index','number']))

    index  number        variable  bigram_ct
3       0     123   farmer plants          1
15      0     123    plants grain          1
7       1     234      farmer son          1
13      1     234      go fishing          1
19      1     234          son go          1
2       2     345    catches tuna          1
11      2     345  fisher catches          1

现在请重复上述过程,针对tfidf:
tfidf = pd.DataFrame(tfidf_mat.todense(), index=data.index, columns=cv.get_feature_names())
tfidf['number'] = data.number

tfidf_long = pd.melt(tfidf.reset_index(), 
                     id_vars=['index','number'], 
                     value_name='tfidf').query('tfidf > 0')

最后,合并bigramstfidf:
fulldf = (bigrams_long.merge(tfidf_long, 
                             on=['index','number','variable'])
                      .set_index('index'))

       number        variable  bigram_ct     tfidf
index                                             
0         123   farmer plants          1  0.707107
0         123    plants grain          1  0.707107
1         234      farmer son          1  0.577350
1         234      go fishing          1  0.577350
1         234          son go          1  0.577350
2         345    catches tuna          1  0.707107
2         345  fisher catches          1  0.707107

似乎是在将CountVectorizer的输出密集化并创建带有“get_feature_names”和“number”的数据框之后立即发生的。这似乎是因为“number”列只是简单地添加而没有办法确定它应该匹配哪些二元组? - OverflowingTheGlass
空值导致了错误。第一个空值之前的所有内容都是匹配的,但之后的内容就不匹配了。为什么会有空值呢?我已经过滤掉了只有一个单词的行(即不符合二元模型要求的行)。 - OverflowingTheGlass
我的答案中的代码已在您的示例数据上进行了测试。numberbigrams在您的示例中具有相同的索引 - bigrams中的每一行不是一个bigram实例,而是指向data中的一行。这就是为什么我们可以直接添加data.number的原因。只有在从宽格式转换为长格式时,行才会从text变为text-bigram对。您能否确认我的答案在您发布的3行示例数据上有效?如果存在边缘情况或异常情况(例如缺少值),请更新您的帖子以包含演示您所看到的问题的代表性样本数据集。 - andrew_reece
1
在调用 pd.DataFrame(dt_mat.todense(), ...)tfidf_mat 时,我会添加 index=df.index - MaxU - stand with Ukraine
额外的微调起作用了,谢谢!还要感谢Andrew提供原始详细答案。现在我面临的问题是,在尝试处理10000多条记录时,它会耗尽内存。我猜测被创建的密集数组太大了。 - OverflowingTheGlass
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接