探索怪谈竞赛中文章作者与评分者Karma等级分布之间的统计相关性:有序多类别逻辑回归分析
2024年4月12日
修订 16
评分
21
↑ 58
↓ 27
支持率
68%
总票数 85
Wilson 95% 下界
57.7%
在相同票数下更稳健的支持率估计
争议指数
0.867
评分趋势
按周聚合 加载图表中...
最近修订
1 / 6
最近投票
1 / 9
2024-04-17
2024-04-17
2024-04-17
2024-04-17
2024-04-16
2024-04-15
2024-04-15
2024-04-14
2024-04-14
2024-04-14
相关页面
暂无推荐
页面源码
[[>]]
[[module Rate]]
[[/>]]
++ 引言:
随着[https://scp-wiki-cn.wikidot.com/hidden-tale-hub 怪谈竞赛]的落幕,一个有趣的问题可以被提出:文章作者的Karma等级与评分者的Karma等级分布之间是否存在统计学相关性。本文将采用怪谈竞赛的全部参赛作品作为研究样本,进行探索性数据分析,检验评分者的Karma等级与作者的Karma等级之间的关联性。
++ 获取样本:
通过Wikidot的listpage模块以及基于Python的爬虫操作,我们收集了作者的Karma等级、作品页面链接及评分者的Karma等级信息,并将这些数据导出为CSV格式以便进行后续分析。数据收集分为以下两步:
[[collapsible show="1.获取作品页面连接及作者的Karma等级"]]
[[code type="python"]]
import random
import string
from fake_useragent import UserAgent
import requests
import time
import re
NUM_THREADS = 30
def getHTML(link):
rHeader = {
'User-Agent': f'{UserAgent().random}',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Referer': 'https://scp-wiki-cn.wikidot.com/',
}
requestRes = requests.get(link,headers=rHeader)
return requestRes
def postHTML(link, data):
header = {
"User-Agent": f'{UserAgent().random}',
"Content-Type": "application/x-www-form-urlencoded",
"Referer": "https://scp-wiki-cn.wikidot.com/",
}
session = requests.Session()
url = 'https://scp-wiki-cn.wikidot.com/ajax-module-connector.php'
session.cookies.set('wikidot_token7', token, domain='scp-wiki-cn.wikidot.com', path='/')
response = session.post(url, headers= header, data=data).json()["body"]
return response
def getAuthurAndLink(mainRes):
div_pattern = re.compile(r'<div class="list-pages-box">\s*<p>(.*?)\s*</div>', re.DOTALL)
div_table = re.findall(div_pattern,mainRes)[0]
info_pattern = re.compile(r'<a href="(/[^"]+)">([^<]+)</a>.*?<a href="(http://www.wikidot.com/user:info/[^"]+)"[^>]+>([^<]+)</a>.*?(\d+)')
info_match = re.findall(info_pattern,div_table)
searchDictList = []
verifyDictList = []
for link, title, userlink, username, score in info_match:
searchDict = {"link": "https://scp-wiki-cn.wikidot.com"+link, "userlink": userlink}
verifyDict = {"title": title, "username": username.replace(" ",""), "score": score}
searchDictList.append(searchDict)
verifyDictList.append(verifyDict)
return searchDictList,verifyDictList
mainRes = getHTML("https://scp-wiki-cn.wikidot.com/eddy-sandbox-1")
searchDictList,verifyDictList = getAuthurAndLink(mainRes.text)
karmaList = ["none", "low", "medium", "high", "veryhigh", "guru"]
token = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
globalKarma = {}
[[/code]]
[[/collapsible]]
[[collapsible show="2.获取作品投票者的Karma等级"]]
[[code type="python"]]
import threading
from concurrent.futures import ThreadPoolExecutor
def getKarma(userLink,expectUserName):
if expectUserName in globalKarma.keys():
print(f"Output User {expectUserName} of Karma {globalKarma[expectUserName]}")
return globalKarma[expectUserName]
else:
requestRes = getHTML(userLink)
pattern = re.compile(r'<h1 class="profile-title">\s*<img [^>]+>\s*([^\n]+)\s*</h1>.*?<dt>Karma level:</dt>\s*<dd>\s*([^<]+)', re.DOTALL)
match = re.search(pattern,requestRes.text)
foundUserName, karma = match.groups()
foundUserName = foundUserName.replace(" ","")
karma = karma.replace(" ","")
if(foundUserName != expectUserName):
print(f"{foundUserName} != {expectUserName}")
if not(karma in karmaList):
print(f"Given level {karma} not valid")
time.sleep(1)
globalKarma[expectUserName] = karma
print(f"Record User {expectUserName} of Karma {karma}")
return karma
def getVoterList(pageLink,expectVoterNum):
requestsRes = getHTML(pageLink)
pattern = re.compile(r'WIKIREQUEST.info.pageId = (.*?);', re.DOTALL)
pageId = re.search(pattern, requestsRes.text).group(1)
data = {
'wikidot_token7': token,
'pageId': pageId,
'moduleName': 'pagerate/WhoRatedPageModule'
}
votersHtml = postHTML(pageLink,data)
votersPattern = re.compile(r'<a href="(http://www.wikidot.com/user:info/[^"]+)"[^>]*>([^<]+)</a></span>\s*<span style="color:#777">\s*([+-])', re.DOTALL)
deleteNum = votersHtml.count('deleted')
user_ratings = votersPattern.findall(votersHtml)
time.sleep(1)
return user_ratings
def process_author(inputTupel):
searchDict,verifyDict = inputTupel
print(f'Processing {verifyDict["username"]}')
authorDict = {
"authorKarma": getKarma(searchDict["userlink"], verifyDict["username"]),
"none_upvote": 0, "none_downvote": 0,
"low_upvote": 0, "low_downvote": 0,
"medium_upvote": 0, "medium_downvote": 0,
"high_upvote": 0, "high_downvote": 0,
"veryhigh_upvote": 0, "veryhigh_downvote": 0,
"guru_upvote": 0, "guru_downvote": 0,
"link": searchDict["link"]
}
votersList = getVoterList(searchDict["link"], verifyDict["score"])
for voterLink, voterName, vote in votersList:
voterKarma = getKarma(voterLink, voterName.replace(" ", ""))
vote_type = "upvote" if vote == '+' else "downvote"
karma_key = f"{voterKarma}_{vote_type}"
if karma_key in authorDict:
authorDict[karma_key] += 1
else:
print(f"Unrecognized karma level or vote type: {karma_key}")
return authorDict
infoDictList = []
with ThreadPoolExecutor(max_workers=NUM_THREADS) as executor:
infoDictList += list(executor.map(process_author, zip(searchDictList, verifyDictList)))
[[/code]]
[[/collapsible]]
++ 数据介绍:
本数据集包含72个作品的相关信息,记录了以下变量(1个类别变量和6个定量变量):
* **authorKarma**: 作者的Karma等级,共为6种(无,低,中,高,非常高,宗师)
* **none**: 投票者中Karma等级为无的数量。
* **low**:投票者中Karma等级为低的数量。
* **medium**:投票者中Karma等级为中的数量。
* **high**:投票者中Karma等级为高的数量
* **veryhigh**:投票者中Karma等级为非常高的数量。
* **guru**:投票者中Karma等级为宗师的数量
为了从评分者的Karma等级分布推断作者的Karma等级,作者的Karma等级将作为因变量,其余的定量变量作为自变量。
++ 单变量描述性统计:
[[collapsible show="+ 展开"]]
----
+++ 作者的Karma等级:
[[image http://scpsandboxcn.wikidot.com/local--files/html-1/karmafactored.png]]
|| 无 || 低 || 中 || 高 || 非常高 || 宗师 ||
|| 3 || 3 || 14 || 25 || 17 || 10 ||
----
+++ 投票者Karma等级为无的数量:
[[image http://scpsandboxcn.wikidot.com/local--files/html-1/none.png]]
|| 最小值 || 第一四分位数 || 中位数 || 平均值 || 第三四分位数 || 最大值 ||
|| 7.00 || 16.00 || 23.00 || 29.68 || 34.50 || 118.00 ||
----
+++ 投票者Karma等级为低的数量:
[[image http://scpsandboxcn.wikidot.com/local--files/html-1/low.png]]
|| 最小值 || 第一四分位数 || 中位数 || 平均值 || 第三四分位数 || 最大值 ||
|| 1.00 || 5.00 || 7.00 || 8.68 || 12.00 || 25.00 ||
----
+++ 投票者Karma等级为中的数量:
[[image http://scpsandboxcn.wikidot.com/local--files/html-1/medium.png]]
|| 最小值 || 第一四分位数 || 中位数 || 平均值 || 第三四分位数 || 最大值 ||
|| 1.00 || 3.00 || 5.00 || 6.08 || 7.25 || 20.00 ||
----
+++ 投票者Karma等级为高的数量:
[[image http://scpsandboxcn.wikidot.com/local--files/html-1/high.png]]
|| 最小值 || 第一四分位数 || 中位数 || 平均值 || 第三四分位数 || 最大值 ||
|| 1.00 || 5.00 || 7.00 || 7.75 || 10.00 || 24.00 ||
----
+++ 投票者Karma等级为非常高的数量:
[[image http://scpsandboxcn.wikidot.com/local--files/html-1/veryhigh.png]]
|| 最小值 || 第一四分位数 || 中位数 || 平均值 || 第三四分位数 || 最大值 ||
|| 1.00 || 3.00 || 6.00 || 7.46 || 10.00 || 29.00 ||
----
+++ 投票者Karma等级为宗师的数量:
[[image http://scpsandboxcn.wikidot.com/local--files/html-1/guru.png]]
|| 最小值 || 第一四分位数 || 中位数 || 平均值 || 第三四分位数 || 最大值 ||
|| 0.00 || 0.00 || 1.00 || 0.88 || 1.00 || 5.00 ||
----
在自变量们的直方图中,注意到全部变量的分布呈单峰分配和右偏态。在Karma等级为无和中的直方图中,可以观测到轻微的异常值,且均分布在右侧,说明存在少量作品使得Karma等级为无和中的用户进行大量投票。
[[/collapsible]]
++ 构建统计模型:
当样本中的因变量为类别变量时,逻辑回归模型是一种有效的建模方式。考虑到Karma等级是一系列有序的类别[[footnote]] 确立Karma等级的有序性,即从“无”到“宗师”的递增顺序。
[[/footnote]],我们将选择有序多类别逻辑回归模型进行数据探索分析。以下使用R语言进行实现:
导入模型并确保类别变量的有序性:
[[code type="r"]]
cpRes2 <- readr::read_csv("SCPresult2.csv")
cpRes2$karma_factored <- factor(cpRes2$authorKarma,
levels=c("none", "low", "medium","high","veryhigh","guru"),
ordered=TRUE)
[[/code]]
[[code type="r"]]
cpModel2 <- polr(karma_factored ~ none + low + medium + high + veryhigh + guru, data = cpRes2, Hess=TRUE, method="logistic", control = list(maxit = 50, reltol = 1e-5))
[[/code]]
++ 模型的数学表达:
设因变量为Y,有序多类别逻辑回归模型的形式为:
[[math]]
\begin{equation}
Y = \left\{
\begin{array}{lr}
分类 C (最高) & \\
分类 C -1 &\\
... &\\
分类 2 &\\
分类 1 (最低)
\end{array}
\right.
\end{equation}
[[/math]]
定义累计概率模型为:[[$ P(Y\leq j)$]]对于 [[$ j = 1, 2, ...., c -1$]]:
[[math]]
P(Y\leq 1) &= P(Y=1) \\ P(Y\leq 2) &= P(Y=1 \text{ or } Y=2) \\ &.... \\ P(Y\leq c-1) &= P(Y=1 \text{ or } Y=2 \text{ or }... \text{ or } Y= c-1)
[[/math]]
由此,当自变量为[[$X_1, X_2, .... X_k$]]时,可得以下模型[[footnote]] 其中log为自然对数 [[/footnote]]:
[[math]]
\log(\frac{P(Y\leq 1)}{P(Y>1)}) &= \beta^{(1)}_0 + \beta_1X_1 + \beta_2X_2 + .... + \beta_kX_k \\
\log(\frac{P(Y\leq 2)}{P(Y>2)})&= \beta^{(2)}_0 + \beta_1X_1 + \beta_2X_2 + .... + \beta_kX_k \\
&...\\
\log(\frac{P(Y\leq c-1)}{P(Y>c-1)}) &= \beta^{(c-1)}_0 + \beta_1X_1 + \beta_2X_2 + .... + \beta_kX_k
[[/math]]
++ 模型统计:
[[code]]
系数:
值 标准误差 t值
none 0.03636 0.02946 1.2340
low -0.01126 0.09545 -0.1180
medium -0.27223 0.13023 -2.0903
high -0.03265 0.09991 -0.3268
veryhigh 0.25541 0.10082 2.5334
guru 0.39791 0.28777 1.3827
截距:
值 标准误差 t值
none|low -2.4281 0.7291 -3.3303
low|medium -1.6682 0.6071 -2.7480
medium|high -0.0908 0.5020 -0.1808
high|veryhigh 1.7864 0.5265 3.3928
veryhigh|guru 3.7467 0.7131 5.2540
残差离差: 194.5533
AIC: 216.5533
[[/code]]
++ 建立模型:
注意到预测变量的值应取反,考虑到R语言实现polr函数时的具体实现。定义:
||[[$X_1$]]||[[$X_2$]]||[[$X_3$]]||[[$X_4$]]||[[$X_5$]]||[[$X_6$]]||
||"无"的数量||"低"的数量||"中"的数量||"高"的数量||"非常高"的数量||"宗师"的数量||
[[math]]
\log(\frac{P(Y\leq 无)}{P(Y>无)}) &= -2.428 + -0.036X_1+ 0.011X_2 + 0.272X_3 + 0.033X_4 + -0.255X_5 + -0.398X_6\\
\log(\frac{P(Y\leq 低)}{P(Y>低)}) &= -1.6682 + -0.036X_1+ 0.011X_2 + 0.272X_3 + 0.033X_4 + -0.255X_5 + -0.398X_6 \\
\log(\frac{P(Y\leq 中)}{P(Y>中)}) &= -0.0908 + -0.036X_1+ 0.011X_2 + 0.272X_3 + 0.033X_4 + -0.255X_5 + -0.398X_6 \\
\log(\frac{P(Y\leq 高)}{P(Y>高)}) &= 1.7864 + -0.036X_1+ 0.011X_2 + 0.272X_3 + 0.033X_4 + -0.255X_5 + -0.398X_6 \\
\log(\frac{P(Y\leq 非常高)}{P(Y>非常高)}) &= 3.7467 + -0.036X_1+ 0.011X_2 + 0.272X_3 + 0.033X_4 + -0.255X_5 + -0.398X_6
[[/math]]
++ 检验模型:
----
+++ 平行性检验:确保自变量在不同的回归方程中对因变量的影响是一致的。
* 无效假设([[$H0$]]):满足平行性检验。
* 备择假设([[$HA$]]):不满足平行性检验。
输出如下:
[[code]]
--------------------------------------------
Test for X2 df probability
--------------------------------------------
Omnibus 18.51 24 0.78
none 0.83 4 0.93
low 2.53 4 0.64
medium 4.4 4 0.35
high 3.67 4 0.45
veryhigh 2.82 4 0.59
guru 3.08 4 0.54
--------------------------------------------
[[/code]]
注意到概率下全部项均大于预先假设的显著性水平(0.05),故不存在显著的证据表明无效假设被拒绝,即模型通过了平行性检验。
----
+++ 显著性检验:通过方差分析,比较只含截距的模型与完整模型间的差异,验证模型的预测能力。
* 无效假设([[$H0$]]):两个模型间的平均数的差异不显著(没有区别)
* 备择假设([[$HA$]]):显著(存在区别)
输出如下:
[[code]]
Likelihood ratio tests of ordinal regression models
Response: karma_factored
Model Resid. df Resid. Dev
1 1 67 225.4382
2 none + low + medium + high + veryhigh + guru 61 194.5533
Test Df LR stat. Pr(Chi)
1
2 1 vs 2 6 30.88489 2.666474e-05
[[/code]]
因p值远远低于0.05,故拒绝无效假设,即原模型具备统计学意义。
++ 分析:
注意到Karma等级为“中”和“非常高”的数量对于因变量的预测具备显著性[[footnote]] 在现有的显著性水平下(α=0.05),回归系数显著性的判断标准是系数的绝对值大于其标准误差的两倍。 [[/footnote]] 。通过计算累计优势比,我们可以评估解释变量对有序响应变量的影响强度和方向:
----
|| Karma等级为"中”的数量的回归系数||累计优势比 || 统计学分析|| 结论||
||0.27223 ||[[$ \text e^{0.27223} = 1.312889$]] || 在控制了其他相关因素(除了'中'以外的所有解释变量)的情况下,我们预估对于一个作品,Karma等级为‘中’的评分者每增加一名,与给定等级相比,不超过任何给定等级的累计优势比平均为之前的1.312889倍。|| Karma等级“中”的评分者的增加将降低该作品作者具备更高Karma等级的概率。||
----
|| Karma等级为"非常高”的数量的回归系数||累计优势比 || 统计学分析|| 结论||
||-0.25541 || [[$ \text e^{-0.25541} = 0.7745988$]] || 在控制了其他相关因素(除了'非常高'以外的所有解释变量)的情况下,我们预估对于一个作品,Karma等级为‘非常高’的评分者每增加一名,与给定等级相比,不超过任何给定等级的累计优势比平均为之前的0.7745988倍。|| Karma等级“非常高”的评分者的增加将增加该作品作者具备更高Karma等级的概率。||
++ 对比:
在统计了cn3000, cn2000, cn1000, 2024冬季征文,2023冬季征文,2023夏季征文,2022冬季征文,2022夏季征文,2022电子游戏竞赛,2023撕咬竞赛等竞赛的全部484个具备有效账户作者的参赛作品[[footnote]] 共504个,其中20个作品作者的账户已删除。 [[/footnote]]后。我们基于该数据集和已有统计方法,构建了一个有效并符合检验条件的模型。
[[code]]
Call:
polr(formula = karma_factored ~ none + low + medium + high +
veryhigh + guru, data = cpCompareRes, Hess = TRUE, method = "logistic")
Coefficients:
Value Std. Error t value
none 0.0007420 0.004123 0.1799
low -0.0302076 0.029411 -1.0271
medium -0.0001873 0.018728 -0.0100
high -0.0224676 0.020102 -1.1177
veryhigh 0.1036817 0.017584 5.8963
guru 0.0232437 0.042801 0.5431
Intercepts:
Value Std. Error t value
none|low -3.2861 0.3693 -8.8986
low|medium -2.4436 0.2593 -9.4224
medium|high -1.1063 0.1720 -6.4303
high|veryhigh 0.8522 0.1535 5.5526
veryhigh|guru 3.1637 0.2189 14.4530
Residual Deviance: 1230.326
AIC: 1252.326
[[/code]]
显著性测试[[footnote]]
[[code]]
2.5 % 97.5 %
none -0.006810603 0.008187392
low -0.087748098 0.026821282
medium -0.037163066 0.036567442
high -0.061910545 0.017025441
veryhigh 0.069558869 0.138497331
guru -0.060443273 0.107357075
[[/code]]
[[/footnote]]表明,在当前显著性水平下,仅Karma等级为"非常高”的数量在预测作者的Karma等级时具备显著性:在控制了其他相关因素(除了'非常高'以外的所有解释变量)的情况下,我们预估对于一个作品,Karma等级为‘非常高’的评分者每增加一名,与给定等级相比,不超过任何给定等级的累计优势比平均为之前的0.9015122倍。
由此,我们可以发现在该数据集中,相对于其他Karma等级,"非常高"级别的评分者的数量对预测作者Karma等级的影响较小。即与原数据集(怪谈竞赛数据集)相比,"非常高"级别评分者的增加对提高作者达到更高Karma等级的概率的幅度的降低。
不难发现,怪谈数据集与该数据集的回归系数具备相同的符号项,表明尽管两者在统计学意义上的影响幅度不同,影响的方向是一致的,可能反映了不同数据集(环境)下影响趋势的稳定性。
++ 结论:
本文探讨了怪谈竞赛中作品的作者Karma等级与评分者Karma等级分布之间的关联性。通过有序多类别逻辑回归分析,我们发现,Karma等级为“中”和“非常高”的评分者数量对作者Karma等级的预测具有统计学意义:当一个作品的评分者中,拥有“中”Karma等级的评分者数量增加时,作者具备更高Karma等级的概率降低;而“非常高”Karma等级的评分者数量增加时,该概率增加。
这一分析揭示了评分者Karma等级分布与作者Karma等级之间的潜在相关性,但我们必须认识到,相关性并不等同于因果关系。此外,考虑到数据集规模相对较小,所得出的模型推断可能受到样本选择的限制,其外推性需要谨慎对待。此分析的结果应被视为初步的,它为未来在更大规模和更广泛样本上进行研究提供了理论基础和动机。