ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ML-For-Beginners 实战:用 NLTK 与 pandas 对 51.5 万条欧洲酒店评论做数据清洗与 VADER 情感分析

ML-For-Beginners 实战:用 NLTK 与 pandas 对 51.5 万条欧洲酒店评论做数据清洗与 VADER 情感分析 ML-For-Beginners 实战用 NLTK 与 pandas 对 51.5 万条欧洲酒店评论做数据清洗与 VADER 情感分析【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners本指南是微软开源课程 ML-For-Beginners 中「6-NLP」模块第 5 课的完整实战讲解。它承接上一课对 515,738 条欧洲酒店评论的探索性数据分析EDA讲解如何用 pandas 过滤无效列、重算可信统计量、把混乱的Tags文本列转化为可建模的特征再用 NLTK 的 VADER 模型对正负评论做情感打分最终产出可直接用于下游聚类与推荐任务的Hotel_Reviews_NLP.csv。读完本文你将掌握一条完整可复制的「原始文本 → 结构化特征 情感分数」NLP 数据流水线并能把它迁移到任何新的评论类数据集上。背景为什么要做第二轮清洗上一课4-Hotel-Reviews-1已经发现这份来自 Booking.com 的公开数据集515K Hotel Reviews Data in Europe共 515,738 行、17 列、覆盖 6 座城市的 1493 家酒店存在若干问题部分列装的是无法独立核验的信息例如Average_Score的官方口径是基于最近一年最新评论计算的平均分与用Reviewer_Score自行重算的平均分存在差异以Britannia International Hotel Canary Wharf为例官方 7.1 vs 重算 6.8Total_Number_of_Reviews与数据集中实际出现的评论数对不上同一酒店 9086 vs 4789Tags列是存储在文本字段里的伪列表顺序与子项个数都不固定。因此本课的第一阶段目标非常明确把不可信或无用列删掉用自己算出来的可信数值替换官方数值为后续情感分析铺平道路。第一阶段用 pandas 完成列级过滤与数值重算1. 初列处理删除坐标、压缩地址首先删除没有建模价值的经纬度列lat、lng然后把冗长的Hotel_Address压缩为城市, 国家格式。整个数据集只有 6 个城市/国家组合所以用一个简单的逐行映射函数即可def replace_address(row): if Netherlands in row[Hotel_Address]: return Amsterdam, Netherlands elif Barcelona in row[Hotel_Address]: return Barcelona, Spain elif United Kingdom in row[Hotel_Address]: return London, United Kingdom elif Milan in row[Hotel_Address]: return Milan, Italy elif France in row[Hotel_Address]: return Paris, France elif Vienna in row[Hotel_Address]: return Vienna, Austria # Replace all the addresses with a shortened, more useful form df[Hotel_Address] df.apply(replace_address, axis 1) # The sum of the value_counts() should add up to the total number of reviews print(df[Hotel_Address].value_counts())在官方过滤 Notebook 中replace_address还带有一个else: return row.Hotel_Address兜底分支保证未知地址不被意外置空。压缩后即可在国家层面做聚合查询display(df.groupby(Hotel_Address).agg({Hotel_Name: nunique}))Hotel_AddressHotel_NameAmsterdam, Netherlands105Barcelona, Spain211London, United Kingdom400Milan, Italy162Paris, France458Vienna, Austria1582. 酒店元评论列删除不可信列、重算可信指标Additional_Number_of_Scoring表示只给了分数、没有写文字评论的评分与数据集内的评论无关直接删除Total_Number_of_Reviews与Average_Score则用groupby在数据集内自行重算——这正是上一课 EDA 得出的结论官方值不可信时就用自己算的值。# Drop Additional_Number_of_Scoring df.drop([Additional_Number_of_Scoring], axis 1, inplaceTrue) # Replace Total_Number_of_Reviews and Average_Score with our own calculated values df.Total_Number_of_Reviews df.groupby(Hotel_Name).transform(count) df.Average_Score round(df.groupby(Hotel_Name).Reviewer_Score.transform(mean), 1)注意这里的实现细节transform(count)会为每个酒店返回其评论行数并广播回每一行保证该列在每个酒店的每条评论上一致Average_Score则是按酒店对Reviewer_Score取均值并四舍五入到 1 位小数与上一课验证过的Calc_Average_Score口径完全一致。3. 评论列只保留得分与正负评论文本Review_Total_Negative_Word_Counts、Review_Total_Positive_Word_Counts、Review_Date、days_since_review对情感分析没有直接价值删除保留Reviewer_Score2.5–10 的数值分、Negative_Review、Positive_Review无文字时分别为No Negative/No Positive。Tags暂时保留下一节还要处理。4. 评论者列删除评论文本无关信息Total_Number_of_Reviews_Reviewer_Has_Given无法关联到具体评论者数据中没有唯一评论者 ID对推荐模型帮助有限删除Reviewer_Nationality保留作为后续可能的人口统计维度。第二阶段把混乱的 Tags 文本转化为有用特征为什么 Tags 列非用 NLP 不可Tags列本质上是存成字符串的列表例如[ Business trip , Solo traveler , Single Room , Stayed 5 nights , Submitted from a mobile device ]。问题在于515,000 行数据、1427 家酒店每家酒店的 Tag 选项都不尽相同顺序和个数也参差不齐人肉识别几乎不可能。这正是 NLP 的用武之地——扫描文本、统计最常见短语。直接用多词短语频率统计算法处理 6,762,646 个单词会非常耗时但探索性数据分析可以大幅缩减工作量先抽样看几个 Tag就能判断真正关心的其实只有出行类型和客群类型两类短语。据此确定取舍原则出行类型如商务/休闲→ 保留客群类型如情侣、独自旅行→ 保留房型Double Room 等→ 无关删除提交设备移动设备等→ 无关删除入住夜数Stayed N nights→ 大概率无关删除。结论保留 2 类 Tag其余全部剔除。清洗 Tags 的快速路径计数之前必须先把方括号和引号清理掉。pandas 提供了最快的方式# Remove opening and closing brackets df.Tags df.Tags.str.strip([]) # remove all quotes too df.Tags df.Tags.str.replace( , , ,, regex False)处理后的每个 Tag 变成类似Business trip, Solo traveler, Single Room, Stayed 5 nights, Submitted from a mobile device的纯逗号分隔文本。regex False是关键参数——它强制按字面量替换而不是正则表达式在 50 万行规模下显著更快。用列序即顺序的技巧统计短语频率由于每条评论的 Tag 个数不同有的 5 个、有的 3 个、有的 6 个且顺序不一致直接value_counts()会得到不准确的计数。巧妙的解法是既然 Tag 是多词短语且用逗号分隔就按逗号切分后把每个位置的 Tag 放进对应的临时列再把 6 个临时列合并成一列做value_counts()。结果显示共有2428 个唯一 Tag部分高频样本如下TagCountLeisure trip417778Submitted from a mobile device307640Couple252294Stayed 1 night193645Stayed 2 nights133937Solo traveler108545Stayed 3 nights95821Business trip82939Group65392Family with young children61015Stayed 4 nights47817Double Room35207Standard Double Room32248Superior Double Room31393Family with older children26349Deluxe Double Room24823Double or Twin Room22393Stayed 5 nights20845Standard Double or Twin Room17483Classic Double Room16989Superior Double or Twin Room135702 rooms12393像Submitted from a mobile device这类高频但无用的 Tag因为value_counts()本身足够快可以选择留着但忽略。剔除入住夜数与房型 Tag删除只是不再把它们纳入计数与保留范围并非从数据集中物理移除。入住夜数类 Tag 的频次分布为Stayed 1 night193645→ Stayed 9 nights1293呈明显长尾房型类Double Room 35207、Standard Double Room 32248、Superior Double Room 31393、Deluxe Double Room 24823、Double or Twin Room 22393、Standard Double or Twin Room 17483、Classic Double Room 16989、Superior Double or Twin Room 13570含义相近、对推荐无区分度。剔除后剩下的有用 Tag是TagCountLeisure trip417778Couple252294Solo traveler108545Business trip82939Group (combined with Travellers with friends)67535Family with young children61015Family with older children26349With a pet1405Travellers with friends与Group语义高度重叠合理做法是把两者合并计数。识别正确 Tag 的完整代码见 Tags 识别 Notebook。生成独热式 Tag 特征列最后为每个有用 Tag 新建一列若某行评论的Tags包含该短语则置 1否则置 0。最终聚合结果就是有多少评论者选择该酒店是为了商务/休闲/带宠物这正是酒店推荐机器人需要的信号# Process the Tags into new columns # The file Hotel_Reviews_Tags.py, identifies the most important tags # Leisure trip, Couple, Solo traveler, Business trip, Group combined with Travelers with friends, # Family with young children, Family with older children, With a pet df[Leisure_trip] df.Tags.apply(lambda tag: 1 if Leisure trip in tag else 0) df[Couple] df.Tags.apply(lambda tag: 1 if Couple in tag else 0) df[Solo_traveler] df.Tags.apply(lambda tag: 1 if Solo traveler in tag else 0) df[Business_trip] df.Tags.apply(lambda tag: 1 if Business trip in tag else 0) df[Group] df.Tags.apply(lambda tag: 1 if Group in tag or Travelers with friends in tag else 0) df[Family_with_young_children] df.Tags.apply(lambda tag: 1 if Family with young children in tag else 0) df[Family_with_older_children] df.Tags.apply(lambda tag: 1 if Family with older children in tag else 0) df[With_a_pet] df.Tags.apply(lambda tag: 1 if With a pet in tag else 0)保存过滤结果删除最后一批不再需要的列并将当前 DataFrame 另存为新文件df.drop([Review_Total_Negative_Word_Counts, Review_Total_Positive_Word_Counts, days_since_review, Total_Number_of_Reviews_Reviewer_Has_Given], axis 1, inplaceTrue) # Saving new data file with calculated columns print(Saving results to Hotel_Reviews_Filtered.csv) df.to_csv(r../data/Hotel_Reviews_Filtered.csv, index False)从过滤 Notebook 的运行输出可以看到整轮过滤在参考机器上耗时约 23.74 秒产出Hotel_Reviews_Filtered.csv。第三阶段用 NLTK 对评论做 VADER 情感分析加载过滤后的数据而非原始数据情感分析阶段必须加载上一阶段保存的过滤数据集而不是原始Hotel_Reviews.csvimport time import pandas as pd import nltk as nltk from nltk.corpus import stopwords from nltk.sentiment.vader import SentimentIntensityAnalyzer nltk.download(vader_lexicon) # Load the filtered hotel reviews from CSV df pd.read_csv(../../data/Hotel_Reviews_Filtered.csv) # You code will be added here # Finally remember to save the hotel reviews with new NLP data added print(Saving results to Hotel_Reviews_NLP.csv) df.to_csv(r../data/Hotel_Reviews_NLP.csv, index False)为什么先移除停用词性能权衡在 515,000 行上直接对正负评论两列跑情感分析在参考测试机上需要12–14 分钟取决于所选情感库。停用词the、and、a等不携带情感的高频英文词不改变句子情感却显著拖慢分析因此先移除是划算的。实测效果最长的一条负面评论从 395 词降到 195 词而停用词移除本身在 2 列 × 515,000 行上仅耗时约 3.3 秒参考机器相对情感分析的总时长几乎可以忽略。实现上采用先构建set缓存再逐词过滤的推荐做法——把stopwords.words(english)转成集合后成员判断是 O(1) 的比反复扫描列表快得多from nltk.corpus import stopwords # Load the hotel reviews from CSV df pd.read_csv(../../data/Hotel_Reviews_Filtered.csv) # Remove stop words - can be slow for a lot of text! start time.time() cache set(stopwords.words(english)) def remove_stopwords(review): text .join([word for word in review.split() if word not in cache]) return text # Remove the stop words from both columns df.Negative_Review df.Negative_Review.apply(remove_stopwords) df.Positive_Review df.Positive_Review.apply(remove_stopwords)在情感分析 Notebook 的实际输出中这一步耗时约 5.77 秒。VADER 情感打分与哨兵值处理NLTK 提供多种情感分析器本课选用VADERValence Aware Dictionary and sEntiment Reasoner。它来自论文Hutto, C.J. Gilbert, E.E. (2014). VADER: A Parsimonious Rule-based Model for Sentiment Analysis of Social Media Text. Eighth International Conference on Weblogs and Social Media (ICWSM-14). Ann Arbor, MI是一种轻量、基于词典与规则的模型特别适合社交媒体与短文本。关键细节是处理No Negative/No Positive哨兵值当评论列没有文字时直接返回 0而不是交给 VADER 解析from nltk.sentiment.vader import SentimentIntensityAnalyzer # Create the vader sentiment analyser (there are others in NLTK you can try too) vader_sentiment SentimentIntensityAnalyzer() # There are 3 possibilities of input for a review: # It could be No Negative, in which case, return 0 # It could be No Positive, in which case, return 0 # It could be a review, in which case calculate the sentiment def calc_sentiment(review): if review No Negative or review No Positive: return 0 return vader_sentiment.polarity_scores(review)[compound]polarity_scores()返回包含neg、neu、pos、compound四个分量的字典其中compound是归一化到 [-1, 1] 的综合情感分接近 1 为极度正面接近 -1 为极度负面。对每一行应用打分并计时# Add a negative sentiment and positive sentiment column print(Calculating sentiment columns for both positive and negative reviews) start time.time() df[Negative_Sentiment] df.Negative_Review.apply(calc_sentiment) df[Positive_Sentiment] df.Positive_Review.apply(calc_sentiment) end time.time() print(Calculating sentiment took str(round(end - start, 2)) seconds)参考机器上约需 120 秒Notebook 中记录为 201.07 秒依硬件而异。验证情感与评分是否吻合可按情感分排序打印抽查df df.sort_values(by[Negative_Sentiment], ascendingTrue) print(df[[Negative_Review, Negative_Sentiment]]) df df.sort_values(by[Positive_Sentiment], ascendingTrue) print(df[[Positive_Review, Positive_Sentiment]])Notebook 输出显示负面情感最低约 -0.99如 So bad experience memories I hotel...最高甚至出现 0.99 的异常值正面评论列同样会出现 -0.98 的低分。这印证了文档中的重要提醒情感分析会犯错且往往可解释——例如 Of course I LOVED sleeping in a room with no heating 这类反讽文本VADER 会误判为正情感。因此后续必须把情感分与Reviewer_Score交叉比对而非盲信。列重排与最终保存保存前对列做一次重排纯装饰性方便人眼浏览然后落盘# Reorder the columns (This is cosmetic, but to make it easier to explore the data later) df df.reindex([Hotel_Name, Hotel_Address, Total_Number_of_Reviews, Average_Score, Reviewer_Score, Negative_Sentiment, Positive_Sentiment, Reviewer_Nationality, Leisure_trip, Couple, Solo_traveler, Business_trip, Group, Family_with_young_children, Family_with_older_children, With_a_pet, Negative_Review, Positive_Review], axis1) print(Saving results to Hotel_Reviews_NLP.csv) df.to_csv(r../data/Hotel_Reviews_NLP.csv, index False)完整流水线回顾与数据流向整条流水线共 4 步前序依赖关系清晰原始Hotel_Reviews.csv由上一课的探索 Notebook 完成 EDA由过滤 Notebook 过滤得到Hotel_Reviews_Filtered.csv列清洗 Tag 特征 重算统计量由情感分析 Notebook 处理得到Hotel_Reviews_NLP.csv停用词移除 VADER 情感列用Hotel_Reviews_NLP.csv完成课程挑战对情感做聚类分析。原文使用的数据集需从 Kaggle 下载约 230 MB 解压后放到 6-NLP/data 目录本仓库的 data 目录说明 同样提示将酒店评论数据下载至该目录。运行环境要求 Python 3、pandas 与本地安装的 NLTK。课程挑战与延伸完成情感列之后课程的 Challenge 建议把本课程学到的聚类策略应用到新数据集上围绕情感分数发现模式例如哪些国家的评论者情感均值更高、哪类客群对特定酒店群的情感更正面。配套作业则要求读者换一个全新数据集用 NLTK 为文本分配情感并新建 Notebook 记录数据处理过程与发现——评分标准强调 Notebook 的完整性与单元注释质量。结语从一份列很多、但可信度存疑的原始数据集出发本文演示了完整的三步方法论先探索、再过滤、后计算。你删除了无法核验的官方指标并用自己的计算替代把混乱的Tags文本转化成了 8 个可建模的独热特征移除了拖慢分析的停用词最终用 VADER 为每条评论补充了正负两个情感维度。这套pandas 清洗 NLTK 情感分析的组合拳可以直接复用到任何评论类、反馈类文本数据集上是构建推荐系统与情感监控管道的基础能力。【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表