当前位置:   article > 正文

程序设计二:情感分析_情感分析代码

情感分析代码

程序设计二:情感分析

功能一

使用pretreatment_txt函数读取数据并进行预处理:去除噪声url,并将原文本按照位置、文本、用户ID、日期四种属性将原文本分开存入列表location, text, user_id, date

使用cut_word函数对文本进行分词,具体函数如下:

def pretreatment_txt(file_path):
    text = []
    user_id = []
    location = []
    date = []
    with open(file_path, 'r', encoding='utf-8') as f:
        data = f.readlines()
        for i in range(1, 10000):
            # 使用正则表达式去除url
            del_url = re.split(r'http://[a-zA-Z0-9.?/&=:]*', data[i])
            # 将每一行按照四种属性 location,text,user_id,date划分
            att_split = ''.join(del_url).split('\t')
            # 去除伴随url的‘我在:’,‘我在这里:’等中文文本
            text_i = att_split[1].split()
            text_i.pop()
            text_i = ' '.join(text_i)
            # 将四种属性添加到相应列表存储
            text.append(text_i)
            date.append(att_split[3])
            user_id.append(att_split[2])
            location.append(att_split[0])
    return location, text, user_id, date

def cut_word(text):
    text_cut = []
    jieba.load_userdict(r'emotion_lexicon\joy.txt')
    jieba.load_userdict(r'emotion_lexicon\anger.txt')
    jieba.load_userdict(r'emotion_lexicon\fear.txt')
    jieba.load_userdict(r'emotion_lexicon\sadness.txt')
    jieba.load_userdict(r'emotion_lexicon\disgust.txt')
    stopwords = [line.strip() for line in open('stopwords_list.txt', 'r', encoding='utf-8').readlines()]
    for sentence in text:
        words = jieba.lcut(sentence)
        cut_word_i = []
        for word in words:
            if word not in stopwords and word != ' ':
                cut_word_i.append(word)
        text_cut.append(cut_word_i)
    return text_cut
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

功能二

对文件中的每一句话进行情感分析,计算混合情绪的特征向量,其中向量各个位置代表的元素分别是['anger', 'disgust', 'fear', 'joy', 'sadness']。采用单一情绪进行分析,得到句子中出现最多的情绪(如果情绪出现次数相同,则取后出现的作为本句的情绪词)。

使用闭包结构,实现外函数outer_analysis一次加载情绪词典,在子函数inner_analysis中多次调用,该结构代码如下。

def outer_analysis():
    # nonlocal joy,anger,fear,sadness,disgust
    joy = [line.strip() for line in open(r'emotion_lexicon\joy.txt', 'r', encoding='utf-8').readlines()]
    anger = [line.strip() for line in open(r'emotion_lexicon\anger.txt', 'r', encoding='utf-8').readlines()]
    fear = [line.strip() for line in open(r'emotion_lexicon\fear.txt', 'r', encoding='utf-8').readlines()]
    sadness = [line.strip() for line in open(r'emotion_lexicon\sadness.txt', 'r', encoding='utf-8').readlines()]
    disgust = [line.strip() for line in open(r'emotion_lexicon\disgust.txt', 'r', encoding='utf-8').readlines()]

    def inner_analysis(sentence_cut):
        vector = [0] * 5
        sentiment = ['anger', 'disgust', 'fear', 'joy', 'sadness', 'no-sentiment']
        nonlocal joy, anger, fear, sadness, disgust
        for word in sentence_cut:
            if word in anger:
                vector[0] += 1
            elif word in disgust:
                vector[1] += 1
            elif word in fear:
                vector[2] += 1
            elif word in joy:
                vector[3] += 1
            elif word in sadness:
                vector[4] += 1
        if sum(vector) == 0:
            maxiture_vec = [0] * len(vector)
        else:
            maxiture_vec = np.array(vector) / sum(vector)
        max_index = 0
        if sum(vector) == 0:
            max_index = 5
        else:
            for i in range(5):
                if vector[i] > vector[max_index]:
                    max_index = i
        single_sen = sentiment[max_index]
        # 返回值对应sentiment中的情绪值
        return maxiture_vec, single_sen

    return inner_analysis

def text_sentiment_analysis(text_cut):
    analysis = outer_analysis()
    maxi_senti = []
    sing_senti = []
    for sentence_cut in text_cut:
        maxi, sing = analysis(sentence_cut)
        maxi_senti.append(maxi)
        sing_senti.append(sing)
    return sing_senti, maxi_senti
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

在main函数中调用上述闭包,使用代码如下:

