技术工具详解

本项目所使用的核心技术与 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))

这种认证方式的优点:

  1. 安全性:Token 不直接写在代码中
  2. 便捷性:利用已有的 gh CLI 认证
  3. 可移植性:在不同机器上无需修改代码

分页机制

当数据量较大时,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,它提供了:

  1. 更简洁的语法:自动处理 URL 构建
  2. 内置分页:自动获取所有页面的数据
  3. 智能认证:自动检测和使用 GitHub Token
  4. 错误提示:更友好的错误信息

核心用法

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. 每个函数只做一件事:清晰、可组合
  2. 函数名是动词:直观表达操作意图
  3. 管道操作 %>%:将多个操作串联成可读的工作流
  4. 惰性求值:只在需要时执行计算

核心”动词”

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))

管道的好处:

  1. 代码可读性:操作顺序与阅读顺序一致
  2. 易于修改:在中间插入或删除步骤方便
  3. 减少临时变量:不需要创建多个中间对象

tidyr 包:数据整理专家

整洁数据原则

tidyr 用于将数据转换为”整洁”(tidy)格式,遵循三个原则:

  1. 每列是一个变量
  2. 每行是一个观测
  3. 每个单元格是一个值

核心函数

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 提供:

  1. 一致的命名:所有函数以 str_ 开头
  2. 一致的参数顺序string 总是第一个参数
  3. 向量化操作:自动处理向量输入
  4. 统一的正则语法:基于 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     │  ← 数据获取
                    └─────────────────┘

延伸阅读

  1. R for Data Science (Hadley Wickham) - 数据科学的完整指南
  2. ggplot2: Elegant Graphics for Data Analysis - 可视化的深度解析
  3. Quarto 官方文档 (quarto.org) - 最新的文档特性
  4. GitHub API 文档 (docs.github.com/rest) - API 的完整参考

掌握这些工具,您就拥有了进行专业数据科学分析所需的核心技能。