和鲸社区数据分析每周挑战【第九十七期:技术博客文本分析】
文章目录
- 和鲸社区数据分析每周挑战【第九十七期:技术博客文本分析】
- 一、背景描述
- 二、数据说明
- 三、问题描述
- 四、数据导入
- 五、数据探索性分析
- 六、对文章标题进行文本分类预测
- 1、数据预处理
- 2、逻辑回归模型
- 3、支持向量机模型
- 4、随机森林模型
- 5、模型对比
一、背景描述
本数据集包含了34k+条GeeksforGeeks网站上的文章数据。
GeeksforGeeks.com是一个计算机科学门户网站,为初学者和有经验的程序员提供各种编程语言的学习资源和参考资料。该网站涵盖了所有计算机科学核心课程,包括数据结构、算法、操作系统、计算机网络、数据库等。
如果你正在学习数据结构和算法,你可以在该网站上找到许多关于如何解决不同类型问题的文章。这些文章通常包括详细的解释和示例代码,以帮助你更好地理解问题和解决方案。
此外,你还可以使用在线编辑器来练习编程技能,并在完成挑战后查看其他用户的答案以获取灵感和想法。
二、数据说明
字段 | 说明 |
---|---|
title | 文章的标题 |
author_id | 文章的作者 |
last_updated | 文章的最后更新日期 |
link | 文章在GeeksforGeeks上的链接 |
category | 文章分类 |
三、问题描述
网站文章数据EDA
标题内容文本分类
四、数据导入
import pandas as pd
articles_df = pd.read_csv('articles.csv')
articles_df.head()
数据集包含以下字段:
- title:文章的标题
- author_id:文章的作者
- last_updated:文章的最后更新日期
- link:文章在GeeksforGeeks上的链接
- category:文章的分类
# 查看数据分布
articles_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 34574 entries, 0 to 34573
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 title 34574 non-null object
1 author_id 34555 non-null object
2 last_updated 34556 non-null object
3 link 34574 non-null object
4 category 34574 non-null object
dtypes: object(5)
memory usage: 1.3+ MB
这是我们数据集的基本信息:
- 我们有 34,574 条数据。
- 数据集包含5个字段,都是对象(字符串)类型的数据。
- title, link, 和 category 字段没有缺失值。
- author_id 和 last_updated 字段存在少量的缺失值。
五、数据探索性分析
接下来我们将会检查这些字段的具体数据。先从 category 字段开始,我们将检查这个字段中有多少个不同的类别以及每个类别有多少个样本。这将帮助我们理解文章是如何分类的,以及每个类别的样本数量是否均衡。然后,我们将使用可视化的方式来展示这些信息,以便于我们更直观地理解数据。
category_counts = articles_df['category'].value_counts()
category_counts
medium 10440
easy 9663
basic 8144
hard 4232
expert 2095
Name: category, dtype: int64
从 category 字段的分析结果我们可以看出:
-
文章被分为五个类别:medium、easy、basic、hard 和 expert。
-
medium 类别的文章数量最多,有 10,440 篇。
-
expert 类别的文章数量最少,有 2,095 篇。
from pyecharts import options as opts
from pyecharts.charts import Bar
categories = category_counts.index.tolist()
article_counts = category_counts.values.tolist()
# 创建柱状图对象
bar_chart = Bar()
bar_chart.set_global_opts(
title_opts=opts.TitleOpts(title="文章类别分布"),
xaxis_opts=opts.AxisOpts(name="类别"),
yaxis_opts=opts.AxisOpts(name="文章数量"),
)
# 添加数据
bar_chart.add_xaxis(categories)
bar_chart.add_yaxis("", article_counts)
# 设置x轴标签旋转角度
bar_chart.set_series_opts(
label_opts=opts.LabelOpts(
position="top", rotate=0
)
)
# 展示图表
bar_chart.render_notebook()
这是文章类别的分布情况。从这个柱状图中,我们可以看到:
-
medium 类别的文章数量最多,这可能意味着这个网站上的大部分文章难度适中。
-
easy 和 basic 类别的文章数量也比较多,这可能是为了更好地满足编程新手的学习需求。
-
hard 和 expert 类别的文章数量相对较少,这可能反映出高难度文章的需求相对较小,或者编写高难度文章的成本较高。
接下来,我们将分析 author_id 字段。我们将检查有多少不同的作者以及每个作者发布了多少篇文章。这将帮助我们理解哪些作者比较活跃,以及作者与文章数量的分布情况。
author_counts = articles_df['author_id'].value_counts()
# 显示文章最多的前10位作者
author_counts.head(10)
GeeksforGeeks 11957
ManasChhabra2 317
Striver 265
manjeet_04 246
Chinmoy Lenka 192
pawan_asipu 157
sarthak_ishu11 151
anuupadhyay 148
Shubrodeep Banerjee 143
ankita_saini 125
Name: author_id, dtype: int64
这是发布文章数量最多的10个作者及其发布的文章数量:
GeeksforGeeks
是发布文章最多的作者,发布了 11,957 篇文章。ManasChhabra2
发布了 317 篇文章。Striver
发布了 265 篇文章。manjeet_04
发布了 246 篇文章。Chinmoy Lenka
发布了 192 篇文章。pawan_asipu
发布了 157 篇文章。sarthak_ishu11
发布了 151 篇文章。anuupadhyay
发布了 148 篇文章。Shubrodeep Banerjee
发布了 143 篇文章。ankita_saini
发布了 125 篇文章。
这些信息可以帮助我们理解哪些作者在平台上比较活跃。
authors = author_counts.head(10).index.tolist()
article_counts = author_counts.head(10).values.tolist()
# 创建柱状图对象
bar_chart = Bar()
bar_chart.set_global_opts(
title_opts=opts.TitleOpts(title="发表文章最多的10位作者"),
xaxis_opts=opts.AxisOpts(name="作者"),
yaxis_opts=opts.AxisOpts(name="文章数量"),
)
# 添加数据
bar_chart.add_xaxis(authors)
bar_chart.add_yaxis("", article_counts)
# 设置x轴标签旋转角度
bar_chart.set_series_opts(
label_opts=opts.LabelOpts(
position="top", rotate=45
)
)
# 展示图表
bar_chart.render_notebook()
从图中,我们可以清晰地看到:
-
GeeksforGeeks
是最活跃的作者,发布的文章数量远超其他作者。 -
其他位于列表前十的作者的文章数量差距不大,范围在 100-300 篇之间。
接下来,我们将处理 last_updated
字段。这个字段是日期字段,我们需要将其转换为正确的日期格式,然后我们可以分析文章的更新情况,例如在一年中的哪个月份更新的文章最多,每年的文章更新数量有何变化等。
# 将“last_updated”列转换为日期时间
articles_df['last_updated'] = pd.to_datetime(articles_df['last_updated'], errors='coerce')
articles_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 34574 entries, 0 to 34573
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 title 34574 non-null object
1 author_id 34555 non-null object
2 last_updated 34460 non-null datetime64[ns]
3 link 34574 non-null object
4 category 34574 non-null object
dtypes: datetime64[ns](1), object(4)
memory usage: 1.3+ MB
last_updated
字段已经成功转换为日期类型。然而,值得注意的是,这个字段的非空值数量从 34,556 降低到了 34,460。这意味着在转换过程中,有些无法转换为有效日期的值被转换为了空值。这是因为我们在调用 pd.to_datetime
函数时设置了 errors='coerce'
参数,当遇到无法转换为日期的值时,Pandas 会将其转换为 NaT
(即空日期)。
接下来,我们将提取年份和月份信息,并分析文章在不同年份和月份的更新情况。我们可以通过计数和可视化来展示这些信息。这将有助于我们理解文章的更新趋势,例如哪些月份的文章更新较多,以及每年的文章更新数量是否有增长或减少的趋势等。
# 从“last_updated”列中提取年份和月份
articles_df['year'] = articles_df['last_updated'].dt.year
articles_df['month'] = articles_df['last_updated'].dt.month
articles_df.head()
已经成功从 last_updated 字段中提取出了年份和月份信息,并将其存储在了新的字段 year 和 month 中。
# 统计每年更新的文章数量
year_counts = articles_df['year'].value_counts().sort_index()
# 统计每个月更新的文章数量
month_counts = articles_df['month'].value_counts().sort_index()
year_counts, month_counts
(2010.0 1
2011.0 1
2012.0 5
2013.0 70
2014.0 53
2015.0 172
2016.0 200
2017.0 1021
2018.0 2524
2019.0 3985
2020.0 4625
2021.0 18616
2022.0 3187
Name: year, dtype: int64,
1.0 3206
2.0 2716
3.0 1510
4.0 2318
5.0 3331
6.0 4060
7.0 2824
8.0 3193
9.0 2919
10.0 2775
11.0 2933
12.0 2675
Name: month, dtype: int64)
# 绘制每年文章更新数量柱状图
bar_chart_year = Bar()
bar_chart_year.set_global_opts(
title_opts=opts.TitleOpts(title="每年文章更新数量"),
xaxis_opts=opts.AxisOpts(name="年份"),
yaxis_opts=opts.AxisOpts(name="文章数量"),
)
# 添加数据
bar_chart_year.add_xaxis(year_counts.index.tolist())
bar_chart_year.add_yaxis("", year_counts.values.tolist())
# 设置x轴标签旋转角度
bar_chart_year.set_series_opts(
label_opts=opts.LabelOpts(
position="top", rotate=45
)
)
# 展示图表
bar_chart_year.render_notebook()
# 绘制每月文章更新数量柱状图
bar_chart_month = Bar()
bar_chart_month.set_global_opts(
title_opts=opts.TitleOpts(title="每月文章更新数量"),
xaxis_opts=opts.AxisOpts(name="月份"),
yaxis_opts=opts.AxisOpts(name="文章数量"),
)
# 添加数据
bar_chart_month.add_xaxis(month_counts.index.tolist())
bar_chart_month.add_yaxis("", month_counts.values.tolist())
# 设置x轴标签旋转角度
bar_chart_month.set_series_opts(
label_opts=opts.LabelOpts(
position="top", rotate=0
)
)
# 展示图表
bar_chart_month.render_notebook()
这是每年和每月文章更新数量的柱状图。
从每年文章更新数量的柱状图中,我们可以看到:
- 从2010年到2017年,文章的更新数量逐年增加,但增加的速度相对较慢。
- 2018年,文章的更新数量显著增加,达到了2524篇。
- 2019年和2020年,文章的更新数量持续增加,分别达到了3985篇和4625篇。
- 2021年,文章的更新数量达到了峰值,共有18616篇文章更新。
- 2022年,文章的更新数量有所下降,但仍然保持在较高的水平(3187篇)。
从每月文章更新数量的柱状图中,我们可以看到:
- 每个月的文章更新数量差距不大,大致在2500篇到4000篇之间。
- 6月的文章更新数量最多(4060篇),3月的文章更新数量最少(1510篇)。
这些信息可以帮助我们理解文章的更新趋势,以及在一年中的哪些时间更新的文章较多。
六、对文章标题进行文本分类预测
1、数据预处理
在进行文本分类预测之前,我们需要对数据进行预处理。文本预处理是一个重要步骤,它可以帮助我们清理和准备数据,以便更有效地进行模型训练。预处理步骤可能包括:
-
文本清理:删除或替换特定的字符或词,例如标点符号、数字、停用词等。
-
分词:将文章标题分解为单词或短语(称为“词元”)。
-
向量化:将词元转换为能被模型理解的数值向量。
在进行预处理之前,我们需要将数据集分为训练集和测试集。训练集用于训练模型,测试集用于评估模型的性能。我们将使用 sklearn 的 train_test_split
函数来进行这个操作。
接下来,我们开始进行预处理和数据集划分。
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
# 将数据分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
articles_df['title'], articles_df['category'], test_size=0.2, random_state=42
)
# 初始化一个TfidfVectorizer
vectorizer = TfidfVectorizer(stop_words='english', max_features=1000)
# 拟合并转换训练数据
X_train_vec = vectorizer.fit_transform(X_train)
# 转换测试数据
X_test_vec = vectorizer.transform(X_test)
X_train_vec.shape, X_test_vec.shape
((27659, 1000), (6915, 1000))
我们已经成功地将数据集分为了训练集和测试集,并对文章标题进行了预处理。我们使用了 TfidfVectorizer 来将文章标题转换为数值向量。这个转换器会计算每个词的 TF-IDF 值,这是一个常用于信息检索和文本挖掘的权重,用于反映一个词对于一个文件集或文档库中的一个文件的重要程度。
我们选择了最常出现的 1000 个词来代表每个文章标题。这样做的目的是为了减小特征的数量,使模型更易于训练,同时仍保留大部分有用的信息。我们也移除了英文停用词,这些词(如“the”、“is”、“and”等)在文本中非常常见,但通常并不携带有用的信息。
训练集包含 27,659 篇文章,测试集包含 6,915 篇文章。每篇文章都被表示为一个长度为 1000 的向量。
接下来我们将使用逻辑回归模型、支持向量机模型以及随机森林模型对文章标题进行文本分类预测。
2、逻辑回归模型
逻辑回归是一个简单但有效的分类模型,适合用于处理二分类或多分类问题。我们将使用 sklearn 的 LogisticRegression 类来创建这个模型。
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
from sklearn.metrics import accuracy_score
# 初始化 LogisticRegression 模型
model = LogisticRegression(solver='liblinear', random_state=42)
# 训练模型
model.fit(X_train_vec, y_train)
# 预测测试数据的类别
y_pred = model.predict(X_test_vec)
lr_accuracy = accuracy_score(y_test, y_pred)
print(classification_report(y_test, y_pred))
print('Accuracy: ', lr_accuracy)
precision recall f1-score support
basic 0.41 0.42 0.42 1607
easy 0.34 0.36 0.35 1950
expert 0.40 0.04 0.08 408
hard 0.37 0.05 0.08 876
medium 0.38 0.57 0.45 2074
accuracy 0.37 6915
macro avg 0.38 0.28 0.27 6915
weighted avg 0.38 0.37 0.34 6915
Accuracy: 0.3745480838756327
这是我们的逻辑回归模型在测试集上的性能报告。性能报告包括了每个类别的精度(precision)、召回率(recall)和 F1 分数,以及整体的准确度(accuracy)。
- 精度是预测为正例的样本中真正为正例的比例,用于衡量预测结果的可靠程度。
- 召回率是真正为正例的样本中被预测为正例的比例,用于衡量正例的识别能力。
- F1 分数是精度和召回率的调和平均值,用于综合衡量模型的性能。
从报告中,我们可以看到:
medium
类别的 F1 分数最高,达到了 0.45,说明模型在预测这个类别的文章时表现最好。expert
和hard
类别的 F1 分数较低,分别为 0.08 和 0.08,说明模型在预测这两个类别的文章时表现较差。- 整体的准确度为 0.37,说明模型正确预测文章类别的概率为 37%。
这些结果可能受到数据不均衡的影响,即 medium
类别的文章数量远多于其他类别。为了改进模型的性能,我们可以尝试其他的处理不均衡数据的方法,例如过采样少数类、欠采样多数类或使用合成样本等。我们也可以尝试其他的模型和参数,或进一步优化特征提取的过程。
我们将使用混淆矩阵来可视化模型的预测结果。混淆矩阵是一种常用的展示模型性能的方法,可以直观地显示模型在每个类别上的预测情况。
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
# 计算混淆矩阵
cm = confusion_matrix(y_test, y_pred)
# Plot the confusion matrix
plt.figure(figsize=(10, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=model.classes_, yticklabels=model.classes_)
plt.title('混淆矩阵')
plt.xlabel('预测类别')
plt.ylabel('真实类别')
plt.show()
混淆矩阵的每一行代表了真实类别,每一列代表了预测类别。对角线上的数字表示模型正确预测的样本数量,非对角线上的数字表示模型预测错误的样本数量。
从混淆矩阵中,我们可以看到:
-
模型在预测
medium
类别的文章时表现最好,大部分medium
类别的文章都被正确预测。 -
在预测
expert
和hard
类别的文章时,模型的表现较差,大部分expert
和hard
类别的文章被错误地预测为了其他类别。
3、支持向量机模型
支持向量机是一种常用的分类模型,它的目标是找到一个超平面来最大化两个类别的间隔。
from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score
# 初始化 LinearSVC 模型
svm_model = LinearSVC(random_state=42)
# 训练模型
svm_model.fit(X_train_vec, y_train)
# 预测测试数据的类别
svm_pred = svm_model.predict(X_test_vec)
# 计算准确率
svm_accuracy = accuracy_score(y_test, svm_pred)
print('Accuracy:', svm_accuracy)
Accuracy: 0.3683297180043384
4、随机森林模型
随机森林是一种集成模型,它通过构造多个决策树并取它们的平均结果来进行预测。
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
# 初始化随机森林模型
rf_model = RandomForestClassifier(random_state=42)
# 训练模型
rf_model.fit(X_train_vec, y_train)
# 预测测试数据的类别
rf_pred = rf_model.predict(X_test_vec)
# 计算准确率
rf_accuracy = accuracy_score(y_test, rf_pred)
print('Accuracy:', rf_accuracy)
Accuracy: 0.3515545914678236
5、模型对比
这是我们三个模型在测试集上的准确度:
-
逻辑回归模型的准确度为 37.45%
-
支持向量机模型的准确度为 36.83%
-
随机森林模型的准确度为 35.16%
这些结果表明,对于这个任务,逻辑回归模型的性能最好,随机森林模型的性能最差。
接下来,我们将使用图形来可视化这些准确度,以便更直观地比较模型的性能。
models = ['逻辑回归模型', '支持向量机模型', '随机森林模型']
accuracies = [lr_accuracy, svm_accuracy, rf_accuracy]
# 创建柱状图对象
bar_chart = Bar()
bar_chart.set_global_opts(
title_opts=opts.TitleOpts(title="模型比较"),
xaxis_opts=opts.AxisOpts(name="模型"),
yaxis_opts=opts.AxisOpts(name="准确度", min_=0.3, max_=0.4),
)
# 添加数据
bar_chart.add_xaxis(models)
bar_chart.add_yaxis("", accuracies)
# 展示图表
bar_chart.render_notebook()
这是我们三个模型在测试集上的准确度的比较图,从图中我们可以看到:
- 逻辑回归模型的准确度最高,达到了37.45%。
- 支持向量机模型的准确度次之,为36.83%。
- 随机森林模型的准确度最低,为35.16%。
接下来,我们来简要讨论一下这三个模型的优缺点:
- 逻辑回归模型:
- 优点:模型简单,训练和预测速度快,容易解释,不容易过拟合。
- 缺点:假设数据服从伯努利分布,对于不满足这个假设的数据,预测性能可能不好。
- 支持向量机模型:
- 优点:可以解决高维问题,即大型特征空间;能够处理非线性特征的相互作用;无需依赖整个数据。
- 缺点:当观测样本很多时,效率并不是很高;对非线性问题没有通用解决方案,有时候很难找到一个合适的核函数。
- 随机森林模型:
- 优点:既能处理二分类问题,也能处理多分类问题;可以处理高维度(特征多)的数据,并且不需要做特征选择;训练完后,能给出哪些特征重要。
- 缺点:在某些噪音较大的分类或回归问题上,会过拟合;对于有不同取值的属性的数据,取值划分较多的属性会对随机森林产生更大的影响,所以随机森林在这种数据上产出的属性权值是不可信的。