sing_senti, maxi_senti = text_sentiment_analysis(text_cut)  # 对每句话进行情绪分析(两种方式)
     print('----------文本 | 单一情绪 | 混合情绪----------')
     for i in range(length):
         print('{}\t{}\t{}'.format(sing_senti[i], maxi_senti[i], text[i]))
  • 1
  • 2
  • 3
  • 4

将结果按照:单一情绪词—混合情绪向量— 文本原文 的顺序输出,结果如下:

在这里插入图片描述

可以看到统计结果符合句子本身的情感。


功能三(时间可视化)

1.数据处理和统计部分

使用定义类Time来处理时间

其中方法week_pattern 是周模式(以一周七天为周期),day_pattern是日模式(以一月31天为周期),hour_pattern是时模式(以一天24小时为周期),minute_pattern是分钟模式(以一小时60分钟为周期)

每种方法内的sentiment参数为指定的情绪类型,可以根据不同的情绪类型和不同的周期统计数据,计算某种情绪的占比。

源代码如下:

class Time(object):
    def __init__(self, date, sing_senti):
        self.length = len(date)
        self.date = date
        self.sentiment = sing_senti
        self.week = []
        self.month = []
        self.day = []
        self.time = []
        self.year = []
        self.hour = []
        self.minute = []
        self.second = []
        self.sentiment_lis = ['anger', 'disgust', 'fear', 'joy', 'sadness', 'no-sentiment']
        for d in self.date:
            date_lis = d.split()
            time = date_lis[3].split(':')
            self.week.append(date_lis[0])
            self.month.append(date_lis[1])
            self.day.append(int(date_lis[2]))
            self.hour.append(int(time[0]))
            self.minute.append(int(time[1]))
            self.second.append(int(time[2]))
            self.year.append(date_lis[5])
        # print(self.day)
        print(self.minute)

    def week_pattern(self, sentiment):
        self.proportion = [0] * 7
        self.minute_sen = [[0] * 7, [0] * 7, [0] * 7, [0] * 7, [0] * 7]
        week_sen_sum = [0] * 7
        weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
        for i in range(self.length):
            for j in range(7):  # 周
                for k in range(5):  # 情绪 k
                    if self.sentiment[i] == self.sentiment_lis[k] and self.week[i] == weekdays[j]:
                        self.minute_sen[k][j] += 1
                        week_sen_sum[j] += 1
        sen_index = 0
        for i in range(5):
            if sentiment == self.sentiment_lis[i]:
                sen_index = i
        sen_vec = self.minute_sen[sen_index]
        for i in range(7):
            if week_sen_sum[i] == 0:
                self.proportion[i] = 0
            else:
                self.proportion[i] = sen_vec[i] / week_sen_sum[i]
        return self.proportion

    def day_pattern(self, sentiment):
        self.proportion = [0] * 31
        self.day_sen = [[0] * 31, [0] * 31, [0] * 31, [0] * 31,
                        [0] * 31]  # 'anger', 'disgust', 'fear', 'joy', 'sadness','no-sentiment'
        day_sen_sum = [0] * 31
        for i in range(self.length):
            for k in range(5):  # 情绪
                if self.sentiment[i] == self.sentiment_lis[k]:
                    self.day_sen[k][self.day[i] - 1] += 1
                    day_sen_sum[self.day[i] - 1] += 1
        sen_index = 0
        for i in range(5):
            if sentiment == self.sentiment_lis[i]:
                sen_index = i
        sen_vec = self.day_sen[sen_index]
        for i in range(31):
            if day_sen_sum[i] == 0:
                self.proportion[i] = 0
            else:
                self.proportion[i] = sen_vec[i] / day_sen_sum[i]
        return self.proportion

    def hour_pattern(self, sentiment):
        self.proportion = [0] * 24
        self.minute_sen = [[0] * 24, [0] * 24, [0] * 24, [0] * 24, [0] * 24]
        hour_sen_sum = [0] * 24
        for i in range(self.length):
            for k in range(5):  # 情绪 k
                if self.sentiment[i] == self.sentiment_lis[k]:
                    self.minute_sen[k][self.hour[i] - 1] += 1
                    hour_sen_sum[self.hour[i] - 1] += 1
        sen_index = 0
        for i in range(5):
            if sentiment == self.sentiment_lis[i]:
                sen_index = i
        sen_vec = self.minute_sen[sen_index]
        for i in range(24):
            if hour_sen_sum[i] == 0:
                self.proportion[i] = 0
            else:
                self.proportion[i] = sen_vec[i] / hour_sen_sum[i]
        return self.proportion

    def minute_pattern(self, sentiment):
        self.proportion = [0] * 60
        self.minute_sen = [[0] * 60, [0] * 60, [0] * 60, [0] * 60, [0] * 60]
        minute_sen_sum = [0] * 60
        for i in range(self.length):
            for k in range(5):  # 情绪 k
                if self.sentiment[i] == self.sentiment_lis[k]:
                    self.minute_sen[k][self.minute[i] - 1] += 1
                    minute_sen_sum[self.minute[i] - 1] += 1
        sen_index = 0
        for i in range(5):
            if sentiment == self.sentiment_lis[i]:
                sen_index = i
        sen_vec = self.minute_sen[sen_index]
        for i in range(60):
            if minute_sen_sum[i] == 0:
                self.proportion[i] = 0
            else:
                self.proportion[i] = sen_vec[i] / minute_sen_sum[i]
        return self.proportion
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113

