用Python解密霍格沃茨的情感密码:哈利波特系列文本挖掘之旅
目录
- 1. 当魔法遇到代码:Python揭秘哈利波特的情感宇宙
- 2. 魔法世界的基石:数据准备与预处理 (Python版)
- 3. 情感的度量衡:认识我们的情感词典 (Python版)
- 4. 初探情感全貌:整体情感分布 (Python版)
- 5. 情感的过山车:系列书籍情感走势分析 (Python版)
- 5.1 使用
bing
词典 (Python版)- 5.2 综合
AFINN
,bing
,nrc
词典比较 (Python版)- 6. 那些“搞事”的词:核心情感贡献词分析 (Python版)
- 7. 微观情感世界:《魔法石》章节情感深度游 (Python版)
- 8. 实验结论与思考:数据背后的故事
哈利波特,这个陪伴了无数人童年和青春的名字,不仅仅是一个关于魔法、友谊和勇气的故事,更是一个情感充沛、跌宕起伏的世界。当我们挥舞魔杖,念出咒语时,是否想过这些文字背后隐藏着怎样的情感密码?作为一名AI爱好者和数据巫师,我决定用Python这根“数字魔杖”,对哈利波特系列小说进行一次彻底的文本挖掘和情感分析,让我们一起看看数据能揭示出J.K.罗琳笔下这个魔法世界哪些不为人知的情感秘密。
这不仅仅是一次技术实践,更是一次情怀之旅。准备好了吗?让我们骑上扫帚,进入Python的魔法课堂!
1. 当魔法遇到代码:Python揭秘哈利波特的情感宇宙
在数据科学的武器库中,Python因其庞大的生态系统、易用性和强大的库支持,在文本挖掘领域大放异彩。本次冒险,我们将主要依赖以下几个核心Python库:
pandas
: 数据处理和分析的基石,提供高效的DataFrame结构。nltk
(Natural Language Toolkit): 强大的自然语言处理库,用于文本分词、停用词去除等。matplotlib
和seaborn
: 流行的可视化库,用于绘制各种图表。- (可选)
textblob
: 一个简化文本处理的库,内置简单的情感分析功能。 - 情感词典文件:我们将需要手动加载或从网络获取情感词典(如AFINN, Bing, NRC)。
我们的目标是:提取哈利波特系列书籍中的文本,利用情感词典分析情感倾向,并可视化这些情感在故事中的分布和变化。这就像用“显形咒”让隐藏的情感脉络清晰可见。
首先,确保你已安装必要的库:
pip install pandas nltk matplotlib seaborn requests
你可能还需要下载NLTK的一些资源(如分词器punkt
和停用词表stopwords
):
import nltk
try:nltk.data.find('tokenizers/punkt')
except nltk.downloader.DownloadError:nltk.download('punkt')
try:nltk.data.find('corpora/stopwords')
except nltk.downloader.DownloadError:nltk.download('stopwords')
2. 魔法世界的基石:数据准备与预处理 (Python版)
冒险的第一步,是收集和整理我们的“魔法原材料”——书籍文本。Python中我们通常需要自行准备文本数据。
假设你已经有了7本书的文本,并且每本书的内容被组织成一个章节列表。
import pandas as pd
from nltk.tokenize import word_tokenize
import re # 用于简单的文本清理# 书籍标题,按出版顺序
titles = ["Philosopher's Stone", "Chamber of Secrets", "Prisoner of Azkaban","Goblet of Fire", "Order of the Phoenix", "Half-Blood Prince","Deathly Hallows"]# --- 数据加载模拟 ---
# 在真实场景中,你需要从文件加载这些文本
# 这里我们用非常简短的占位符文本来模拟
# 每一本书是一个章节列表,每个章节是一个字符串
example_texts = {"Philosopher's Stone": ["Mr. and Mrs. Dursley, of number four, Privet Drive, were proud to say that they were perfectly normal, thank you very much. They were the last people you'd expect to be involved in anything strange or mysterious, because they just didn't hold with such nonsense.","Harry had a thin face, knobbly knees, black hair, and bright green eyes. He wore round glasses held together with a lot of Scotch tape because of all the times Dudley had punched him on the nose. The only thing Harry liked about his own appearance was a very thin scar on his forehead that was shaped like a bolt of lightning."],"Chamber of Secrets": ["Not for the first time, an argument had broken out over breakfast at number four, Privet Drive.","Harry was used to spiders, because the cupboard under the stairs was full of them, and that was where he slept."],# ... 为简洁起见,省略其他书籍的模拟文本# 你需要为所有7本书提供真实的章节文本
}
# 为了让示例能运行,我们为其他书也创建简短的占位符
for title in titles:if title not in example_texts:example_texts[title] = [f"This is chapter 1 of {title}.", f"This is chapter 2 of {title}. Danger and magic await!"]# books_data 将是一个列表,每个元素是一个包含章节文本的列表
books_data = [example_texts.get(title, []) for title in titles]
# --- 数据加载模拟结束 ---all_words_list = [] # 初始化一个空列表用于存储所有词的数据# 循环处理每本书
for i, book_title in enumerate(titles):book_chapters = books_data[i] # 获取当前书的章节列表for chap_num, chapter_text in enumerate(book_chapters):# 1. 分词 (Tokenization)# 我们转换为小写,并只保留字母组成的词words = word_tokenize(chapter_text.lower())cleaned_words = [word for word in words if word.isalpha()] # 只保留字母词# 2. 为每个词创建记录for word in cleaned_words:all_words_list.append({'book': book_title,'chapter': chap_num + 1, # 章节号从1开始'word': word})# 将列表转换为Pandas DataFrame
series_df = pd.DataFrame(all_words_list)# 将书名转换为分类类型,并按原始顺序排序,便于后续绘图
# Pandas的Categorical类型类似于R的factor
series_df['book'] = pd.Categorical(series_df['book'], categories=titles, ordered=True)# 查看处理后的数据结构
print(series_df.head())
print(f"\nTotal words processed: {len(series_df)}")
代码解读:
titles
: 定义书名列表。example_texts
和books_data
: 这是数据加载的关键部分。这里我们使用了一个字典example_texts
来模拟章节化的文本数据。在实际应用中,你需要替换这部分代码,使其能从你的文本文件(如.txt)中读取内容,并将每本书的文本分割成章节(如果原始数据是这样组织的)。books_data
随后被构建成一个列表的列表,外层列表代表书籍,内层列表代表该书的章节文本。all_words_list
: 初始化一个空列表,用于收集所有词汇及其元数据(所属书籍、章节)。for
循环遍历书籍和章节:word_tokenize(chapter_text.lower())
: 使用nltk
的word_tokenize
函数将章节文本分割成词汇,并转换为小写。cleaned_words = [word for word in words if word.isalpha()]
: 进行简单的清洗,只保留由字母组成的词汇,移除非字母标记(如标点符号)。更复杂的预处理可能包括去除停用词(stopwords)、词形还原(lemmatization)等。all_words_list.append(...)
: 为每个清洗后的词创建一个字典,包含书名、章节号和词本身,然后添加到列表中。
series_df = pd.DataFrame(all_words_list)
: 将包含所有词汇信息的列表转换为Pandas DataFrame。这个DataFrame的结构类似于R代码中的series
tibble,每一行代表一个词。series_df['book'] = pd.Categorical(...)
: 将book
列转换为Pandas的Categorical
类型,并指定了类别的顺序(categories=titles
)。这确保了在后续绘图时,书籍能按照出版顺序排列。
现在,series_df
DataFrame包含了哈利波特全系列(模拟数据)每一本书、每一章的每一个词。这为我们后续的情感分析打下了坚实的基础。
图1: Python数据准备流程图
3. 情感的度量衡:认识我们的情感词典 (Python版)
要进行情感分析,我们需要情感词典。在Python中,我们可以加载常见的词典文件,或者使用像VADER
这样内置词典的库。这里我们将演示如何加载AFINN, Bing和NRC词典。你需要从网络上下载这些词典文件,或者使用下面提供的URL。
import pandas as pd
import requests # 用于从URL下载文件
from io import StringIO # 用于读取文本内容# --- AFINN 词典 ---
# AFINN为词汇分配一个从-5(极消极)到+5(极积极)的整数分数。
afinn_url = "https://raw.githubusercontent.com/fnielsen/afinn/master/afinn/data/AFINN-111.txt"
try:response = requests.get(afinn_url)response.raise_for_status() # 检查请求是否成功afinn_lexicon = pd.read_csv(StringIO(response.text), sep='\t', header=None, names=['word', 'value'])print("AFINN Lexicon loaded successfully:")print(afinn_lexicon.head())
except requests.exceptions.RequestException as e:print(f"Error loading AFINN lexicon: {e}")afinn_lexicon = pd.DataFrame(columns=['word', 'value']) # 创建空DataFrame以防出错# --- Bing Liu 词典 ---
# Bing将词汇分为“positive”和“negative”两类。
bing_positive_url = "https://ptrckprry.com/course/ssd/data/positive-words.txt"
bing_negative_url = "https://ptrckprry.com/course/ssd/data/negative-words.txt"
bing_lexicon_list = []
try:# Positive wordsresponse_pos = requests.get(bing_positive_url)response_pos.raise_for_status()# Skip comment lines starting with ;pos_words = [word for word in response_pos.text.splitlines() if not word.startswith(';')]for word in pos_words:if word.strip(): # Ensure word is not emptybing_lexicon_list.append({'word': word.strip(), 'sentiment': 'positive'})# Negative wordsresponse_neg = requests.get(bing_negative_url)response_neg.raise_for_status()neg_words = [word for word in response_neg.text.splitlines() if not word.startswith(';')]for word in neg_words:if word.strip():bing_lexicon_list.append({'word': word.strip(), 'sentiment': 'negative'})bing_lexicon = pd.DataFrame(bing_lexicon_list)print("\nBing Lexicon loaded successfully:")print(bing_lexicon.head())print(bing_lexicon.tail())
except requests.exceptions.RequestException as e:print(f"Error loading Bing lexicon: {e}")bing_lexicon = pd.DataFrame(columns=['word', 'sentiment'])# --- NRC 词典 ---
# NRC将词汇分为多种情感类别以及positive/negative。
# NRC Emotion Lexicon: NRC-Emotion-Lexicon-Wordlevel-v0.92.txt
# Format: word<TAB>emotion<TAB>association (1 if associated, 0 if not)
nrc_url = "https://raw.githubusercontent.com/jeffreybreen/twitter-sentiment-analysis-tutorial-201107/master/data/emotions-lexicon/NRC-Emotion-Lexicon-Wordlevel-v0.92.txt"
nrc_lexicon_list = []
try:response = requests.get(nrc_url)response.raise_for_status()lines = response.text.splitlines()for line in lines:parts = line.split('\t')if len(parts) == 3:word, emotion, association = parts[0], parts[1], int(parts[2])if association == 1: # Only include words associated with the emotionnrc_lexicon_list.append({'word': word, 'sentiment': emotion})nrc_lexicon = pd.DataFrame(nrc_lexicon_list)print("\nNRC Lexicon loaded successfully:")print(nrc_lexicon.head())
except requests.exceptions.RequestException as e:print(f"Error loading NRC lexicon: {e}")nrc_lexicon = pd.DataFrame(columns=['word', 'sentiment'])
代码解读:
- 我们使用
requests
库从URL获取词典的文本内容,并用StringIO
将其包装成文件类对象,以便pandas.read_csv
(AFINN) 或手动解析。 - AFINN: 直接是一个制表符分隔的文件,包含词和对应的情感分数。
- Bing: 分为积极词和消极词两个文