数学建模预测模型实例–表白墙影响力量化模型
数学建模预测模型实例(一)–大学生体测数据模型
数学建模预测模型实例(二)—表白墙影响力量化模型
python预测模型–线性回归
建立模型的目的
本篇文章中,假设获取到了某高校表白墙建墙以来至2020年12月21日发布的所有说说的文字内容,发布时间,点赞数,评论数。目的是通过前期数据挖掘探索性数据分析等建立表白墙影响力量化模型,进而得出发布寻物类消息的最佳时机以及持续影响力,最大影响人数等指标。
数据分析的基本过程包括:获取数据、数据清洗、中文分词聚类、探索性数据挖掘、数据可视化、基于SIR模型的表白墙影响力量化模型。
获取数据
在这里我们用selenium模拟登录qq空间,然后用xpath解析网页
#coding: utf-8
from imp import reload
import time
from selenium import webdriver
from lxml import etree
import pandas as pd
def text(friend, user , pw):
# 获取浏览器驱动
driver = webdriver.Chrome(executable_path='chromedriver')
# 浏览器窗口最大化
driver.maximize_window()
# 浏览器地址定向为 qq 登陆页面
driver.get('http://i.qq.com')
# 所以这里需要选中一下 frame,否则找不到下面需要的网页元素
driver.switch_to.frame('login_frame')
# 自动点击账号登陆方式
driver.find_element_by_id('switcher_plogin').click()
# 账号输入框输入已知 QQ 账号
driver.find_element_by_id('u').send_keys(user)
# 密码框输入已知密码
driver.find_element_by_id('p').send_keys(pw)
# 自动点击登陆按钮
driver.find_element_by_id('login_button').click()
time.sleep(10)
# 让 webdriver 操纵当前页
driver.switch_to.default_content()
# 跳到说说的 url,friend 你可以任意改成你想访问的空间
driver.get('http://user.qzone.qq.com/' + friend + '/311')
next_num = 0 # 初始“下一页”的 id
while True:
# 下拉滚动条,使浏览器加载出动态加载的内容
# 我这里是从 1 开始到 6 结束分 5 次加载完每页数据
for i in range(1, 6):
height = 20000 * i# 每次滑动 20000 像素
strWord = "window.scrollBy(0," + str(height) + ")"
driver.execute_script(strWord)
time.sleep(4)
# 很多时候网页由多个<frame>或<iframe>组成,webdriver 默认定位的是最外层的 frame
# 所以这里需要选中一下说说所在的 frame,否则找不到下面需要的网页元素
driver.switch_to.frame('app_canvas_frame')
selector = etree.HTML(driver.page_source)
divs = selector.xpath('//*[@id="msgList"]/li/div[3]')
# 这里使用 a 表示内容可以连续不清空写入
with open(r'C:\Users\15643\Desktop\公众号\表白墙\data2.csv',"a",encoding='utf-8')as f:
for div in divs:
#qq_name = div.xpath('./div[2]/a/text()')
qq_content = div.xpath('./div[2]/pre/text()')
qq_time = div.xpath('./div[4]/div[1]/span/a/@title')
qq_zan = div.xpath('./div[5]/div[2]/a[2]/text()')
qq_comment = div.xpath('./div[4]/div[2]/a[3]/text()')
#qq_name = qq_name[0] if len(qq_name) > 0 else ''
qq_content = qq_content[0] if len(qq_content) > 0 else ''
qq_time = qq_time[0] if len(qq_time) > 0 else ''
f.write("{}.{}.{}.{}\n".format(qq_content,qq_time,qq_zan,qq_comment))
print(qq_time, qq_content,qq_zan,qq_comment)
# 当已经到了尾页,“下一页”这个按钮就没有 id 了,可以结束了
if driver.page_source.find('pager_next_' + str(next_num)) == -1:
break
# 找到“下一页”的按钮,因为下一页的按钮是动态变化的,这里需要动态记录一下
driver.find_element_by_id('pager_next_' + str(next_num)).click()
# "下一页"的 id
next_num += 1
# 因为在下一个循环里首先还要把页面下拉,所以要跳到外层的 frame 上
driver.switch_to.parent_frame()
if __name__ == '__main__':
friend = 'xxxx' # 朋友的 QQ 号,朋友的空间要求允许你能访问
user = 'xxxx' # 你的 QQ 号
pw = 'xxxx' # 你的 QQ 密码
text(friend, user, pw)
最终获取到一个包含所有说说内容,发布时间,点赞数,评论数的excel文件,随后再将说说文本提取到一个txt文件里面便于之后进行分词。
最终获取到的数据:说说数据.xlsx
说说文本.txt
数据:表白墙数据 提取码:klh0
数据清洗—中文文本分词
首先先对txt里的说说数据进行分词、并删去停用词,无效符号。考虑到表白墙中一些词语的连贯性,我们引入了自定义词典
求同好
大橘
首医
墙墙
表白墙
一教
二教
随后运用jieba分词库进行分词处理
import jieba
# 加载停用词列表
def load_stopword():
f_stop = open('stopword.txt', encoding='utf-8') # 自己的中文停用词表
sw = [line.strip() for line in f_stop] # strip() 方法用于移除字符串头尾指定的字符(默认为空格)
f_stop.close()
return sw
# 中文分词并且去停用词
def seg_word(sentence):
file_userDict = 'dict.txt' # 自定义的词典
jieba.load_userdict(file_userDict)
sentence_seged = jieba.cut(sentence.strip())
stopwords = load_stopword()
outstr = ''
for word in sentence_seged:
if word not in stopwords:
if word != '/t':
outstr += word
outstr += " "
print(outstr)
return outstr
if __name__ == '__main__':
with open("qq_word.txt", "r",encoding='utf-8') as f:
content=f.read()
result = seg_word(content)
分词结束后再应用 jiaba.analyse.extract_tags() 中默认的TF-IDF模型对文档进行分析。
TF-IDF的基本思想是:词语的重要性与它在文件中出现的次数成正比,但同时会随着它在语料库中出现的频率成反比下降 。也就是说TD-IDF综合考虑了两方面因素,排除了那些看似词频很高实则是无效常见词的情况(例如:的,包括)。因此某个词对文章的重要性越高,它的TF-IDF值就越大
#关键词提取
import jieba.analyse as analyse
analyse.extract_tags(result, topK=5, withWeight=True, allowPOS=())
#词云制作
import matplotlib.pyplot as plt
from wordcloud import WordCloud
import jieba
import os
wc = WordCloud(background_color = "white", #设置背景颜色
#mask = "图片", #设置背景图片
max_words = 1500, #设置最大显示的字数
#stopwords = "", #设置停用词
font_path = r'C:\Users\15643\Desktop\公众号\表白墙\qihei55.ttf',
#设置中文字体,使得词云可以显示(词云默认字体是“DroidSansMono.ttf字体库”,不支持中文)
#max_font_size = 50, #设置字体最大值
#random_state = 30, #设置有多少种随机生成状态,即有多少种配色方案
width=1920,
height=1080,
margin=5,
)
myword = wc.generate(result)#生成词云
myword.to_file(os.path.join(r'C:\Users\15643\Desktop\公众号\表白墙\wordcloud.png'))
#展示词云图
plt.imshow(myword)
plt.axis("off")
#plt.savefig(r'C:\Users\15643\Desktop\公众号\表白墙\wordcloud.png')
plt.show()
从以上结果可以看出,提取到的分词数据所制成的词云图与关键词相呼应,表明“表白,求助,吐槽,失物招领,一周cp”是表白墙的主题内容。表白墙不愧为表白墙,表白还是占了绝大多数。
探索性数据分析–表白+吐槽
确定思路
在进行过初始的数据清洗之后我们找到了表白墙的五大主题,为了方便接下来的分析,我们将这五大主题划归为三大类:表白,吐槽,寻物,在这里我们认为前两类是带有明显且强烈的情感暗示的内容,因此这两类说说的点赞,评论,乃至发布时间可能会遵循某种分布。以下为对这两类说说的分析
注:为保证数据的真实性,我们删去了新冠疫情期间的所有表白墙数据(2020/3-2020/8)
时间分析
为了衡量表白墙发布这两类投稿的偏好时间,我们对一天三个时间段表白墙针对这两个主题说说的发布情况进行了统计。
##以吐槽说说的分析过程为例
import numpy as np
import pandas as pd
excel_path_1 = r'C:\Users\15643\Desktop\吐槽全.xlsx'
df1 = pd.DataFrame(pd.read_excel(excel_path_1))
time_new = pd.to_datetime(df1['time'],format='%H:%M:%S')
df1['hour']=time_new.dt.hour
m=[]
a=[]
e=[]
for i in range(len(df1)):
if 8<=df1.iloc[i,3]<=12:
m.append(df1.iloc[i,3])
elif 12<=df1.iloc[i,3]<=18:
a.append(df1.iloc[i,3])
elif 18<=df1.iloc[i,3]<=24:
e.append(df1.iloc[i,3])
从以上结果我们可以很明显的看出晚间实际上是投稿的高峰期,晚上的确是多愁善感的网抑云时间。
热度分析
为了衡量这两类说说在不同时间段的热度,我们首先对热度进行定义:
随后定义了一学期中的三个时间段:
学期初: 开学-开学后一个月 |
---|
学期中 |
学期末:最后一个月 |
根据以上的定义进行数据筛选
#以吐槽说说学期末热度分析为例
import numpy as np
import pandas as pd
excel_path_1 = r'C:\Users\15643\Desktop\吐槽全.xlsx
df1 = pd.DataFrame(pd.read_excel(excel_path_1))
df1['zan'] = df1['zan'].apply(lambda x :x*0.4 if x != 0 else 0)
df1['comment'] = df1['comment'].apply(lambda x :x*0.6 if x != 0 else 0)
hot_p_l=[]
for i in range(len(df1)):
hot_p =df1.iloc[i,2]+df1.iloc[i,3]
hot_p_l.append(hot_p)
df1['hot_p']=pd.DataFrame(hot_p_l)
time_start=pd.datetime(2018,12,15)
time_stop =pd.datetime(2019,1,15)
df_a_1 = df1[(df1['date'] >= time_start) & (df1['date'] <= time_stop)]
df_a_2 = df1[(df1['date'] >= pd.datetime(2019,6,15)) & (df1['date'] <= pd.datetime(2019,7,15))]
df_a_3 = df1[(df1['date'] >= pd.datetime(2019,12,15)) & (df1['date'] <= pd.datetime(2020,1,15))]
#df_a_4 = df1[(df1['date'] >= pd.datetime(2020,9,25)) ]
df_a = pd.concat([df_a_1,df_a_2,df_a_3])
hot_mean=df_a['hot_p'].mean()
hot_mean
从上图结果可以很明显的看出:同学们总是倾向于在学期初进行吐槽,并且在学期末集中表白。对此我们可以解释为学期初新生进校,自然与老生们的各方冲突都比较激烈,而且大家也没有习惯校园生活,因此此时吐槽的投稿会有较高的关注度;而学期末情况则趋于稳定,经过一整个学期大家都有了一定的相互了解,因此此时表白的投稿更能引发同学们的热情。
探索性数据分析–寻物类
确定思路
在对表白以及吐槽类说说分析完成之后,我们将目光转向了寻物类说说。首先我们想要分析在一天三个时间段中发布说说的最佳时间。
最佳时段分析
为达到此目标,我们选择删去所有带有“表白,提醒,注意,吐槽”等关键词的说说,排除由这些说说带来的情感暗示的影响;将剩下的说说看作表白墙的以寻物为主的其他类说说,并进行分时段热度分析。
#总有效-表白-吐槽 去除带有强烈情感暗示的
import numpy as np
import pandas as pd
import re
excel_path_2 = r'C:\Users\15643\Desktop\公众号\表白墙\说说数据.xlsx'
df2 = pd.DataFrame(pd.read_excel(excel_path_2))
df2['zan'] = df2['zan'].apply(lambda x :x*0.4 if x != 0 else 0)
df2['comment'] = df2['comment'].apply(lambda x :x*0.6 if x != 0 else 0)
hot_p_l=[]
for i in range(len(df2)):
hot_p =df2.iloc[i,4]+df2.iloc[i,5]
hot_p_l.append(hot_p)
df2['hot_p']=pd.DataFrame(hot_p_l)
i_list=[]
for i in range(len(df2)):
qq_content = df2.iloc[i,3]
if re.search("表白",str(qq_content))!=None or re.search("吐槽",str(qq_content))!=None or re.search("提醒",str(qq_content))!=None or re.search("注意",str(qq_content))!=None or re.search("告白",str(qq_content))!=None:
i_list.append(i)
for i in i_list:
df2.drop([i],axis=0,inplace=True)
#18-24点说说寻找热度最高的时间段
df2['time_new']=pd.to_datetime(df2['time'],format='%H:%M:%S')
df2_e = df2[(df2['time_new'] >= pd.datetime(1900,1,1,8)) & (df2['time_new'] <= pd.datetime(1900,1,1,12))]
df2_e.sort_values(by='time_new',inplace=True)#排序
df2_e['hour']=df2_e['time_new'].dt.hour
df2_e['min']=df2_e['time_new'].dt.minute
result=[]
for i in range(8,12):
hour=i
df_=df2_e.loc[df2_e['hour']==hour]
if i!=12:
for k in range(0,60):
min=k
df_f = df_.loc[df_['min']==min]
result_=df_f['hot_p'].mean()
result.append(result_)
else:
result.append(df_['hot_p'].mean())
result = pd.DataFrame(result)
result.to_excel(r'C:\Users\15643\Desktop\8-12.xlsx')
通过以上结果我们可以发现,三个时间段发布说说的最佳时间分别为:8:21、15:31、19:08 分别对应了早课开始二十分钟犯困拿出手机的上学人,下午第一节课下课回到宿舍刷空间换换脑子的,晚上吃完饭再玩玩手机消食的干饭人。
表白墙影响力量化模型的建立
确定思路
基于刚刚的探索性数据分析我们得出了发布说说的最佳时段(最佳时段发布的投稿受到的关注度最高),因此我们选择在19:08这个时间点作为我们模型的假设。
我们假设表白墙在19:08发布了一条失物招领的说说内容,我们设法做到了促使表白墙在相似的时间点发布寻物说说,并按分钟为单位统计到了浏览量的累计变化数据(一共30分钟)。
确立模型
对于信息的传播方式,特别是针对高校表白墙而言,我们提出了自己的看法:我们认为高校表白墙的服务群体较为单一,且是由一个个聚集程度较高的熟人网络所构成的社交网,而在此种封闭式聚集式的环境下的信息传播应该具有病毒式传播的特点。
大家有没有想象过自己的投稿是一个石子,在发布到社交网络后一石激起千层浪,你的声音在这之中被不断放大,而这一切仅仅需要几分钟的时间。
所以我们在这里大胆的抛弃了传统的社会传播模型,转而应用经典的传染病模型试图解释病毒式传播这一现象。
在这里我们应用了经典的SIR模型
参数辨识
通过SIR模型的三个微分方程我们构建了以下基于实际增长浏览量数据的优化问题:
该优化问题的目标函数为信息传播强度
β
\beta
β,在这里我们选取5-10分钟的浏览量数据作为参数识别的数据,假设信息传播时每个人每分钟可能接触到的人数为5,信息更新率
γ
=
1
/
6
\gamma = 1/6
γ=1/6,信息传播系统中总人数为2000人。
import numpy as np
import pandas as pd
import math
import datetime
from scipy.integrate import odeint
from scipy.optimize import minimize
import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
%pylab inline
data = pd.read_excel(r'C:\Users\15643\Desktop\公众号\表白墙\data.xlsx')
class estimationInfectionProb():
def __init__(self, estUsedTimeIndexBox, nContact, gamma):
self.timeRange = np.array([i for i in range(estUsedTimeIndexBox[0],estUsedTimeIndexBox[1] + 1)])
self.nContact, self.gamma = nContact, gamma
self.dataStartTimeStep = 33
def setInitSolution(self, x0):
self.x0 = 0.04
def costFunction(self, infectionProb):
#print(data.loc[self.timeRange - self.dataStartTimeStep,'累计确诊'])
#print(np.exp((infectionProb * self.nContact - self.gamma) * self.timeRange))
res = np.array(np.exp((infectionProb * self.nContact - self.gamma) * self.timeRange) - \
data.loc[(self.timeRange - self.dataStartTimeStep)*(-1),'累计增加'])
return (res**2).sum() / self.timeRange.size
def optimize(self):
self.solution = minimize(self.costFunction, self.x0, method='nelder-mead', options={'xtol': 1e-8, 'disp': True})
print('infection probaility: ', self.solution.x)
return self.getSolution()
def getSolution(self):
return self.solution.x
def getBasicReproductionNumber(self):
self.basicReproductionNumber = self.nContact * self.solution.x[0] / (self.gamma)
print("basic reproduction number:", self.basicReproductionNumber)
return self.basicReproductionNumber
startTime = 1
estUsedTimeBox = [5, 15]
estUsedTimeIndexBox = [(t - startTime) for t in estUsedTimeBox]
nContact, gamma = int(5), 1/6
estInfectionProb = estimationInfectionProb(estUsedTimeIndexBox, nContact, gamma)
estInfectionProb.setInitSolution(0.04)
infectionProb = estInfectionProb.optimize()
basicReproductionNumber = estInfectionProb.getBasicReproductionNumber()
SIR模型拟合
class SIRModel():
def __init__(self, N, beta, gamma):
self.beta, self.gamma, self.N = beta, gamma, N
self.t = np.linspace(0, 60, 61)
self.setInitCondition()
print(self.beta)
def odeModel(self, population, t):
diff = np.zeros(3)
s,i,r = population
diff[0] = - self.beta * s * i / self.N
diff[1] = self.beta * s * i / self.N - self.gamma * i
diff[2] = self.gamma * i
return diff
def setInitCondition(self):
self.populationInit = [self.N - 1, 1, 0]
def solve(self):
self.solution = odeint(self.odeModel,self.populationInit,self.t)
print(self.solution[:,1].max())
for i in range(len(self.solution)):
if self.solution[i,1]==self.solution[:,1].max():
print(i)
def report(self):
#plt.plot(self.solution[:,0],color = 'darkblue',label = 'Susceptible',marker = '.')
plt.plot(self.solution[:,1],color = 'orange',label = '接收信息',marker = '.')
plt.plot(self.solution[:,2],color = 'green',label = '退出传播系统',marker = '.')
plt.title('SIR 信息传播模型')
plt.legend()
plt.xlabel('分钟(min)')
plt.ylabel('人数')
plt.savefig(r'C:\Users\15643\Desktop\公众号\表白墙\SIR.jpg')
plt.show()
基于真实的失物招领的浏览量数据我们建立以上模型,得出了失物招领这一信息的传播强度为0.517,传染数为3。为了更好的帮助大家理解这两个指标,我们引入了基于同一模型的新冠疫情的传播强度0.196,以及传染数2.7。信息是超越新冠病毒的存在!
在证实信息的病毒式传播之后我们继续探索,最终找到了大家想要的答案,大家发布的失物招领的投稿的最大传播数为625人,而达到最大传播数只需要短短的23分钟。
也就是说,我们每投稿一次,会有全校三分之一的同学接收到到我们的投稿信息,这么看来影响力还是十分不错的,靠表白墙找卡有希望了!但是这个影响力是有时效性的,要是在23分钟内没有找到失主,信息可能会被逐渐淹没,乃至遗忘。
但是大家还是可以放心的,表白墙的影响力以及病毒式信息传播可不是盖的。
那么就祝大家找卡顺利!
讨论
1.在前期数据获取过程中出现了部分说说点赞数据的缺失,可能会对结果造成一定影响
2.将完完全全的传染病模型应用于社会舆论传播力衡量未免有失偏颇,应用SIR模型纯粹是编者自己的私心,之后有机会会去研究正经的传播力模型并作修改。
喜欢这篇数学建模案例的话,欢迎关注我们和我们一起交流!
奇趣多多,数模多多!