在main函数中使用Time类,并调用相应方法,主函数中的代码和得到结果如下:

    time = Time(date, sing_senti)
    week_joy_stat = time.week_pattern('joy')
    day_sadness_stat = time.day_pattern('sadness')
    hour_joy_stat = time.hour_pattern('joy')
    hour_anger_stat = time.hour_pattern('anger')
    hour_sadness_stat = time.hour_pattern('sadness')
    hour_disgust_stat = time.hour_pattern('disgust')
    print('----------以周为周期,‘joy’情绪占比变化为----------')
    print(week_joy_stat)
    print('----------以月为周期,‘sadness’情绪占比变化为----------')
    print(day_sadness_stat)
    print('----------以日为周期,‘anger’情绪占比变化为----------')
    print(hour_anger_stat)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

根据上图结果可以看出,

  1. “愤怒” 情绪在每天0时到3时、6时到8时这一时间段占比最多,在8到17时这一时间段占比较少,在17时之后有着明显的波动。主要原因可能是:在8到17时这一时间段是大家的工作时间,大家需要忙碌工作,无暇上网发表愤怒情绪;在凌晨和早上,人们有闲暇时间到微博发泄情绪,表达自己积累的愤怒等。
  2. “开心” 情绪在周末的占比明显更高,这有可能是周末大家有休息和陪家人的时间,相比工作和学习,心情更为愉悦。
  3. “伤心”情绪在每月的占比中变化不大,没有明显特征

2.可视化部分

对每天的五种情绪的占比变化进行可视化,在主函数中采用如下代码:

    hour_joy_stat = time.hour_pattern('joy')
    hour_anger_stat = time.hour_pattern('anger')
    hour_sadness_stat = time.hour_pattern('sadness')
    hour_disgust_stat = time.hour_pattern('disgust')
    plt.plot([i for i in range(1, 25)], hour_disgust_stat, label='hour_disgust')
    plt.plot([i for i in range(1, 25)], hour_sadness_stat, label='hour_sadness')
    plt.plot([i for i in range(1, 25)], hour_anger_stat, label='hour_anger')
    plt.plot([i for i in range(1, 25)], hour_joy_stat, label='hour_joy')
    plt.legend()
    plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

得到如下结果,从中可以观察到:

在这里插入图片描述

  1. 在一天的所有时间段中,“开心”情绪始终着主导位置,说明人们在互联网上表达的大多还是积极开心的情绪。
  2. “开心”情绪与“愤怒”情绪的变化呈反向关系(图中红线升则绿线降,红线降则绿线升),这也和事实常理相符合,两种相反的情绪在占比变化上也应该是对立的。
  3. “愤怒”与“厌恶”两种情绪的变化高度相似(图中绿线和蓝线的变化轨迹几乎一致),这一结果表明随着时间变化,相似的情绪也会有着相似的变动。
  4. 在凌晨3时到5时这一时间段,所有情绪的占比均下降,只有“愤怒”情绪的占比上升。原因可能是此时绝大多数人都已经休息,只有少部分人因为特殊原因和动力(可能动力就是愤怒)仍然留在微博,使得“愤怒”数据上升。

功能四(空间可视化)

1.数据处理部分

在这一部分选用北京西单作为中心centre = [39.911377, 116.374367],使用函数sentiment_around(location_str, sing_senti, r, sentiment)来统计某指定情绪在中心点某一半径范围内的占比,其中参数r是距离中心点的半径,sentiment是指定的情绪类型,代码如下:

def sentiment_around(location_str, sing_senti, r, sentiment):
    location = []
    proportion = 0
    senti_stat = [0] * 6
    sum = 0
    for lo in location_str:
        location.append(eval(lo))
    centre = [39.911377, 116.374367]
    sentiment_dic = {'anger': 0, 'disgust': 1, 'fear': 2, 'joy': 3, 'sadness': 4, 'no-sentiment': 5}
    radius = lambda a, b: math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2)
    for i in range(len(location)):
        if radius(location[i], centre) <= r:
            senti_stat[sentiment_dic[sing_senti[i]]] += 1
    # 计算各情绪占比
    for i in range(5):
        sum += senti_stat[i]
    if sum == 0:
        proportion = 0
    else:
        proportion = senti_stat[sentiment_dic[sentiment]] / sum
    return proportion
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在main函数中分别取半径为r = [0.05,0.10,0.15,0.20,0.25,0.35,0.45],计算“joy”情绪所占比例,计算结果如下:

