pip install mlverse-mall
pip install chatlasmall包- AI处理数据表
AI
使用大型语言模型 (LLM) 对您的数据运行自然语言处理 (NLP) 操作。它利用 LLM 的通用语言训练来获取预测,从而消除了训练新 NLP 模型的需要。mall 可用于 R 和 Python。
安装包 (install package)
install.packages("mall")
install.packages("ellmer")加载包 (load package)
保存 Openrouter API OPENROUTER_API_KEY 到 .env 文件
import os
import mall
from chatlas import ChatOpenRouter
from dotenv import load_dotenv
import polars as pl
load_dotenv()True
library(mall)
library(ellmer)
library(dotenv)
library(tidyverse)
# This looks for a .env file in the current working directory
load_dot_env()下载数据 (download data)
数据来源: 豆瓣电影 - 飞驰人生3
import requests
from bs4 import BeautifulSoup
import time
import random
file_path = "douban_comments.csv"
if not os.path.exists(file_path):
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Referer": "https://movie.douban.com/subject/37311135/",
}
all_comments = []
# 抓取前3页,共60条评论
for page in range(3):
url = f"https://movie.douban.com/subject/37311135/comments?start={page * 20}&limit=20&status=P&sort=new_score"
resp = requests.get(url, headers=headers, timeout=10)
soup = BeautifulSoup(resp.text, "html.parser")
for item in soup.select("div.comment-item"):
comment_text = item.select_one("span.short")
rating_span = item.select_one("span.comment-info span.rating")
user_span = item.select_one("span.comment-info a")
if comment_text:
# 从 class 如 'allstar50' 提取评分
rating = ""
if rating_span:
cls = [c for c in rating_span.get("class", []) if c.startswith("allstar")]
if cls:
rating = str(int(cls[0].replace("allstar", "")) // 10)
all_comments.append({
"user": user_span.text.strip() if user_span else "",
"rating": rating,
"comment": comment_text.text.strip()
})
time.sleep(random.uniform(2, 4)) # 礼貌延迟
pl.DataFrame(all_comments).write_csv(file_path)
print(f"已下载 {len(all_comments)} 条评论")
else:
print(f"文件已存在: {file_path}")文件已存在: douban_comments.csv
reviews = pl.read_csv(file_path).head(3)
reviews
shape: (3, 3)
| user | rating | comment |
|---|---|---|
| str | i64 | str |
| "Corleone" | 4 | "三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。" |
| "壹安²" | 4 | "韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。" |
| "阿暖" | 3 | "韩寒你也看过F1" |
library(rvest)
file_path <- "douban_comments.csv"
if (!file.exists(file_path)) {
all_comments <- data.frame(user = character(), rating = character(), comment = character())
for (page in 0:2) {
url <- paste0("https://movie.douban.com/subject/37311135/comments?start=", page * 20, "&limit=20&status=P&sort=new_score")
page_html <- read_html(url,
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
)
items <- page_html %>% html_elements("div.comment-item")
for (item in items) {
comment_text <- item %>% html_element("span.short") %>% html_text2()
user_name <- item %>% html_element("span.comment-info a") %>% html_text2()
rating_el <- item %>% html_element("span.comment-info span.rating")
rating <- ""
if (!is.na(rating_el)) {
cls <- rating_el %>% html_attr("class")
rating_num <- gsub(".*allstar(\\d+).*", "\\1", cls)
rating <- as.character(as.integer(rating_num) %/% 10)
}
if (!is.na(comment_text)) {
all_comments <- rbind(all_comments, data.frame(
user = ifelse(is.na(user_name), "", user_name),
rating = rating,
comment = comment_text
))
}
}
Sys.sleep(runif(1, 2, 4))
}
write.csv(all_comments, file_path, row.names = FALSE)
cat("已下载", nrow(all_comments), "条评论\n")
} else {
cat("文件已存在:", file_path, "\n")
}文件已存在: douban_comments.csv
reviews <- head(read.csv(file_path), 3)
reviews user rating comment
1 Corleone 4 三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。
2 壹安² 4 韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。
3 阿暖 3 韩寒你也看过F1
设置模型 (set up model)
chat = ChatOpenRouter(base_url='https://openrouter.ai/api/v1',
api_key=os.getenv("OPENROUTER_API_KEY"),
model="minimax/minimax-m2.7"
)reviews.llm.use(chat){'backend': 'chatlas', 'chat': <Chat OpenRouter/minimax/minimax-m2.7 turns=0 tokens=0/0>
, '_cache': '_mall_cache'}
chat = chat_openrouter(api_key=Sys.getenv("OPENROUTER_API_KEY"),
model="minimax/minimax-m2.7"
)llm_use(chat)情感分析 (Sentiment)
根据文本自动返回“正面”(positive)、“负面”(negative) 或 “中性”(neutral)。
reviews.llm.sentiment("comment")
shape: (3, 4)
| user | rating | comment | sentiment |
|---|---|---|---|
| str | i64 | str | str |
| "Corleone" | 4 | "三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。" | "positive" |
| "壹安²" | 4 | "韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。" | "positive" |
| "阿暖" | 3 | "韩寒你也看过F1" | "neutral" |
reviews |>
llm_sentiment(comment) user rating comment
1 Corleone 4 三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。
2 壹安² 4 韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。
3 阿暖 3 韩寒你也看过F1
.sentiment
1 positive
2 positive
3 neutral
总结 (Summarize)
可能需要减少给定文本中的字数。通常是为了更容易理解其意图。该函数有一个参数来控制输出的最大字数 (max_words):
reviews.llm.summarize("comment", 10)
shape: (3, 4)
| user | rating | comment | summary |
|---|---|---|---|
| str | i64 | str | str |
| "Corleone" | 4 | "三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。" | "three parts follow low point o… |
| "壹安²" | 4 | "韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。" | "han han true chinese hero stor… |
| "阿暖" | 3 | "韩寒你也看过F1" | "yes han han is a professional … |
reviews |>
llm_summarize(comment, max_words = 10) user rating comment
1 Corleone 4 三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。
2 壹安² 4 韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。
3 阿暖 3 韩寒你也看过F1
.summary
1 lows opportunities scapegoating growth champion pattern repeats
2 韩寒 写 真正 的 中国 侠客 故事,结局 干净 利落
3 是的,我也看过f1
分类 (Classify)
使用 LLM 将文本归类到您提供的选项之一:
reviews.llm.classify("comment", ["推荐", "不推荐"])
shape: (3, 4)
| user | rating | comment | classify |
|---|---|---|---|
| str | i64 | str | str |
| "Corleone" | 4 | "三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。" | "不推荐" |
| "壹安²" | 4 | "韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。" | "推荐" |
| "阿暖" | 3 | "韩寒你也看过F1" | "不推荐" |
reviews |>
llm_classify(comment, c("推荐", "不推荐")) user rating comment
1 Corleone 4 三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。
2 壹安² 4 韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。
3 阿暖 3 韩寒你也看过F1
.classify
1 推荐
2 推荐
3 不推荐
提取 (Extract)
最有趣的用例之一,使用自然语言,我们可以告诉 LLM 返回文本的特定部分。在以下示例中,我们请求 LLM 从影评中提取”观影感受”。LLM 会理解该词的含义,并在文本中寻找与之相关的内容。
reviews.llm.extract("comment", "观影感受")
shape: (3, 4)
| user | rating | comment | extract |
|---|---|---|---|
| str | i64 | str | str |
| "Corleone" | 4 | "三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。" | "惊叹" |
| "壹安²" | 4 | "韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。" | "赞叹" |
| "阿暖" | 3 | "韩寒你也看过F1" | "好奇" |
reviews |>
llm_extract(comment, "观影感受") user rating comment
1 Corleone 4 三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。
2 壹安² 4 韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。
3 阿暖 3 韩寒你也看过F1
.extract
1 绝了
2 真正的中国侠客故事,干净利落
3 文本中未包含任何观影感受的表述。
验证 (Verify)
该函数允许您根据提供的文本检查和确认陈述是否为真。默认情况下,它将返回 1 表示“是”,0 表示“否”。这可以被自定义。
reviews.llm.verify("comment", "是否影评?")
shape: (3, 4)
| user | rating | comment | verify |
|---|---|---|---|
| str | i64 | str | i8 |
| "Corleone" | 4 | "三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。" | 1 |
| "壹安²" | 4 | "韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。" | 1 |
| "阿暖" | 3 | "韩寒你也看过F1" | 0 |
reviews |>
llm_verify(comment, "是否影评?") user rating comment
1 Corleone 4 三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。
2 壹安² 4 韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。
3 阿暖 3 韩寒你也看过F1
.verify
1 1
2 1
3 0
翻译 (Translate)
顾名思义,此函数会将文本翻译成指定的语言。非常棒的一点是,您无需指定源文本的语言。只需定义目标语言即可。翻译的准确性将取决于 LLM。
reviews.llm.translate("comment", "english")
shape: (3, 4)
| user | rating | comment | translation |
|---|---|---|---|
| str | i64 | str | str |
| "Corleone" | 4 | "三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。" | "All three parts follow the sam… |
| "壹安²" | 4 | "韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。" | "Han Han wrote a true Chinese x… |
| "阿暖" | 3 | "韩寒你也看过F1" | "Han Han, you've watched F1 too" |
reviews |>
llm_translate(comment, "English") user rating comment
1 Corleone 4 三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。
2 壹安² 4 韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。
3 阿暖 3 韩寒你也看过F1
.translation
1 It's incredible how all three parts follow the exact same storyline arc: low point – opportunity – being scapegoated – self-improvement – becoming champion.
2 Han Han wrote a genuine Chinese chivalric tale, and once the deed was done, he brushed off his robe and left, cleanly victorious.
3 Han Han, you have watched F1 too
自定义提示词 (Custom prompt)
可以向 LLM 传递您自己的提示词,并让 mall 针对每个文本条目运行它:
my_prompt = (
"回答一个问题。"
"只返回答案,不需要解释"
"可接受的答案是 '值得看', '不值得看'"
"根据以下影评内容判断,这部电影值不值得看:"
)
reviews.llm.custom("comment", prompt = my_prompt)
shape: (3, 4)
| user | rating | comment | custom |
|---|---|---|---|
| str | i64 | str | str |
| "Corleone" | 4 | "三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。" | "值得看" |
| "壹安²" | 4 | "韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。" | "值得看" |
| "阿暖" | 3 | "韩寒你也看过F1" | "值得看" |
my_prompt <- paste(
"回答一个问题。",
"只返回答案,不需要解释",
"可接受的答案是 '值得看', '不值得看'",
"根据以下影评内容判断,这部电影值不值得看:"
)
reviews |>
llm_custom(comment, my_prompt) user rating comment
1 Corleone 4 三部都是低谷–机会–背锅–自强–冠军的剧情也是绝了。
2 壹安² 4 韩寒写了部真正的中国侠客故事,事了拂衣去,干干净净赢。
3 阿暖 3 韩寒你也看过F1
.pred
1 值得看
2 值得看
3 无法判断
调用 Vector
它也可以接收 Vector 作为输入。
# Pass it to a new LLMVec
from mall import LLMVec
llm = LLMVec(chat) llm.sentiment(["我很高兴","我不高兴"])['positive', 'negative']
llm_vec_sentiment(c("我很高兴","我不高兴"))[1] "positive" "negative"
参考资料 (Reference)
https://mlverse.github.io/mall/
https://posit-dev.github.io/chatlas/