18、多层次分析与文本分析:从嵌套数据到文档分类

多层次分析与文本分析:从嵌套数据到文档分类

1. 多层次分析

在处理嵌套数据时,评估预测质量是一个重要的环节。目前并没有完美的方法来衡量嵌套数据预测的质量,不过可以使用相关测试作为一个简单的估计方法。由于数据集具有嵌套结构,我们需要对每个医院分别进行测试,具体代码如下:

correls = matrix(nrow=17,ncol=3)
colnames(correls) = c("Correlation", "p value", "r squared")
for (i in 1:17){
    dat = subset(NursesMLtest, hosp == i)
    correls[i,1] = cor.test(dat$predicted, dat$WorkSat)[[4]]
    correls[i,2] = cor.test(dat$predicted, dat$WorkSat)[[3]]
    correls[i,3] = correls[i,1]^2
}
round(correls, 3)

运行代码后,输出结果展示了各医院预测值与观测值之间的相关性,除了第 10 家医院外,其他医院的相关性在 p < 0.05 水平上均显著。第三列显示了预测值和观测值共享的方差部分,具体数据如下表所示:
| 序号 | Correlation | p value | r squared |
| ---- | ---- | ---- | ---- |
| [1,] | 0.488 | 0.000 | 0.238 |
| [2,] | 0.282 | 0.047 | 0.080 |
| [3,] | 0.511 | 0.000 | 0.262 |
| [4,] | 0.481 | 0.000 | 0.232 |
| [5,] | 0.471 | 0.001 | 0.222 |
| [6,] | 0.342 | 0.015 | 0.117 |
| [7,] | 0.347 | 0.014 | 0.120 |
| [8,] | 0.385 | 0.006 | 0.148 |
| [9,] | 0.498 | 0.000 | 0.248 |
| [10,] | 0.203 | 0.157 | 0.041 |
| [11,] | 0.482 | 0.000 | 0.232 |
| [12,] | 0.459 | 0.001 | 0.211 |
| [13,] | 0.594 | 0.000 | 0.353 |
| [14,] | 0.358 | 0.011 | 0.128 |
| [15,] | 0.565 | 0.000 | 0.320 |
| [16,] | 0.442 | 0.001 | 0.195 |
| [17,] | 0.823 | 0.000 | 0.677 |

为了进一步测试预测值与观测值的关系,我们可以使用多层次分析,将预测模型(modelPred)与零模型(nullPred)进行比较。由于前面的相关性分析显示不同医院的数据存在一定差异,因此我们在模型中加入随机斜率。在进行比较之前,需要先对预测值进行中心化处理,具体操作步骤如下:
1. 对预测值进行中心化:

NursesMLtest$predicted = NursesMLtest$predicted - mean(NursesMLtest$predicted)
  1. 构建零模型:
nullPred = lmer(WorkSat ~ 1 + (1|hosp), data=NursesMLtest, REML = F)
  1. 构建预测模型:
modelPred = lmer(WorkSat ~ predicted + (1+predicted|hosp), data=NursesMLtest, REML = F)

通过运行以下代码可以比较两个模型的拟合效果:

anova(nullPred,modelPred)

结果表明,modelPred 比 nullPred 更能拟合数据。另外,运行以下代码可以得到工作满意度方差中由预测值解释的比例:

r.squaredLR(modelPred,nullPred)

结果显示,预测值解释了工作满意度 27.99% 的方差,不过这个值的好坏需要结合具体情况来判断。

2. 文本分析简介

在当今的数据世界中,文本数据占据了公司和数据分析师可获取数据的很大一部分。然而,文本数据通常以非结构化的形式存在,例如电子邮件、公司备忘录或博客文章等。这些文本主要由单词组成句子,句子再组成段落,更复杂的文档还包含子部分、章节等。人类可以从这种基本结构和元素之间的关系中获取意义,但机器要对文档进行分类和提取意义,就需要进行文本预处理。

文本预处理通常包括以下几个步骤:
1. 导入语料库。
2. 将文本转换为小写,确保在分析过程中,包含大写字母的单词与不包含大写字母的单词被视为相同,例如 “Documents”、”DOCUMENTS” 和 “documents” 在转换后是相同的。
3. 去除标点符号,使前后带有标点符号的单词与不带标点符号的单词被同等对待,例如 “documents.”、”documents:” 和 “documents” 在去除标点后是相同的。
4. 去除文本中的数字,因为数字可能会干扰分析,将其视为单词处理可能会影响结果。
5. 过滤停用词,即去除大多数文档中常见但无实际信息的单词,如 “it”、”me”、”where”、”some” 等。
6. 去除原始文本中或前面操作产生的多余空格。
7. 进行词干提取,将单词替换为其词干形式,例如 “documentation”、”documented”、”documents” 和 “document” 在词干提取后是相同的。
8. 进行其他必要的转换,如分词和构建词 - 文档矩阵,以及修剪稀有词等。