在这里插入图片描述

从结果中可见,随着半径范围的扩大,“joy”情绪的比例基本稳定,有着微小的增加,在r>0.1时情绪占比变化幅度并不明显。

2.可视化部分

采用Python的pyecharts模块进行可视化,定义location_geo函数处理经纬度位置location_str和情绪sing_senti,并根据不同的情绪对位置点上色,呈现在北京地图上,代码如下:

def location_geo(location_str, sing_senti):
    city = '北京'
    sentiment_dic = {'anger': 0, 'disgust': 1, 'fear': 2, 'joy': 3, 'sadness': 4, 'no-sentiment': 5}
    g = Geo()
    g.add_schema(maptype=city)
    location = []
    data_pair = []
    for lo in location_str:
        location.append(eval(lo))
    for i in range(len(location)):
        g.add_coordinate(i, location[i][1], location[i][0])
        data_pair.append([i, sentiment_dic[sing_senti[i]]])  # 定义数据对

    g.add('', data_pair, type_=GeoType.EFFECT_SCATTER, symbol_size=1) # 将数据添加到地图上 
    g.set_series_opts(label_opts=opts.LabelOpts(is_show=False))       # 设置样式
    # 自定义分段 color 可以用取色器取色
    pieces = [
        {'max': 0, 'min': 0, 'label': 'anger', 'color': '#50A3BA'},
        {'min': 1, 'max': 1, 'label': 'digust', 'color': '#3700A4'},
        {'min': 2, 'max': 2, 'label': 'fear', 'color': '#81AE9F'},
        {'min': 3, 'max': 3, 'label': 'joy', 'color': '#E2C568'},
        {'min': 4, 'max': 4, 'label': 'sadness', 'color': '#DD0200'},
        {'min': 5, 'max': 5, 'label': 'no-sentiment', 'color': '#FCF84D'}
    ]
    #  is_piecewise 是否自定义分段, 变为true 才能生效
    g.set_global_opts(
        visualmap_opts=opts.VisualMapOpts(is_piecewise=True, pieces=pieces),
        title_opts=opts.TitleOpts(title="{}-微博情绪分布".format(city)),
    )
    return g
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在main函数中调用上述函数,并以html格式存储点图,代码如下:

# 将位置信息绘制成点图
g = location_geo(location, sing_senti)
# 渲染成html, 可用浏览器直接打开
g.render('weibo_sentiment.html')
os.system("weibo_sentiment.html")
  • 1
  • 2
  • 3
  • 4
  • 5

在浏览器中呈现可视化结果如下(比例尺由大到小):

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


功能五

使用字典方法有如下缺点:

  1. 情感数量较少且固定,不能完整地表达人的情绪,也不能做到灵活添加词表
  2. 对句子情感的判断依赖于既有词表,会有大量的句子因无法对照的相应的词而被忽略
  3. 对于未知情绪的处理无能为力,对于蕴含多种情绪的句子难以处理

扩充字典的思路如下(自动扩充):

  1. 对于某一情感的句子(如句子1、句子2、句子3都为“joy”),找出这些句子中相同的词语集合set1,并统计该情感下(如“joy”),set1中词语出现的次数
  2. 同时统计该情感(“joy”)下,情感字典中词语出现的中位数m,如果set1中有词语new_word的出现次数超过中位数m(或其它阈值),则将new_word添加到对应的情感字典中
  3. 以此迭代,可以在合理范围内自动扩充情绪字典

讨论

微博情绪会随时间变化而变化,也会在空间分布上存在差异,根据这些特点,可以有针对地利用人们的情绪来管理公司员工和营销产品。

例如,早上八点到下午五点这段时间里,微博反映人们情绪普遍较为愉悦且稳定(第三题结果),波动不大,因此适合一定强度的工作,可能有着不错的效率。在下午五点以后,微博反映人们情绪有着两次波动,首先是五点左右“joy”心情占比逐渐下降,可能工作状态会下滑,不适合高强度工作,因此可能需要短暂休息;

其次是晚上八点到深夜十二点这段时间,“joy”心情占比又有着明显的提高,“sadness”,”anger”,”digust”,”fear”等负面情绪显著下降。因此在这段时间加大广告投放力度更有可能会让人们有兴趣挑选,增强广告效益。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/611444
推荐阅读
相关标签
  

闽ICP备14008679号