Statalist上的“火云邪神”
本文作者:李朋冲
文字编辑:李钊颖
技术总编:李朋冲
Stata是一个统计软件,它拥有强大的数据管理和计量统计功能,更为重要的是,它的程序语言也非常简单、清晰——基本上我们可以从命令的名称猜到它的大致用途。即便一下子懵圈了,也不要紧,Stata有一个便捷的帮助体系来帮我们“清醒”——search、findit、help,可以帮助用户迅速找到实现某项功能的命令,并了解它的具体用法。特别是help,简直可以说是“武林秘籍”,有了“help”,我们就可以学习到全球所有Stata开发者提供的“独门绝技”——“洪家铁线拳”、“五郎八卦棍”、“十二路谭腿”…总有一款适合你!
不过,每个月,又或是每天,总有一些数据清洗或者计量上的问题困扰着你——你困惑,失眠,掉头发,这时你绝对不会唱:来啊,快活啊,反正有,大把头发!一个星期过去了,两个星期过去了,你卡壳了,诸事无进展,不敢接老师电话只是“初级水平”,“更高层次”的表现会是怀疑并否定自己:我适合写论文,做学术吗?为何一事无成?
不要这样嘛!有啥子事解决不了呢!要知道,有问题的地方,总会有转机,天无绝人之路!
当我们走在研究的路上时,我们应当唱起Michael的歌You Are Not Alone,也应该去听一听安菲尔德球场上空万人唱响的You Will Never Walk Alone!要知道,我们遇到的问题,说不定别人也早已遇到过,如果足够幸运,或许已经有一些解决方法,抑或是可以给我们一些启发;即使是在探索一个未知的领域,也一定有一些先行者到达了这个领域的边界,做出了一些试探,正是这一步一步的试探,指引了后来者前行的道路。这个世界上,总会有一些好心人,他们乐于分享,乐于助人,乐于为后进者指引方向,这些人都是品德高尚的人!如果有这样一个地方,里面的每个人都很热心,那真是令人开心的一件事!
我们在前面提到了“help”,还有另一个能量场我们没有说到——STATALIST(网址:https://www.statalist.org/)。(输入网址后,进入主页,点击Enter the forum,在新页面中点击general,即可查看各种问题。如果要发帖,需要注册一个账号。)
这是Stata的一个论坛,以前是一个邮箱系统,专门接收Stata用户发送的问题,后来数量太多,就升级为论坛了。在这里,所有的用户都可以按照一定的规则提交自己在数据整理和计量统计上的问题,会有众多的用户一起帮忙想办法解决。大家在这个论坛上互相帮助,即使未曾谋面,也可以感受到helper的热情。这里没有谁鄙视别人问的问题太low,也没有谩骂与争吵,有的,只是帮忙解决问题的热心肠!
论坛里,有众多“能量极强、法术无边”的“终极大BOSS”,他们有的是著作等身的终身教授,有的是正在学术上升期的青年教师。他们每天都会在论坛里查看各种问题,帮忙解决,“大神一出手,就知有没有”!秋风扫落叶,迅速解决各种疑难杂症,可谓药到病除,浑身上下十万个毛孔,没有一个不舒畅的!
我基本上每天都会登录到Statalist上,去查看一些数据处理技巧,当然了,顺带也学一下英语。基本上我们遇到的数据处理问题,论坛上都已经有人问过并且得到了很好的解答。有的时候,这些教授们还会“神仙打架”,给出不同的处理程序,但能从字里行间看到他们应该是惺惺相惜,毕竟,江湖中有一些和自己段位相同并且同样热衷于帮助后辈的高手,确是乐事一件。
有几个名字,经常出现在“Last Post”这一栏中(最近一次回帖),基本上每一页都可以见到(如下图)。在此介绍这几位令人敬仰的教授,当然,论坛上还有很多这样非常热心的学者,他们受得起任何的赞美与尊敬!
左一:Nick Cox教授,英国杜伦大学,主要处理环境数据和社会科学数据。兴趣包括:Stata图形,探索性数据分析,广义线性模型,数据分布、转换、方向性数据分析。Cox教授是Stata的活跃用户,在Stata上发布多个命令并在StataForum上广泛编写程序,帮助解决问题。也在Stata Journal上发表许多文章。截至目前,已在论坛上回帖将近19000次,日均回帖量9.43个。
左二:Clyde Schechter教授,纽约阿尔伯特爱因斯坦医学院,主要研究流行病学、癌症预防和仿真建模。Schechter教授回帖近16500次,日均回帖8.22篇。
右二:Carlo Lazzaro教授,意大利米兰卫生经济学家和研究主任,曾经是一名网球运动员。研究领域:医疗保健计划的经济效益评估,医药经济学,卫生经济学。Lazzaro教授对专业研究领域的统计(包括生物统计学和健康计量经济学)以及贝叶斯统计十分感兴趣。回帖量9200多篇,日均4.6篇。
右一:Richard Williams教授,美国圣母大学,主要从事社会学研究,研究兴趣包括计量统计方法、人口统计学和城市社会学。Williams教授获得2015年Stata Journal编辑奖,目前回帖量4000多篇。
以上列举的只是其中的几位大神,还有很多数据处理和计量水平都非常高超的学者,在此,我们有必要说出这两句话:
我们以第一页为例,给大家介绍一下如何获取里面的信息。
在上面已经通过截图给大家展示如何进入论坛的general部分,它的网址是:https://www.statalist.org/forums/forum/general-stata-discussion/general
我们点击下一个页面,会看到:https://www.statalist.org/forums/forum/general-stata-discussion/general/page2
其实第一页的链接也可以是:https://www.statalist.org/forums/forum/general-stata-discussion/general/page1
那我们想要获取哪些信息呢?我们来看下面的截图:
首先是帖子的title,我们观察到,title是带有链接的,我们把链接也抓下来;接着是下方的发帖人(starter),点击之后会显示每个注册用户的个人简介(starter_profile):
然后是发帖时间(start_time),中间两栏是回帖数量和浏览量(posts_count和views_count),右侧“Last Post”一栏是最近一次回帖人姓名和链接(last_helper和last_helper_profile)以及最近一次回帖时间(last_post_time)。
之所以要获取回帖时间,是因为,当我们在论坛上发帖子的时候,要查看一下比较活跃的helper,他们的活动时间一般是在什么时候,这样我们发帖子,也会有更大的几率被看到。
综上,要获得的信息包括:title、post_url、starter、starter_profile、start_time、posts_count、views_count、last_helper、last_helper_profile、last_post_time。
老规矩,第一步要先查看源代码,看看里面有没有我们需要的信息。我们在源代码里搜索当前页的第二个标题(第一个是Stata16发布的广告,我们不管它):Probit model,outcome does not vary
我们看到,源代码里包含我们所需要的10个信息。我们以这个帖子为例,看一下包含有title和url的源代码:
我们需要的就是未标记为绿色的部分,在源代码里查看一下“topic-title js-topic-title”:
一共有51处,其中第1个是Stata16发布的帖子,页面上显示“Sticky”,也就是说,这个帖子会出现在后续所有页面的源代码里,即使页面上并不显示它。我们在后续的处理中,删除这个帖子即可。
包含有以上10类信息的源代码如下,我们进行分析:
(1)<a href="https://www.statalist.org/forums/forum/general-stata-discussion/general/1518764-probit-model-outcome-does-not-vary" class="topic-title js-topic-title">Probit model,outcome does not vary</a>
(2)by <ahref="https://www.statalist.org/forums/member/38621-aaron-nagel">Aaron Nagel</a>
(3)Started by <a href="https://www.statalist.org/forums/member/38621-aaron-nagel">Aaron Nagel</a>, <spanclass="date">Today, 15:17</span>
(4)<divclass="posts-count">3
(5)<divclass="views-count">13
(6)by <ahref="https://www.statalist.org/forums/member/288-richard-williams">Richard Williams</a>
(7)<spanclass="post-date">Today,20:40</span>
我们观察到:
在(1)中,我们可以用“topic-title js-topic-title”标签来界定帖子的名称和链接。
在(2)、(3)和(6)中,都有“by <a href="”标签,我们可以用来保留发帖人姓名、发帖人简介、发帖时间以及回帖人、回帖人简介等信息。
在(4)中,用“<div>”标签保留回帖量信息。
在(5)中,用“<div>”标签保留浏览量信息。
在(7)中,用“<span>”标签保留最近一次回帖时间的信息。
clear
efolder statalist, cd(d:/) //在D盘创建statalist文件夹,并切换到该路径下
copy "https://www.statalist.org/forums/forum/general-stata-discussion/general/page1" temp1.txt, replace
infix strL v 1-100000 using temp1.txt, clear
导入之后,我们首先查看一下,使用以上的标签可否明确界定出我们所需要的信息:
count if ustrregexm(v, "topic-title js-topic-title") //帖子题目和链接
count if ustrregexm(v, `"by <a href="(.+?)">(.+?)</a>"') //发帖人信息,发帖时间和回帖人信息
count if ustrregexm(v, `"<div class="posts-count">"') //回帖数量
count if ustrregexm(v, `"<div class="views-count">"') //浏览量
count if ustrregexm(v, `"<span class="post-date">"') //最近一次回帖时间
我们注意到,有一个数字是153,这是因为`"by <ahref="(.+?)">(.+?)</a>"'可以匹配到一个帖子的3行信息,这样,一共有51个帖子。这个结果与图上其他HTML标签匹配到的数量相同。所以,我们可以使用这些标签保留有用信息。
keep if ustrregexm(v, `"topic-title js-topic-title"') | ///
ustrregexm(v, `"by <a href="(.+?)">(.+?)</a>"') | ///
ustrregexm(v, `"<div class="posts-count">"') | ///
ustrregexm(v, `"<div class="views-count">"') | ///
ustrregexm(v, `"<span class="post-date">"')
当然,这些标签也可以用“或”的形式合在一起,这里是为了更清楚地显示,所以进行了分离。运行完这段程序之后,数据集里还有357行观测值,每一个帖子对应7行信息。
我们进行如下操作:
forvalues j = 1/6{
gen v`j' = v[_n + `j']
}
keep if mod(_n,7) == 1
这样,数据集中只剩下51个观测值。我们在前面提到,第1个帖子是Stata16发布的内容,我们不需要这个,删除掉。
drop in 1
下面开始从里面提取信息,以第一行观测值为例,从v-v6依次介绍:
gen post_url = ustrregexs(1) if ustrregexm(v, `"<a href="(.+?)".+?>(.+?)</a>"')
gen title = ustrregexs(2) if ustrregexm(v, `"<a href="(.+?)".+?>(.+?)</a>"')
gen starter_profile = ustrregexs(1) if ustrregexm(v1, `"by <a href="(.+?)">(.+?)</a>"')
gen starter = ustrregexs(2) if ustrregexm(v1, `"by <a href="(.+?)">(.+?)</a>"')
gen start_time = ustrregexs(1) if ustrregexm(v2, `".+">(.+?)</span>"')
gen posts_count = ustrregexs(1) if ustrregexm(v3, `".+(\d+)"')
gen views_count = ustrregexs(1) if ustrregexm(v4, `".+(\d+)"')
gen last_helper_profile = ustrregexs(1) if ustrregexm(v5, `"by <a href="(.+?)">(.+?)</a>"')
gen last_helper = ustrregexs(2) if ustrregexm(v5, `"by <a href="(.+?)">(.+?)</a>"')
gen last_post_time = ustrregexs(1) if ustrregexm(v6, `".+">(.+?)</span>"')
drop v-v6
查看数据集,我们看到title变量下的第8行:Createa variable that is equal to 1 if "the below value" bigger than"the above value" 里面有这样的字符串:“"”,这其实是一个英文引号“"”。如果不清楚,查看一下原始网页。我们对它进行替换:
replace title = subinstr(title, `"""', `"""', .) //将引号正常显示出来
在start_time和last_post_time变量下,有很多“Today”和“Yesterday”,我们当然可以看看今天和昨天是什么日子,然后进行替换。但如果程序是在无人照看的时候运行的,这又该怎么解决呢?(我们暂不考虑和美国的时差)
我们知道,Stata中有几类返回值:r类、e类、c类。c类返回值显示的是一些系统参数和设置,日期和时间也包含在其中。
使用宏扩展函数,将日期显示出来:
local date: disp %dd-m-CY date(c(current_date), "DMY")
di "`date'"
local date: disp %dd-m-CY date(c(current_date), "DMY")-1
di "`date'"
下面我们就使用宏扩展函数,对“Today”和“Yesterday”进行替换,同时为了保持变量下字符串类型的一致性,把“-”替换为一个空格:
foreach var in start_time last_post_time{
local date: disp %dd-m-CY date(c(current_date), "DMY")
replace `var' = subinstr(`var', "Today", "`date'", .)
local date1: disp %dd-m-CY date(c(current_date), "DMY")-1
replace `var' = subinstr(`var', "Yesterday", "`date1'", .)
replace `var' = subinstr(`var', "-", " ", .)
}
save page1.dta, replace
这样我们得到了一个页面50个帖子的链接及标题,如果想要得到其他页面的,在外层对页码进行循环即可。完整程序如下:
clear
efolder statalist, cd(d:/)
copy "https://www.statalist.org/forums/forum/general-stata-discussion/general/page1" temp1.txt, replace
infix strL v 1-100000 using temp1.txt, clear
/*以下程序读者可自行选择是否运行
count if ustrregexm(v, "topic-title js-topic-title") //帖子题目和链接
count if ustrregexm(v, `"by <a href="(.+?)">(.+?)</a>"') //发帖人信息,发帖时间和回帖人信息
count if ustrregexm(v, `"<div class="posts-count">"') //回帖数量
count if ustrregexm(v, `"<div class="views-count">"') //浏览量
count if ustrregexm(v, `"<span class="post-date">"') //最近一次回帖时间
*/
keep if ustrregexm(v, `"topic-title js-topic-title"') | ///
ustrregexm(v, `"by <a href="(.+?)">(.+?)</a>"') | ///
ustrregexm(v, `"<div class="posts-count">"') | ///
ustrregexm(v, `"<div class="views-count">"') | ///
ustrregexm(v, `"<span class="post-date">"')
forvalues j = 1/6{
gen v`j' = v[_n + `j']
}
keep if mod(_n,7) == 1
drop in 1
***从v-v6中提取所需信息
gen post_url = ustrregexs(1) if ustrregexm(v, `"<a href="(.+?)".+?>(.+?)</a>"')
gen title = ustrregexs(2) if ustrregexm(v, `"<a href="(.+?)".+?>(.+?)</a>"')
gen starter_profile = ustrregexs(1) if ustrregexm(v1, `"by <a href="(.+?)">(.+?)</a>"')
gen starter = ustrregexs(2) if ustrregexm(v1, `"by <a href="(.+?)">(.+?)</a>"')
gen start_time = ustrregexs(1) if ustrregexm(v2, `".+">(.+?)</span>"')
gen posts_count = ustrregexs(1) if ustrregexm(v3, `".+(\d+)"')
gen views_count = ustrregexs(1) if ustrregexm(v4, `".+(\d+)"')
gen last_helper_profile = ustrregexs(1) if ustrregexm(v5, `"by <a href="(.+?)">(.+?)</a>"')
gen last_helper = ustrregexs(2) if ustrregexm(v5, `"by <a href="(.+?)">(.+?)</a>"')
gen last_post_time = ustrregexs(1) if ustrregexm(v6, `".+">(.+?)</span>"')
drop v-v6
***对变量中的字符串进行替换
replace title = subinstr(title, `"""', `"""', .) //将引号正常显示出来
foreach var in start_time last_post_time{
local date: disp %dd-m-CY date(c(current_date), "DMY")
replace `var' = subinstr(`var', "Today", "`date'", .)
local date1: disp %dd-m-CY date(c(current_date), "DMY")-1
replace `var' = subinstr(`var', "Yesterday", "`date1'", .)
replace `var' = subinstr(`var', "-", " ", .)
}
save page1.dta, replace
Statalist绝对是一个值得一逛的高水平论坛,在这里,你总能找到和你遇到相似问题的研究者。像上面介绍的几位教授,他们可以一下子看出你的问题出在哪里,即使,你没有发示例数据或者描述问题不甚清楚,当然,免不了被他们批评一顿!哈哈哈!
如果,在中文论坛上,大家没有找到问题的解决方法,不妨到Statalist上发个英文帖子,说不定,掐准了点儿发,一会儿Cox教授或者Schechter教授就会为你指点迷津!
最后,以一段话作为结尾:
现在,论文越来越不好发,到处都是拒稿
有拒稿的地方,一定有问题
有问题,那就一定会有转机
有个论坛,叫Statalist
上面有很多大牛,专门替人解除烦恼
看来,你的年纪应该也有二十多快三十了
呐,这几年读研、读博、工作
总有些数据处理的痛是你不愿意再提,有些实证你也不想再做
你觉得,有些审稿人曾经虐你千千万万遍,他们对不起你
也许你想过…
不写了
但是你不敢
哈,又或是你觉得自己确实有问题
其实,梳理一下思路,整理一下问题,发个英文帖子
也不是很难
呐,有一些学者,眼神非常犀利
碰巧乐于助人,也有一些空闲时间
只要你好好看一看发帖须知,把问题描述清楚
他们一定可以帮你解决问题
哈,你尽管考虑一下
——《东邪西毒》
关于我们
微信公众号“Stata and Python数据分析”分享实用的stata、python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到有关数据处理、分析等问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。