以下是文本预处理步骤的 mermaid 流程图:

graph LR
    A[导入语料库] --> B[转换为小写]
    B --> C[去除标点符号]
    C --> D[去除数字]
    D --> E[过滤停用词]
    E --> F[去除多余空格]
    F --> G[词干提取]
    G --> H[其他必要转换]
3. 加载语料库

在进行文本分析之前,我们需要先加载语料库。以电影评论数据为例,我们可以通过以下步骤下载并加载数据:
1. 下载数据:

URL = "http://www.cs.cornell.edu/people/pabo/movie-review-data/review_polarity.tar.gz"
download.file(URL,destfile = "reviews.tar.gz")
untar("reviews.tar.gz")
  1. 更改工作目录:
setwd("txt_sentoken")

该文件夹包含 “pos” 和 “neg” 两个子文件夹,分别包含 1000 条正面和负面电影评论。为了加载这些评论,我们需要下载并加载 tm 包,然后分别加载正面和负面评论语料库,具体代码如下:

install.packages("tm"); library(tm)
SourcePos = DirSource(file.path(".", "pos"), pattern="cv")
SourceNeg = DirSource(file.path(".", "neg"), pattern="cv")
pos = Corpus(SourcePos)
neg = Corpus(SourceNeg)

除了使用 DirSource 加载语料库外,还可以使用数据框源或向量源,示例代码如下(无需运行):

Pos = DataframeSource(theDataframeName) # 数据框语料库
Pos = VectorSource(theVectorName) # 向量语料库

如果使用 PDF 或 Word 文档,也可以相应地进行加载(无需运行):

SourcePos = DirSource(file.path(".", "pos"), readerControl=list(reader=readPDF)) # PDF 文件
SourcePos = DirSource(file.path(".", "pos"), readerControl=list(reader=readDOC)) # Word 文件

加载完成后,我们可以检查语料库是否正确加载:

pos
neg
reviews = c(pos, neg)
reviews

输出结果显示,正面和负面评论语料库均已正确加载,合并后的语料库包含 2000 条评论。

4. 数据准备

在完成语料库加载后,接下来需要进行数据准备工作,包括对语料库进行预处理和检查,以及构建训练和测试数据集。

4.1 预处理和检查语料库

为了方便对语料库进行预处理,我们可以编写一个函数,将前面提到的预处理步骤整合在一起:

install.packages("SnowballC")
preprocess = function(corpus, stopwrds = stopwords("english")){
    library(SnowballC)
    corpus = tm_map(corpus, content_transformer(tolower))
    corpus = tm_map(corpus, removePunctuation)
    corpus = tm_map(corpus, content_transformer(removeNumbers))
    corpus = tm_map(corpus, removeWords, stopwrds)
    corpus = tm_map(corpus, stripWhitespace)
    corpus = tm_map(corpus, stemDocument)
    return(corpus)
}

使用该函数对合并后的语料库进行预处理:

processed = preprocess(reviews)

预处理完成后,我们可以创建词 - 文档矩阵,并分析语料库中的词频信息。具体操作如下:
1. 创建词 - 文档矩阵(以词频为元素):

term_documentFreq = TermDocumentMatrix(processed)
  1. 找出语料库中出现频率最高的五个词:
asMatrix = t(as.matrix(term_documentFreq))
Frequencies = colSums(asMatrix)
head(Frequencies[order(Frequencies, decreasing=T)], 5)

输出结果为:

film  movi  one  like  charact 
11109  6857  5759  3998  3855 
  1. 找出出现次数超过 3000 次的词:
Frequencies[Frequencies > 3000]

输出结果为:

charact  film  get  like  make  movi  one 
3855  11109  3189  3998  3152  6857  5759 
  1. 创建一个新矩阵,统计词在文档中的出现情况(存在为 1,不存在为 0):
Present = data.frame(asMatrix)
Present [Present>0] = 1
  1. 找出文档频率最高的五个词:
DocFrequencies = colSums(Present)
head(DocFrequencies[order(DocFrequencies, decreasing=T)], 5)

输出结果为:

film  one  movi  like  charact 
1797  1763  1642  1538  1431 
  1. 找出在超过 1400 个文档中出现的词:
DocFrequencies[DocFrequencies > 1400]

输出结果为:

charact  film  like  make  movi  one 
1431  1797  1538  1430  1642  1763 
  1. 计算出现次数超过一次的词的数量及其占总词数的比例:
total = ncol(asMatrix)
moreThanOnce = sum(DocFrequencies != Frequencies)
prop = moreThanOnce / total
moreThanOnce
total
prop

输出结果显示,总共有 30585 个词,其中 9748 个词出现次数超过一次,占比 31.9%。

