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

Posted

技术标签:

【中文标题】将 CountVectorizer 和 TfidfTransformer 稀疏矩阵转换为单独的 Pandas 数据帧行【英文标题】:Convert CountVectorizer and TfidfTransformer Sparse Matrices into Separate Pandas Dataframe Rows 【发布时间】:2017-10-12 22:48:09 【问题描述】:

问题:将由 sklearn 的 CountVectorizer 和 TfidfTransformer 生成的稀疏矩阵转换为 Pandas DataFrame 列的最佳方法是什么,每个二元组及其对应的频率和 tf-idf 分数都有单独的行? p>

管道:从 SQL DB 中引入文本数据,将文本拆分为 bigram,计算每个文档的频率和每个文档的每个 bigram 的 tf-idf,然后将结果加载回 SQL DB。

当前状态:

引入了两列数据(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

这个 DataFrame 被输入到 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)

然后将矩阵转换为数组后反馈到原始DataFrame中:

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... 

问题:

    特征名称(即二元组)不在 DataFrame 中 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

但是,我不确定如何对这两个数字列执行此操作,并且这不会引入二元组(功能名称)本身。此外,此方法需要一个数组(这就是我首先将稀疏矩阵转换为数组的原因),并且由于性能问题以及我必须剥离无意义的行这一事实,我想尽可能避免这种情况.

非常感谢任何见解!非常感谢您抽出宝贵时间阅读这个问题 - 对于篇幅太长,我深表歉意。如果有什么我可以做的来改进问题或澄清我的流程,请告诉我。

【问题讨论】:

【参考方案1】:

可以使用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

【讨论】:

非常感谢您——这肯定会让一切都变成一个整洁的 DF!问题是,一切都不匹配,number 已与二元组分离(即二元组和number 的随机配对。奇怪的是,现在还有一些空的numbers(在原始数据)。 在对 CountVectorizer 输出进行增密并使用 get_feature_namesnumber 创建数据帧之后,它似乎立即发生了。似乎可能是因为 number 列只是简单地添加而无法确定它应该匹配哪些二元组? 空值正在抛弃它。第一个 null 之前的所有内容都匹配,之后的所有内容都不匹配。为什么会发生空值?我已经过滤掉只有一个单词的行(即不符合二元组的条件)。 我的答案中的代码是在您的示例数据上测试的。 numberbigrams 在您的示例中具有相同的索引 - bigrams 中的每一行不是二元组实例,而是指 data 中的一行。这就是为什么我们可以添加data.number。只有从宽格式到长格式,行才会从 text 变为 text-bigram 对。您能否确认我的答案适用于您发布的 3 行示例数据?如果存在边缘情况或例外情况,例如缺失值,请更新您的帖子以包含一个具有代表性的示例数据集,以展示您所遇到的问题。 我会在调用pd.DataFrame(dt_mat.todense(), ...)tfidf_mat 时添加index=df.index

以上是关于将 CountVectorizer 和 TfidfTransformer 稀疏矩阵转换为单独的 Pandas 数据帧行的主要内容,如果未能解决你的问题,请参考以下文章

了解集群中的 np.zeros

增加 TF IDF 矩阵项的权重

特征工程特征抽取

Spark CountVectorizer

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

我可以同时在测试和训练数据上使用 CountVectorizer 还是需要将其拆分?