我们想从用户的角度了解有关竞品以及市场的信息
Ukulele 属性简介
看一下亚马逊评论的构成,我们主要的分析对象是评论正文
项目需求:
技术实现:
机器不懂人类的自然语言,我们要将自然语言转换为机器易于理解的“语言”,NLP(Natural language processing)
NLP 里面,最细粒度的是词语,词语组成句子,句子再组成段落、篇章、文档。所以处理 NLP 的问题,首先要处理词语
词语处理的最终目标是用向量来表示词语
①获取原始文本 : Yes, Everjoys ukulele is the right Soprano Ukulele I have been looking for. It arrived earlier and very well packed, just the way I expected.
② 分词:[‘Yes’,’,’, ‘Everjoys’, ‘ukulele’, ‘is’, ‘the’, ‘right’, ‘Soprano’, ‘Ukulele’, ‘I’, ‘have’, ‘been’, ‘looking’, ‘for.’, ‘It’, ‘arrived’, ‘earlier’, ‘and’, ‘very’, ‘well’, ‘packed,’, ‘just’, ‘the’, ‘way’, ‘I’, ‘expected.’]
jieba
③ 向量化编码:[1,0,1,0,1,0,1,0……]
男[1,0]
女[0,1]
北京[1,0,0]
上海[0,1,0]
深圳[0,0,1]
onehot编码
pd.get_dummies()
分词阶段需要处理的内容:
首先将一句话拆分成一个一个单词,英文分词很简单,直接通过空格就可以,中文分词可以借助jieba这样的三方库
I’d I would
I have
接下来我们需要对有时态变化的单词还原成未变化的单词
词干提取 – Stemming
词干提取是去除单词的前后缀得到词根的过程。
大家常见的前后词缀有「名词的复数」、「进行式」、「过去分词」…
词形还原 – Lemmatisation
词形还原是基于词典,将单词的复杂形态转变成最基础的形态。
词形还原不是简单地将前后缀去掉,而是会根据词典将单词进行转换。比如「drove」会转换为「drive」。
获取原始单词之后还需要去掉停用词和一些助词,虚词,连词
上述过程都有三方库帮助我们实现
import pandas as pd
import re
import math
import datetime
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('fivethirtyeight')
import warnings
# 忽略不必要的warnings
warnings.filterwarnings('ignore')
# nltk:文本处理的包
from nltk.stem.wordnet import WordNetLemmatizer # 词性还原
from nltk.corpus import wordnet as wn
from collections import Counter
import pyecharts.options as opts
from pyecharts.charts import WordCloud
%matplotlib inline
df_reviews=pd.read_csv('data/reviews.csv')
df_reviews.head()
df_reviews.info()
# 对于评论内容空缺的,直接删除
df_reviews = df_reviews.dropna()
# 截取评论中的星级数据
def get_stars(n):
return float(n.replace(' out of 5 stars',''))
# 根据评星数量获取评价属性, 好评(4分及以上), 中评(3分), 差评(2分及以下)
def stars_cat(n):
'''
评分转换为好中差评 1分2分为差评, 3分中评, 4分5分好评
'''
if n<=2:
return '差评'
elif n ==3:
return '中评'
else:
return '好评'
# 获取评论中的日期信息,转换成日期时间格式
def get_date(x):
'''
处理评论日期 Reviewed in the United States on June 24, 2020
先用 'on ' 去拆分, 把日期文本拆分成两部分
再用', '拆分, 把后面的部分拆分成 ['月 日','年']
最后把前面的'月 日' 用空格拆分成 月 日
'''
x = x.split('on ')[1] # 把数据拆分成两部分 ['Reviewed in the United States on ','June 24, 2020']
x = x.split(', ')
y= x[1]
x = x[0].split(' ')
m,d = x[0],x[1]
if m=='January' or m=='Jan':
on_date='01-'+d+'-'+y
elif m=='February' or m=='Feb':
on_date='02-'+d+'-'+y
elif m=='March' or m=='Mar':
on_date='03-'+d+'-'+y
elif m=='April' or m=='Apr':
on_date='04-'+d+'-'+y
elif m=='May':
on_date='05-'+d+'-'+y
elif m=='June' or m=='Jun':
on_date='06-'+d+'-'+y
elif m=='July' or m=='Jul':
on_date='07-'+d+'-'+y
elif m=='August' or m=='Aug':
on_date='08-'+d+'-'+y
elif m=='September' or m=='Sep':
on_date='09-'+d+'-'+y
elif m=='October' or m=='Oct':
on_date='10-'+d+'-'+y
elif m=='November' or m=='Nov':
on_date='11-'+d+'-'+y
elif m=='December' or m=='Dec':
on_date='12-'+d+'-'+y
#on_date=datetime.datetime.strptime(on_date, '%m-%d-%Y').strftime('%Y-%m-%d')
return on_date
# 字数统计
df_reviews['stars_num']=df_reviews['stars'].apply(get_stars)
df_reviews['content_cat']=df_reviews['stars_num'].apply(stars_cat)
df_reviews['date_d']=df_reviews['date'].apply(get_date)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rKRsNDI-1632470680944)(img\reviews3.png)]
# 查看不同商品的总数
sns.set(font_scale=1)
df_reviews['product_name'].value_counts().plot(kind='bar')
# 按时间顺序统计发文数量,分析是否有周期性规律
df_reviews['date_d'] = pd.to_datetime(df_reviews['date_d'])
df_reviews['y_m'] = df_reviews['date_d'].astype('datetime64[M]')#提取日期年月
df_reviews.head()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mwBe3U90-1632470680946)(img\reviews5.png)]
# 不同产品 everjoys ranch kala donner
# 构建结构化多绘图网格,不同子集上绘制同一图的多个实例, --> FacetGrid()
# FacetGrid 参数说明 data 绘图用到的数据 col每一个小图 利用哪一列来截取数据 col_wrap 小图有几列 sharex是否共享x轴 sharey 是否共享Y轴 height图片高度 aspect 宽高比例
g = sns.FacetGrid(data = df_reviews,col = 'product_name',col_wrap = 2,sharex=False,sharey=False,height = 5, aspect= 1.2)
# g.map 绘制分面图中的小图,利用 sns.countplot 绘图 , 从df_reviews 利用 product_name分组 每组画 content_cat 不同类别的数量order 指定柱子的顺序
g.map(sns.countplot,'content_cat',order=['好评','差评','中评'])
# 每种产品的发文数量按月统计
df_content=df_reviews.groupby(['product_name','y_m'])['content'].count().reset_index()
g=sns.FacetGrid(data=df_content,col='product_name',col_wrap=2,sharey=False,sharex=False,height=4,aspect =2)
# 第一个参数传入的是 要调用哪个API 绘图, 后面几个参数传的是 调用 (plt.plot 的时候需要用到哪些参数, 具体的数据传入列名就行了
g.map(plt.plot,"y_m",'content',marker='1')#marker='1' 折线图每一个点会一条短线来表示
# 好中差评的随时间分布
df_content=df_reviews.groupby(['product_name','y_m','content_cat'])['content'].count().reset_index()
g=sns.FacetGrid(data=df_content,col='product_name',hue='content_cat',col_wrap=2,sharey=False,sharex=False,height=4,aspect =2)
g.map(plt.plot,"y_m",'content',marker='.')#marker='.' 折线图每一个点会用一个点来表示
g.add_legend()# 添加图例
# 同产品不同型号的分布
df_content=df_reviews.groupby(['product_name','y_m','type'])['content'].count().reset_index()
g=sns.FacetGrid(data=df_content,col='product_name',hue='type',col_wrap=2,sharey=False,sharex=False,height=4,aspect =2)
g.map(plt.plot,"y_m",'content',marker='.')
g.add_legend()
df_data = df_reviews.drop_duplicates(subset={
"product_name","type","date_d","content_cat","content","stars_num","name"})
df_text=df_data['content']
df_text[0]
"This is for children, not adults. I cannot use the tuner, so I use one on my phone. It doesn't stay in tune longer than a couple minutes."
sample_positive=df_data[(df_data.product_name=='everjoys-Soprano') & (df_data.content_cat=='好评')]
sample_negative=df_data[(df_data.product_name=='everjoys-Soprano') & (df_data.content_cat=='差评')]
len(sample_positive)
1037
len(sample_negative)
223
# 在用语料分析的第一步,用正则表达式对文本进行处理,
# 正则:由一系列普通与特殊字符组成的用于描述文本规则的表达式
# re的包,是一个有关正则匹配的
# re.sub(pattern,replacement,string) ##查找字符串中出现的所有模式,并做替换,输出替换后的结果字符串
# ? 前一个字符至多出现一次:<=1
# * 前一个字符至少出现0次:>=0
# + 前一个字符至少出现一次:>=1
# ^ 标识字符串以后一个字符开始
# . 代表任意一个字符
# $ 标识字符串以前一个字符结束
# () 标识一个group,group(0)表示正则表达式的模式匹配结果,group(1)表示小括号内匹配的第一个群
# [] 中括号内的字符表示一个字符的取值范围
# {} 大括号内的数字表示前一个字符重复的次数
# \ 反斜线表示去除通配符的特殊意义,仅作为普通字符使用
# | 或者
def replace_abbreviations(text):
# 还原常见缩写单词还原,i'm i'd he's
new_text = re.sub(r"(it|he|she|that|this|there|here)(\'s)",r"\1 is", text,re.I)
# (?<=pattern)xxx,就是捕获以pattern开头的内容xxx
new_text = re.sub(r"(?<=[a-zA-Z])n\'t"," not", new_text) # not的缩写 aren't-- are not
new_text = re.sub(r"(?<=[a-zA-Z])\'d"," would", new_text) # would的缩写i'd -->i would--> 'i' 'would'
new_text = re.sub(r"(?<=[a-zA-Z])\'ll"," will", new_text) # will的缩写
new_text = re.sub(r"(?<=[I|i])\'m"," am", new_text) # am的缩写
new_text = re.sub(r"(?<=[a-zA-Z])\'re"," are", new_text) # are的缩写
new_text = re.sub(r"(?<=[a-zA-Z])\'ve"," have", new_text) # have的缩写
new_text = new_text.replace('\'', ' ').replace('.', '. ')
return new_text
nltk:Natural Language Toolkit 是自然语言处理领域中很常用的Python开源库,nltk提供了一系列的方法帮助我们进行词性标注,词干提取,分词等nlp领域的常见操作,还提供了50多个语料库和词汇资源的接口
def get_lemma(word):
lemma=wn.morphy(word)
if lemma is None:
return word
else:
return lemma
#标点符号
punctuation = [",", ":", ";", ".", "!", "'", '"', "’", "?", "/", "-", "+", "&", "(", ")"]
stop_words=nltk.corpus.stopwords.words('english')+punctuation
# 封装成pipeline
def prepare_text(n):
tx = replace_abbreviations(str(n)) # 缩写还原
# 分词处理 英文分词 实际上就是用空格去split
tokens = nltk.word_tokenize(tx)
# 词还原词形
tokens = [get_lemma(token) for token in tokens]
# 去停用词
tokens = [ i for i in tokens if i not in stop_words] # 遍历每一个单词 如果在停用词表中的就去掉 不在停用词表中的返回
return tokens
clean_txt_positive=[prepare_text(s) for s in sample_positive['content']]
clean_txt_negative=[prepare_text(s) for s in sample_negative['content']]
sample_positive['content'][2]
‘Very nice product! The ukulele is very light and the craftsmanship is great. Everything it came with was good as well. Needs lots of tuning in the beginning’
clean_txt_positive[0]
['nice',
'product',
'ukulele',
'light',
'craftsmanship',
'great',
'everything',
'come',
'wa',
'good',
'well',
'need',
'lots',
'tuning',
'beginning']
Counter(clean_txt_positive[0]).most_common(2)
[(‘nice’, 1), (‘product’, 1)]
#clean_text 所有的评论 经过处理之后的list 是一个二维列表 , 每一条评论都对应一个list 这个list里面保存的是这条评论经过处理之后的关键词
def get_words(clean_text):
words_all = [] # 创建一个列表 用来保存所有评论的单词
for words in clean_text:
for word in words:
words_all.append(word) # 遍历出每条评论的每一个单词, 放到列表里
total_words = list(set(words_all)) # 统计一共出现了多少个不同的单词 对words_all去重, 转换成list
all_words = Counter(words_all) # 统计每个单词出现了多少次
content_mean = len(words_all)/len(clean_text) # 平均每条评论有多少个关键词 总单词数量/ 一共有多少条评论
words_cap = len(total_words)/len(words_all) # 去重之后的词汇总数/去重之前的词汇总数
return all_words,content_mean,total_words,words_cap
words_all_positive,content_mean_positive,total_words,words_cap_positive=get_words(clean_txt_positive)
words_all_negative,content_mean_negative,total_words,words_cap_negative=get_words(clean_txt_negative)
1832
959
content_mean_positive,words_cap_positive
(15.278152069297402, 0.11540884465163159)
content_mean_negative,words_cap_negative
(19.6457399103139, 0.21889979456745035)
positive_words_wordcloud=words_all_positive.most_common(100)# 取出前100个频率最高的单词
negative_words_wordcloud=words_all_negative.most_common(100)
positive_words_wordcloud
[('ukulele', 402),
('love', 390),
('great', 381),
('wa', 356),
('good', 252),
('play', 236),
('tune', 219),
('come', 201),
('get', 200),
('tuner', 192),
('beginner', 189),
('daughter', 184),
……
(WordCloud()
.add(series_name="好评词云",
data_pair=positive_words_wordcloud, #传入绘制词云图的数据
word_size_range=[16, 80]) #word_size_range 字号大小取值范围
.set_global_opts(
title_opts=opts.TitleOpts(
title="好评词云",
title_textstyle_opts=opts.TextStyleOpts(font_size=23) # 设置标题字号
),
tooltip_opts=opts.TooltipOpts(is_show=True), # 设置为True 鼠标滑过文字会弹出提示框
)
.render_notebook()
)
(WordCloud()
.add(series_name="差评词云", data_pair=negative_words_wordcloud, word_size_range=[16, 80])
.set_global_opts(
title_opts=opts.TitleOpts(
title="差评词云", title_textstyle_opts=opts.TextStyleOpts(font_size=23)
),
tooltip_opts=opts.TooltipOpts(is_show=True),
)
.render_notebook()
)