由于我们的目标是对未知样本进行预测,tf - idf 度量在这种情况下更有意义,它可以增加在多个文档中出现的词的权重,使分类更加可靠。因此,我们将使用 tf - idf 度量创建一个新的词 - 文档矩阵:

term_documentTfIdf = TermDocumentMatrix(processed, control = list(weighting = function(x) weightTfIdf(x, normalize = TRUE)))

考虑到语料库中存在大量低频词,这些低频词可能会降低分类性能,我们可以使用 removeSparseTerms() 函数去除稀疏词:

SparseRemoved = as.matrix(t(removeSparseTerms(term_documentTfIdf, sparse = 0.8)))

检查去除稀疏词后词 - 文档矩阵中剩余的词的数量:

ncol(SparseRemoved)

输出结果显示,现在词 - 文档矩阵中仅剩下 202 个词。为了确保所有文档都包含至少一个剩余的词,我们可以检查文档中所有词的总和是否为 0:

sum(rowSums(as.matrix(SparseRemoved)) == 0)

输出结果为 0,说明所有文档都至少包含一个剩余的词。

4.2 计算新属性

为了对文档进行分类,我们需要创建一些新的属性。根据前面的处理,我们知道前 1000 条评论为正面评论,后 1000 条评论为负面评论,因此可以创建一个向量来表示评论的质量:

quality = c(rep(1,1000),rep(0,1000))

此外,评论的长度可能与评论的正负性有关,我们可以计算评论的长度作为一个新的属性:

lengths = colSums(as.matrix(TermDocumentMatrix(processed)))
4.3 创建训练和测试数据集

将质量属性、评论长度和词 - 文档矩阵合并成一个数据框:

DF = as.data.frame(cbind(quality, lengths, SparseRemoved))

然后将数据集划分为训练集和测试集:

set.seed(123)
train = sample(1:2000,1000)
TrainDF = DF[train,]
TestDF = DF[-train,]

通过以上步骤,我们完成了数据的加载、预处理和准备工作,为后续的文档分类奠定了基础。在接下来的部分,我们将使用不同的算法对文档进行分类,并评估分类效果。

5. 文档分类

在完成数据准备后,我们将使用多种算法对电影评论进行分类,判断评论是正面还是负面。

5.1 k - 近邻算法(k - NN)

我们将尝试使用 3 个邻居和 5 个邻居的 k - NN 算法进行分类。首先,需要加载相关的包:

library(class) # knn() 函数所在的包
library(caret) # confusionMatrix 所在的包

然后,分别使用 3 个邻居和 5 个邻居进行训练集的分类:

set.seed(975)
Class3n = knn(TrainDF[,-1], TrainDF[,-1], TrainDF[,1], k = 3)
Class5n = knn(TrainDF[,-1], TrainDF[,-1], TrainDF[,1], k = 5)

使用混淆矩阵评估 3 个邻居的分类效果:

confusionMatrix(Class3n,as.factor(TrainDF$quality))

输出结果如下:
| Prediction | 0 | 1 |
| ---- | ---- | ---- |
| 0 | 358 | 126 |
| 1 | 134 | 382 |

统计信息:
- Accuracy : 0.74
- 95% CI : (0.7116, 0.7669)
- No Information Rate : 0.508
- P - Value [Acc > NIR] : <2e - 16
- Kappa : 0.4876

可以看出,虽然准确率达到了 0.74,但 Kappa 值不理想(应至少为 0.60)。

再评估 5 个邻居的分类效果:

confusionMatrix(Class5n,as.factor(TrainDF$quality))

输出结果显示与 3 个邻居的情况差别不大,但 5 个邻居的效果更差:
| Prediction | 0 | 1 |
| ---- | ---- | ---- |
| 0 | 358 | 126 |
| 1 | 134 | 382 |

统计信息:
- Accuracy : 0.682
- 95% CI : (0.6521, 0.7108)
- No Information Rate : 0.508
- P - Value [Acc > NIR] : <2e - 16
- Kappa : 0.364

接下来,我们看看 3 个邻居的 k - NN 算法在测试集上的表现:

set.seed(975)
Class3nTest = knn(TrainDF[,-1], TestDF[,-1], TrainDF[,1], k = 3)
confusionMatrix(Class3nTest,as.factor(TestDF$quality))

输出结果如下:
| Prediction | 0 | 1 |
| ---- | ---- | ---- |
| 0 | 235 | 226 |
| 1 | 273 | 266 |

统计信息:
- Accuracy : 0.501
- 95% CI : (0.4695, 0.5324)
- No Information Rate : 0.508
- P - Value [Acc > NIR] : 0.68243
- Kappa : 0.032

可以看到,在测试集上的分类效果非常差,准确率接近随机分类,Kappa 值几乎为 0。

5.2 朴素贝叶斯算法

首先,加载所需的包并计算朴素贝叶斯模型:

