技术工具详解
本项目所使用的核心技术与 R 包
概述
本项目作为数据科学入门教学案例,采用了经过业界验证的现代化工具链。本章将详细介绍每个工具的设计哲学、核心功能和使用方法,帮助您不仅知其然,更知其所以然。
GitHub API:数据的源头
什么是 API?
API(Application Programming Interface,应用程序编程接口)是软件系统之间交互的桥梁。可以把 API 想象成餐厅的服务员:您(客户端)向服务员(API)点餐(发送请求),服务员将订单传递给厨房(服务器),然后将做好的菜(数据)送回给您。
RESTful API 是目前最流行的 API 设计风格,它基于 HTTP 协议,使用统一的接口来操作资源。
GitHub API 的核心概念
GitHub API 遵循 REST 设计原则,通过 HTTP 请求来操作 GitHub 上的资源(仓库、Issue、评论等)。
端点(Endpoint)结构
https://api.github.com/{资源类型}/{所有者}/{仓库}/{操作}
常用端点示例:
| 端点 | 方法 | 说明 |
|---|---|---|
/repos/:owner/:repo/issues |
GET | 获取仓库的所有 Issue |
/repos/:owner/:repo/issues/:number/comments |
GET | 获取指定 Issue 的评论 |
/repos/:owner/:repo/issues/:number/comments |
POST | 在 Issue 下添加评论 |
/orgs/:org/members |
GET | 获取组织成员列表 |
/orgs/:org/invitations |
POST | 邀请用户加入组织 |
认证机制
GitHub API 对未认证请求有严格的频率限制(每小时 60 次),认证后限制提高到每小时 5000 次。
本项目的认证方式:
# 使用 gh CLI 的 Token
Sys.setenv(GITHUB_PAT = system("gh auth token", intern = TRUE))这种认证方式的优点:
- 安全性:Token 不直接写在代码中
- 便捷性:利用已有的 gh CLI 认证
- 可移植性:在不同机器上无需修改代码
分页机制
当数据量较大时,GitHub API 会分页返回结果。处理分页有两种方式:
方式一:使用 --paginate 参数(gh CLI)
gh api repos/D2RS-2026spring/members/issues/1/comments --paginate方式二:使用 R 的 gh 包(自动处理分页)
comments <- gh::gh(
"/repos/{owner}/{repo}/issues/{issue_number}/comments",
owner = "D2RS-2026spring",
repo = "members",
issue_number = 1,
per_page = 100, # 每页 100 条
.limit = Inf # 获取所有页面
)API 响应数据结构
GitHub API 返回 JSON 格式的数据。在 R 中,我们通常将其转换为列表或数据框:
# JSON 示例结构
{
"id": 123456,
"user": {
"login": "username",
"id": 789
},
"body": "评论内容",
"created_at": "2024-01-15T08:30:00Z"
}
# 在 R 中访问
comment <- comments[[1]]
comment$body # 访问评论内容
comment$user$login # 访问用户名httr 包:R 的 HTTP 客户端
设计哲学
httr 是 R 语言中用于 HTTP 请求的基础包,由 Hadley Wickham 开发。它的设计目标是提供简洁、一致的 API 来处理 HTTP 请求,同时保持足够的灵活性。
核心函数
| 函数 | 用途 | 示例 |
|---|---|---|
GET() |
发送 GET 请求 | GET(url) |
POST() |
发送 POST 请求 | POST(url, body = data) |
content() |
提取响应内容 | content(response, "text") |
status_code() |
获取状态码 | status_code(response) |
headers() |
获取响应头 | headers(response) |
使用示例
library(httr)
# 发送 GET 请求
response <- GET("https://api.github.com/repos/D2RS-2026spring/members/issues/1/comments")
# 检查状态码
status_code(response) # 200 表示成功
# 提取内容
content_text <- content(response, "text")
# 解析 JSON
library(jsonlite)
data <- fromJSON(content_text)错误处理
HTTP 请求可能失败,良好的错误处理是必要的:
response <- GET(url)
if (status_code(response) == 200) {
# 处理成功响应
data <- content(response, "parsed")
} else if (status_code(response) == 404) {
# 资源不存在
stop("请求的资源不存在")
} else if (status_code(response) == 403) {
# 频率限制
stop("API 频率限制,请稍后再试")
} else {
# 其他错误
stop(paste("请求失败,状态码:", status_code(response)))
}gh 包:GitHub API 的 R 封装
为什么选择 gh?
gh 是 r-lib 团队开发的 GitHub API 专用客户端,相比直接使用 httr,它提供了:
- 更简洁的语法:自动处理 URL 构建
- 内置分页:自动获取所有页面的数据
- 智能认证:自动检测和使用 GitHub Token
- 错误提示:更友好的错误信息
核心用法
library(gh)
# 基本请求
result <- gh("/repos/D2RS-2026spring/members/issues/1/comments")
# 带参数的请求
result <- gh(
"/repos/{owner}/{repo}/issues/{issue}/comments",
owner = "D2RS-2026spring",
repo = "members",
issue = 1,
per_page = 100
)
# POST 请求(创建资源)
gh(
"POST /repos/{owner}/{repo}/issues",
owner = "D2RS-2026spring",
repo = "members",
title = "新 Issue",
body = "Issue 内容"
)与 gh CLI 的集成
gh 包可以与 GitHub CLI(命令行工具)共享认证:
# 在 R 中使用 gh CLI 的 token
Sys.setenv(GITHUB_PAT = system("gh auth token", intern = TRUE))
# 现在 gh 包会自动使用这个 token这种集成方式的优势:
- 避免在多处管理 Token
- 利用 gh CLI 的安全存储
- 便于在脚本和命令行之间切换
jsonlite 包:JSON 数据处理
JSON 简介
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人类阅读和编写,也易于机器解析和生成。它是现代 Web API 的事实标准。
JSON 的基本数据类型:
| 类型 | 示例 | R 中的对应 |
|---|---|---|
| 对象 | {"name": "张三", "age": 25} |
命名列表 |
| 数组 | [1, 2, 3, 4] |
向量 |
| 字符串 | "hello" |
character |
| 数字 | 42, 3.14 |
numeric |
| 布尔 | true, false |
logical |
| 空值 | null |
NULL |
jsonlite 核心函数
library(jsonlite)
# 解析 JSON 字符串 → R 对象
json_str <- '{"name": "张三", "age": 25}'
data <- fromJSON(json_str)
# 结果:list(name = "张三", age = 25)
# R 对象 → JSON 字符串
obj <- list(name = "李四", scores = c(85, 90, 78))
json <- toJSON(obj, pretty = TRUE, auto_unbox = TRUE)
# 从文件/URL 解析
data <- fromJSON("data.json") # 本地文件
data <- fromJSON("https://api.example.com/data") # URL关键参数详解
| 参数 | 作用 | 推荐值 |
|---|---|---|
simplifyVector |
是否将单元素数组简化为向量 | TRUE(默认) |
simplifyDataFrame |
是否将结构化数据转换为数据框 | TRUE |
flatten |
是否展平嵌套结构 | TRUE |
示例:简化数据结构
# 原始 JSON
json <- '[
{"user": {"login": "alice"}, "body": "hello"},
{"user": {"login": "bob"}, "body": "hi"}
]'
# 默认解析(保留嵌套列表结构)
data1 <- fromJSON(json, simplifyDataFrame = FALSE)
# 结果:列表的列表,需要 data[[1]]$user$login 访问
# 转换为数据框(推荐)
data2 <- fromJSON(json, simplifyDataFrame = TRUE)
# 结果:数据框,可直接 data$body 访问dplyr 包:数据操作的艺术
设计理念
dplyr 是 tidyverse 的核心组件,提供了一套”动词”(verbs)来进行数据操作。它的设计哲学是:
- 每个函数只做一件事:清晰、可组合
- 函数名是动词:直观表达操作意图
- 管道操作
%>%:将多个操作串联成可读的工作流 - 惰性求值:只在需要时执行计算
核心”动词”
1. filter():筛选行
根据条件筛选满足条件的行:
library(dplyr)
# 筛选有效学号
students %>%
filter(valid_id == TRUE)
# 多条件筛选
students %>%
filter(valid_id == TRUE, !is.na(interest))
# 复杂条件
students %>%
filter(str_detect(student_id, "^2025"))2. select():选择列
选择或排除特定的列:
# 选择特定列
students %>%
select(student_id, name, interest)
# 使用辅助函数
students %>%
select(starts_with("student")) # 选择以 student 开头的列
students %>%
select(-created_at) # 排除某列
students %>%
select(where(is.numeric)) # 选择数值型列3. mutate():添加或修改列
创建新列或修改现有列:
# 添加新列
students %>%
mutate(
prefix = substr(student_id, 1, 4),
valid_id = str_detect(student_id, "^2025\\d{9}$")
)
# 基于现有列计算
students %>%
mutate(interest_clean = ifelse(interest == "无", NA, interest))4. arrange():排序
按一个或多个列排序:
# 升序排列
students %>%
arrange(created_at)
# 降序排列
students %>%
arrange(desc(created_at))
# 多列排序
students %>%
arrange(prefix, desc(created_at))5. summarise() / summarize():汇总
对数据进行汇总统计:
# 基本汇总
students %>%
summarise(
total = n(), # 计数
valid_count = sum(valid_id, na.rm = TRUE),
avg_length = mean(nchar(student_id), na.rm = TRUE)
)
# 与分组结合使用
students %>%
group_by(prefix) %>%
summarise(count = n())6. group_by():分组
为后续操作设置分组:
# 分组统计
votes %>%
group_by(model) %>%
summarise(
total_votes = n(),
avg_rank = mean(rank)
) %>%
arrange(avg_rank)管道操作符 %>%
管道操作符将左侧的结果作为右侧函数的第一个参数:
# 传统写法(嵌套,难以阅读)
arrange(
summarise(
group_by(students, prefix),
count = n()
),
desc(count)
)
# 管道写法(线性,从左到右)
students %>%
group_by(prefix) %>%
summarise(count = n()) %>%
arrange(desc(count))管道的好处:
- 代码可读性:操作顺序与阅读顺序一致
- 易于修改:在中间插入或删除步骤方便
- 减少临时变量:不需要创建多个中间对象
tidyr 包:数据整理专家
整洁数据原则
tidyr 用于将数据转换为”整洁”(tidy)格式,遵循三个原则:
- 每列是一个变量
- 每行是一个观测
- 每个单元格是一个值
核心函数
1. pivot_longer():宽格式 → 长格式
将多列合并为一列,适用于:
- 时间序列数据(多时间点为列)
- 重复测量数据
- 本项目的排名数据
library(tidyr)
# 原始数据(宽格式)
vote_wide <- data.frame(
user = c("A", "B"),
rank_1 = c("GPT-4o", "Claude"),
rank_2 = c("Claude", "GPT-4o"),
rank_3 = c("Gemini", "Kimi")
)
# 转换(长格式)
vote_long <- vote_wide %>%
pivot_longer(
cols = starts_with("rank"),
names_to = "rank_col",
values_to = "model"
) %>%
mutate(rank = as.numeric(str_extract(rank_col, "\\d+")))
# 结果:
# user rank_col model rank
# A rank_1 GPT-4o 1
# A rank_2 Claude 2
# ...2. pivot_wider():长格式 → 宽格式
将一列展开为多列,适用于:
- 创建透视表
- 宽格式展示
- 矩阵型数据
# 从长格式转回宽格式
vote_long %>%
pivot_wider(
names_from = rank,
values_from = model,
names_prefix = "第",
names_suffix = "名"
)3. separate() 和 unite():列的拆分与合并
# 拆分一列为多列
data %>%
separate(col = date, into = c("year", "month", "day"), sep = "-")
# 合并多列为一列
data %>%
unite(col = "full_name", first_name, last_name, sep = " ")4. drop_na() 和 replace_na():处理缺失值
# 删除含 NA 的行
data %>% drop_na()
# 替换 NA
data %>% replace_na(list(interest = "未填写"))stringr 包:字符串处理的优雅方案
为什么用 stringr?
相比 R 基础的正则函数(grep(), gsub(), strsplit() 等),stringr 提供:
- 一致的命名:所有函数以
str_开头 - 一致的参数顺序:
string总是第一个参数 - 向量化操作:自动处理向量输入
- 统一的正则语法:基于 ICU 正则表达式
核心函数分类
1. 检测与匹配
library(stringr)
# 检测模式是否存在
str_detect(c("apple", "banana", "cherry"), "a")
# [1] TRUE TRUE TRUE
# 查找匹配位置
str_locate("hello world", "world")
# start end
# [1,] 7 11
# 提取匹配内容
str_extract("学号:2025303110116", "\\d{13}")
# [1] "2025303110116"
# 提取所有匹配
str_extract_all("1, 2, 3", "\\d", simplify = TRUE)2. 替换与删除
# 替换匹配内容
text <- "学号:2025303110116,姓名:张三"
str_replace(text, "学号[::]\\s*", "")
# [1] "2025303110116,姓名:张三"
# 替换所有匹配
str_replace_all(text, "[::]", "=")
# 删除匹配内容(替换为空)
str_remove(text, "学号[::]\\s*")3. 拆分与连接
# 拆分字符串
str_split("a,b,c", ",")
# [[1]]
# [1] "a" "b" "c"
# 固定元素数拆分
str_split("2025-03-15", "-", n = 2)
# 连接字符串
str_c("Hello", "World", sep = " ")
# [1] "Hello World"
str_c("Prefix", c("A", "B", "C"), sep = "-")
# [1] "Prefix-A" "Prefix-B" "Prefix-C"4. 子字符串操作
text <- "华中农业大学"
# 提取子串
str_sub(text, 1, 3)
# [1] "华中农"
# 修改子串(R 中字符串不可变,实际是创建新字符串)
str_sub(text, 1, 2) <- "湖南"
# [1] "湖南农业大学"正则表达式速查
| 模式 | 含义 | 示例 |
|---|---|---|
. |
任意字符 | a.c 匹配 “abc”, “a_c” |
^ |
行首 | ^2025 匹配以 2025 开头 |
$ |
行尾 | \\d$ 匹配以数字结尾 |
\\d |
数字 | \\d{13} 匹配 13 位数字 |
\\w |
单词字符 | [a-zA-Z0-9_] |
\\s |
空白字符 | 空格、制表符、换行 |
* |
零次或多次 | a* 匹配 ““,”a”, “aaa” |
+ |
一次或多次 | a+ 匹配 “a”, “aaa” |
? |
零次或一次 | colou?r 匹配 “color”, “colour” |
{n} |
恰好 n 次 | \\d{4} 匹配 4 位数字 |
{n,m} |
n 到 m 次 | \\d{1,3} 匹配 1-3 位数字 |
[] |
字符集 | [abc] 匹配 a、b 或 c |
[^] |
否定字符集 | [^0-9] 匹配非数字 |
\u007c |
或 | cat\u007cdog 匹配 “cat” 或 “dog” |
() |
分组 | (ab)+ 匹配 “ab”, “abab” |
本项目的正则应用:
# 学号提取(以学号:或学号:开头,后跟数字)
str_extract(body, "(?<<=学号[::]\\s{0,5})\\d{13}")
# 姓名提取(2-4个中文字符)
str_extract(body, "[\\u4e00-\\u9fa5]{2,4}")
# 排名行匹配(以数字开头,后跟分隔符)
str_detect(lines, "^\\d+[..、]")ggplot2 包:数据可视化的语法
图形语法(Grammar of Graphics)
ggplot2 基于 Leland Wilkinson 的《图形语法》,将图形分解为独立的组件:
| 组件 | 作用 | 示例 |
|---|---|---|
| 数据(Data) | 要可视化的数据集 | data = students |
| 映射(Aesthetics) | 数据到视觉属性的映射 | aes(x = interest, y = count) |
| 几何对象(Geoms) | 图形的基本元素 | geom_bar(), geom_point() |
| 统计变换(Stats) | 数据汇总计算 | stat_count(), stat_smooth() |
| 坐标系(Coordinates) | 数据的定位系统 | coord_flip(), coord_polar() |
| 分面(Facets) | 多子图展示 | facet_wrap(), facet_grid() |
| 主题(Theme) | 非数据视觉元素 | theme_minimal(), theme_bw() |
基本结构
library(ggplot2)
# 基本模板
ggplot(data = <DATA>) +
<GEOM_FUNCTION>(
mapping = aes(<MAPPINGS>),
stat = <STAT>,
position = <POSITION>
) +
<COORDINATE_FUNCTION> +
<FACET_FUNCTION> +
<THEME_FUNCTION>常用几何对象
1. geom_bar():条形图
# 基础条形图
students %>%
count(interest) %>%
ggplot(aes(x = interest, y = n)) +
geom_bar(stat = "identity", fill = "steelblue")
# 水平条形图
ggplot(interest_count, aes(x = reorder(interest, n), y = n)) +
geom_bar(stat = "identity", fill = "steelblue") +
coord_flip() +
labs(title = "学生感兴趣方向分布", x = "方向", y = "人数")2. geom_tile():热力图
# 排名热力图
rank_long %>%
ggplot(aes(x = rank, y = reorder(model, avg_position), fill = count)) +
geom_tile() +
scale_fill_gradient(low = "white", high = "coral") +
labs(title = "模型排名分布热力图", x = "排名", y = "模型")3. geom_boxplot():箱线图
# 排名分布箱线图
vote_data %>%
ggplot(aes(x = reorder(model, rank), y = rank)) +
geom_boxplot(fill = "lightblue") +
coord_flip() +
labs(title = "模型排名分布", x = "模型", y = "排名")美学映射(Aesthetics)
# x, y:位置映射
# color:边框颜色
# fill:填充颜色
# size:点的大小
# alpha:透明度
# shape:点的形状
# linetype:线型
ggplot(data, aes(x = x, y = y, color = group, size = value)) +
geom_point()主题系统
# 内置主题
theme_gray() # 默认灰色背景
theme_bw() # 黑白主题
theme_minimal() # 极简主题
theme_classic() # 经典主题(无网格线)
# 自定义主题
theme(
plot.title = element_text(size = 16, face = "bold"),
axis.text = element_text(size = 10),
panel.background = element_rect(fill = "white"),
panel.grid.major = element_line(color = "gray90")
)showtext 包:解决中文显示问题
中文字体的挑战
R 的图形设备默认使用系统字体,在 macOS 和 Linux 上可能无法正确显示中文。showtext 包通过将文本渲染为图形路径来解决这个问题。
使用方法
library(showtext)
# 添加中文字体
# macOS 常见路径
font_add("WenQuanYi", "/System/Library/Fonts/wqy-microhei.ttc")
# 也可以使用 Google Fonts
font_add_google("Noto Sans SC", "noto")
# 自动启用
showtext_auto()
# 在 ggplot2 中使用
ggplot(data, aes(x, y)) +
geom_bar() +
labs(title = "中文标题") +
theme(text = element_text(family = "WenQuanYi"))各包之间的关系
┌─────────────────┐
│ Quarto │ ← 文档整合与发布
│ (输出层) │
└────────┬────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ggplot2 │ │ dplyr │ │ tidyr │
│ (可视化) │ │ (数据操作) │ │ (数据整理) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────┼───────────────────┘
│
▼
┌─────────────────┐
│ stringr │ ← 文本处理
│ (字符串处理) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ jsonlite │ ← 数据解析
│ httr / gh │ ← 数据获取
└─────────────────┘
延伸阅读
- R for Data Science (Hadley Wickham) - 数据科学的完整指南
- ggplot2: Elegant Graphics for Data Analysis - 可视化的深度解析
- Quarto 官方文档 (quarto.org) - 最新的文档特性
- GitHub API 文档 (docs.github.com/rest) - API 的完整参考
掌握这些工具,您就拥有了进行专业数据科学分析所需的核心技能。