library(e1071)
set.seed(345)
model <- naiveBayes(TrainDF[-1], as.factor(TrainDF[[1]]))

然后,对训练集进行分类并评估效果:

classifNB = predict(model, TrainDF[,-1])
confusionMatrix(as.factor(TrainDF$quality),classifNB)

输出结果如下:
| Prediction | 0 | 1 |
| ---- | ---- | ---- |
| 0 | 353 | 139 |
| 1 | 74 | 434 |

统计信息:
- Accuracy : 0.787
- 95% CI : (0.7603, 0.812)
- No Information Rate : 0.573
- P - Value [Acc > NIR] : < 2e - 16
- Kappa : 0.573

可以看出,在训练集上的准确率还可以,但 Kappa 值仍然较低。

接着,对测试集进行分类并评估:

classifNB = predict(model, TestDF[,-1])
confusionMatrix(as.factor(TestDF$quality),classifNB)

输出结果如下:
| Prediction | 0 | 1 |
| ---- | ---- | ---- |
| 0 | 335 | 173 |
| 1 | 120 | 372 |

统计信息:
- Accuracy : 0.707
- 95% CI : (0.6777, 0.7351)
- No Information Rate : 0.545
- P - Value [Acc > NIR] : < 2e - 16
- Kappa : 0.4148

测试集上的准确率有所下降,Kappa 值也不理想。

5.3 逻辑回归算法

我们可以使用逻辑回归来检查评论长度与评论质量之间的关联。首先,计算逻辑回归模型:

model = glm(quality~ lengths, family = binomial)
summary(model)

输出结果如下:

Call:
glm(formula = quality ~ lengths, family = binomial)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.7059  -1.1471  -0.1909   1.1784   1.3980  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -0.6383373  0.1171536  -5.449 5.07e-08 ***
lengths      0.0018276  0.0003113   5.871 4.32e-09 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 2772.6  on 1999  degrees of freedom
Residual deviance: 2736.4  on 1998  degrees of freedom
AIC: 2740.4

Number of Fisher Scoring iterations: 4

结果显示,评论长度与电影的感知质量显著相关。由于估计值是以对数优势比表示的,我们需要进行指数运算来了解斜率的实际意义:

exp(0.0018276)

输出结果为 1.001828。这个值略大于 1,说明每增加一个处理后的词,电影被认为是好的几率增加 0.01829%。

我们可以计算每个评论被分类为好电影的概率,并进行分类:

Prob1 = exp(-0.6383373 + lengths * 0.0018276) / (1 + exp(-0.6383373 + lengths * 0.0018276))
classif = Prob1
classif[classif>0.5] = 1
classif[classif<=0.5] = 0
table(classif, quality)

输出结果如下:
| quality | 0 | 1 |
| ---- | ---- | ---- |
| classif 0 | 614 | 507 |
| classif 1 | 386 | 493 |

可以看出,虽然正确分类的实例比错误分类的多,但仍有很多实例没有被正确分类。计算 Kappa 值:

cohen.kappa(table(classif, quality))

输出结果显示 Kappa 值仅为 0.11,效果不佳。

接下来,我们将评论的语言内容(202 个词)纳入逻辑回归模型:

model2 = glm(quality ~ ., family = binomial, data = TrainDF)

在训练集上进行预测并评估效果:

TrainDF$classif = fitted.values(model2, type = "response")
TrainDF$classif[TrainDF$classif>0.5] = 1
TrainDF$classif[TrainDF$classif<=0.5] = 0
confusionMatrix(TrainDF$quality, TrainDF$classif)

结果显示,准确率达到了 0.857,Kappa 值为 0.71,分类效果较为理想。

总结

本文围绕多层次分析和文本分析展开,涵盖了嵌套数据的预测质量评估、文本预处理以及多种文档分类算法的应用。以下是整个流程的 mermaid 流程图:

graph LR
    A[多层次分析] --> B[评估预测质量]
    B --> C[多层次模型比较]
    D[文本分析] --> E[文本预处理]
    E --> F[加载语料库]
    F --> G[数据准备]
    G --> H[文档分类]
    H --> I[k - NN 算法]
    H --> J[朴素贝叶斯算法]
    H --> K[逻辑回归算法]

在多层次分析中,我们通过相关测试和多层次模型比较,发现预测模型能较好地拟合嵌套数据。在文本分析方面,经过一系列的文本预处理步骤,包括导入语料库、转换大小写、去除标点和停用词等,我们成功构建了词 - 文档矩阵。在文档分类环节,不同算法的表现各有差异,逻辑回归算法在考虑评论语言内容后取得了较好的分类效果。

在实际应用中,我们可以根据具体的数据特点和任务需求选择合适的分析方法和分类算法。同时,也可以进一步探索其他算法或对现有算法进行优化,以提高分类的准确性和可靠性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值