数据预处理入门学习教程,从入门到精通,数据清理工具—— OpenRefine 完整知识点与案例代码(7)
数据清理工具—— OpenRefine 完整知识点与案例代码
一、OpenRefine 介绍
1.1 什么是 OpenRefine
OpenRefine 是一款开源的桌面级数据清理与转换工具,最初由 Google 开发(原名 Google Refine),后移交社区维护。它以浏览器为界面,以本地服务器为后端,擅长处理杂乱数据的清洗、标准化和结构化。
核心特点:
| 特点 | 说明 |
|---|---|
| 交互式操作 | 所有操作均可撤销/重做,可视化学步轨迹 |
| 面向列 | 以列为操作单元,适合结构化表格数据 |
| 聚类(Clustering) | 内置多种聚类算法,识别拼写变体 |
| 表达式语言 | 支持 GREL、Jython、Clojure 三种表达式 |
| 操作历史 | 每一步操作记录为 JSON,可导出复用 |
| 扩展性 | 支持插件扩展和 Reconciliation API 对接 |
1.2 支持的数据格式
支持导入:
├── CSV / TSV
├── Excel(.xls / .xlsx)
├── JSON(标准 JSON、JSON 数组)
├── XML
├── RDF(三元组格式)
├── Google Sheets(通过插件)
└── 自定义分隔符文件
1.3 架构原理
┌──────────────┐ HTTP ┌──────────────────┐
│ 浏览器 UI │ ◄────────────► │ OpenRefine 服务 │
│ (前端交互) │ localhost │ (Java 后端) │
└──────────────┘ :3333 └───────┬──────────┘
│
┌──────▼──────┐
│ 数据存储 │
│ (项目目录) │
└─────────────┘
二、OpenRefine 的下载与安装
2.1 环境要求
- Java JDK 8 或以上(OpenRefine 3.x 需要 JDK 11+)
- 浏览器(推荐 Chrome / Firefox)
- 操作系统:Windows / macOS / Linux
2.2 安装步骤
步骤一:验证 Java 环境
# 检查 Java 是否已安装,输出版本号即表示可用
java -version
# 如果未安装,Ubuntu 下可执行:
sudo apt-get install default-jdk
# macOS 下使用 Homebrew:
brew install openjdk@11
步骤二:下载 OpenRefine
# 方式一:官网下载
# 访问 https://openrefine.org/download 下载对应系统版本
# 方式二:使用 wget 命令行下载(以 3.7.7 为例)
wget https://github.com/OpenRefine/OpenRefine/releases/download/3.7.7/openrefine-linux-3.7.7.tar.gz
# 解压
tar -xzf openrefine-linux-3.7.7.tar.gz
步骤三:启动 OpenRefine
# 进入解压后的目录
cd openrefine-3.7.7
# Linux/macOS 启动命令
./refine
# Windows 下双击 refine.bat 或在命令行执行
refine.bat
# 指定端口启动(默认 3333)
./refine -p 8080
# 指定内存(默认 1400M)
./refine -m 4096M
步骤四:访问界面
打开浏览器,访问:http://localhost:3333
2.3 配置文件修改
# 编辑配置文件
vi refine.ini
# 常用配置项:
REFINE_PORT=3333 # 修改默认端口
REFINE_MEMORY=4096M # 修改分配内存
REFINE_HOST=0.0.0.0 # 允许外部访问(默认仅 localhost)
三、OpenRefine 的基本操作
3.1 基本配置
3.1.1 语言与界面设置
OpenRefine 启动后可在界面右上角进行语言切换:
操作路径:界面右上角 → Language → 选择 "中文(简体)"
3.1.2 偏好设置(preferences)
OpenRefine 的偏好设置存储在 workspace.json 中:
{
"user界面": {
// 设置默认语言为中文
"lang": "zh",
// 设置浏览器界面显示的默认行数
"pageSize": 50
},
"scripting": {
// 设置 GREL 表达式最大执行时间(毫秒)
"maxTimeMsec": 300000
}
}
3.2 创建项目
3.2.1 从 CSV 创建项目
操作路径:
首页 → Create Project → This Computer → 选择文件 → 进入解析预览界面 → Create Project
CSV 文件解析选项说明:
Parse data as : CSV / TSV / Excel / JSON / XML -- 选择解析格式
Character encoding: UTF-8 / GBK / ISO-8859-1 -- 字符编码
Separator : 逗号(,) / 制表符(\t) / 自定义 -- 分隔符
Quote character : 双引号(") -- 引号字符
Header rows : 1 -- 表头行数(0 表示无表头)
3.2.2 从 JSON 创建项目
假设有一个 JSON 文件 data.json:
[
{"姓名": "张三", "年龄": 28, "城市": "北京"},
{"姓名": "李四", "年龄": 35, "城市": "上海"},
{"姓名": "王五", "年龄": 42, "城市": "广州"}
]
操作路径:
首页 → Create Project → This Computer → 选择 data.json
→ Parse data as: JSON → 选择合适的记录路径 → Create Project
3.2.3 从 URL 导入数据
操作路径:
首页 → Create Project → Web Addresses (URLs)
→ 输入 URL(如 https://example.com/data.csv)
→ 点击 "Next" → 确认解析选项 → Create Project
3.2.4 使用 GREL 辅助创建项目时的数据解析
在导入 JSON 时,OpenRefine 会自动检测 JSON 的嵌套结构,用户需要指定 记录路径(Record Path):
示例 JSON 结构:
{
"results": [
{"name": "Alice", "score": 95},
{"name": "Bob", "score": 87}
]
}
记录路径填写:/results -- 指定数组路径
即 GREL 表达式:value.results
3.3 操作列
3.3.1 列的基本操作
OpenRefine 的每列标题右侧有一个下拉菜单 ▼,包含以下常用操作:
列操作菜单结构:
├── Sort(排序)
├── View(视图)
├── Edit column
│ ├── Rename this column -- 重命名列
│ ├── Move column to beginning -- 移至最前
│ ├── Move column to end -- 移至最后
│ ├── Move column left -- 左移
│ ├── Move column right -- 右移
│ ├── Hide this column -- 隐藏列
│ ├── Remove this column -- 删除列
│ └── Add column based on this column -- 基于此列创建新列
├── Facet(分面)
├── Text filter(文本过滤)
├── Edit cells(单元格编辑)
│ ├── Transform... -- 转换
│ ├── Common transforms -- 常用转换
│ ├── Split multi-valued cells... -- 拆分多值单元格
│ └── Join multi-valued cells... -- 合并多值单元格
└── Reconcile(数据对账)
3.3.2 重命名列
GREL 表达式方式(通过 Edit column → Rename this column):
操作:
1. 点击列标题下拉菜单
2. 选择 Edit column → Rename this column
3. 输入新列名,如 "employee_name"
4. 点击 OK
3.3.3 添加基于现有列的新列
操作路径: 列下拉菜单 → Edit column → Add column based on this column
案例:根据 “姓名” 列生成 “姓名首字母” 列
# GREL 表达式(在弹出的对话框中输入)
# 获取姓名列的值,提取第一个字符
value.substring(0, 1)
# 新列名称:first_letter
案例:根据 “价格” 列计算含税价格(税率 13%)
# GREL 表达式
# 将价格转为数值,乘以 1.13 得到含税价格
# toNumber() 将字符串转为数值
# round() 四舍五入保留两位小数
round(toNumber(value) * 1.13, 2)
# 新列名称:price_with_tax
案例:根据 “姓” 和 “名” 两列合并为 “全名”
# GREL 表达式
# cells["姓"].value 获取另一列的值
# + 运算符连接字符串
cells["姓"].value + " " + cells["名"].value
# 新列名称:full_name
3.3.4 移除列
操作路径:
列下拉菜单 → Edit column → Remove this column
→ 确认删除
3.3.5 隐藏与显示列
隐藏操作:
列下拉菜单 → Edit column → Hide this column
显示操作:
界面左侧 All → 勾选需要显示的列
或点击 "Show All" 恢复全部显示
3.3.6 编辑单元格
操作路径: 列下拉菜单 → Edit cells → Transform...
案例:将 “状态” 列中的 “Active” 替换为 “活跃”
# GREL 表达式(在 Transform 对话框中输入)
# replace() 函数:将第一个参数替换为第二个参数
value.replace("Active", "活跃")
案例:去除 “地址” 列中的前后空格
# GREL 表达式
# trim() 去除字符串首尾空白字符
value.trim()
案例:将 “电话” 列统一为标准格式
# GREL 表达式
# replaceAll() 使用正则表达式替换
# 将所有非数字字符替换为空,只保留数字
# 然后用 substring 拼接为 xxx-xxxx-xxxx 格式
with(value.replaceAll("[^0-9]", ""), m,
m.substring(0, 3) + "-" + m.substring(3, 7) + "-" + m.substring(7, 11)
)
常用 Edit cells → Common transforms 选项:
Common transforms(常用快速转换):
├── To uppercase -- 转大写
├── To lowercase -- 转小写
├── To titlecase -- 首字母大写
├── To text -- 转为文本
├── To number -- 转为数字
├── To date -- 转为日期
├── To boolean -- 转为布尔值
├── To null -- 转为空值
├── Trim leading whitespace -- 去除首部空格
├── Trim trailing whitespace-- 去除尾部空格
├── Collapse whitespace -- 合并连续空格为一个
└── Unescape HTML entities -- 还原 HTML 实体
3.4 撤销与重做
3.4.1 操作历史面板
操作路径:界面左侧 → Undo / Redo
显示内容:
├── 操作步骤列表(每一步都有文字描述)
├── 当前步骤用粗体标记
├── 点击任意历史步骤可回退到该状态
└── 回退后的操作将变为灰色(可重新应用)
3.4.2 导出操作历史(JSON)
操作路径:
Undo / Redo → 点击 Extract... 按钮
导出内容格式为 JSON 数组,每个元素代表一步操作:
[
{
"op": "core/column-rename",
"description": "Rename column old_name to new_name",
"oldColumnName": "old_name",
"newColumnName": "new_name"
},
{
"op": "core/text-transform",
"description": "Text transform on cells in column address using expression value.trim()",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"columnName": "address",
"expression": "value.trim()",
"onError": "keep-original",
"repeat": false,
"repeatCount": 10
}
]
3.4.3 应用操作历史(JSON)
操作路径:
Undo / Redo → 点击 Apply... 按钮 → 粘贴 JSON → 点击 Perform Operations
用途:将清理步骤应用到具有相同结构的新数据集上
3.5 导出数据
3.5.1 导出菜单
操作路径:界面右上角 Export 按钮
导出格式:
├── Tab-separated values (.tsv) -- 制表符分隔
├── Comma-separated values (.csv) -- 逗号分隔
├── Excel (.xls) -- Excel 旧格式
├── Excel 2007+ (.xlsx) -- Excel 新格式
├── HTML table -- HTML 表格
├── SQL statements (.sql) -- SQL 插入语句
├── Templating... -- 自定义模板导出
├── OpenRefine project archive (.tar.gz) -- 项目归档
└── Custom tabular exporter... -- 自定义表格导出器
3.5.2 使用模板(Templating)自定义导出
操作路径: Export → Templating...
案例:将数据导出为 JSON 数组格式
// Templating 模板配置
// 开始模板(整个输出的开头)
[
// 行模板(每行数据的格式)
{
"name": "{{cells["姓名"].value}}",
"age": {{cells["年龄"].value}},
"city": "{{cells["城市"].value}}"
}
// 行分隔符(每行之间的连接符)
,
// 结束模板(整个输出的结尾)
]
// 以上模板输出结果示例:
// [
// {
// "name": "张三",
// "age": 28,
// "city": "北京"
// },
// {
// "name": "李四",
// "age": 35,
// "city": "上海"
// }
// ]
案例:将数据导出为 SQL INSERT 语句
// Templating 模板配置
// 开始模板
INSERT INTO employees (name, age, city) VALUES
// 行模板
('{{cells["姓名"].value}}', {{cells["年龄"].value}}, '{{cells["城市"].value}}')
// 行分隔符
,
// 结束模板
;
// 输出示例:
// INSERT INTO employees (name, age, city) VALUES
// ('张三', 28, '北京'),
// ('李四', 35, '上海');
案例:将数据导出为自定义 CSV(使用分号分隔)
// Templating 模板配置
// 开始模板
姓名;年龄;城市
// 行模板
{{cells["姓名"].value}};{{cells["年龄"].value}};{{cells["城市"].value}}
// 行分隔符
(换行符)
// 结束模板
(留空)
3.5.3 自定义表格导出器
操作路径:Export → Custom tabular exporter...
配置选项:
├── 勾选需要导出的列
├── 指定列的输出顺序
├── 选择分隔符(逗号、制表符、自定义)
├── 选择引号字符
├── 是否包含表头
└── 是否对多值单元格进行特殊处理
四、OpenRefine 的进阶操作
4.1 数据排序
4.1.1 单列排序
操作路径:
点击目标列标题 → Sort
排序选项:
├── Sort by: 选择列名
├── Order: text / number / date / boolean
│ ├── text: 字典序(A-Z)
│ ├── number: 数值大小
│ ├── date: 日期先后
│ └── boolean: true/false
└── Direction: ascending(升序) / descending(降序)
案例:按 “年龄” 列数值升序排序
步骤:
1. 点击 "年龄" 列的下拉菜单
2. 选择 Sort
3. Sort by: 年龄
4. Order: number(数值排序)
5. Direction: ascending(升序)
6. 点击 OK
4.1.2 多列排序
操作路径:
先对第一列排序 → 再点击第二列 → Sort → 勾选 "Sort by this column only" 取消
→ 选择 "Sort by" 添加多列排序
GREL 表达式排序示例:
# 使用 GREL 自定义排序键
# 先按城市字典序排,同城市再按年龄倒序
# 在 Sort 对话框中选择 "text" 类型,表达式如下:
# 排序键表达式(用于主排序列 "城市" 的排序):
value
# 排序键表达式(用于次排序列 "年龄" 的排序,注意降序用负号技巧):
# 如果年龄是数字类型,可以用负数实现降序
-toNumber(cells["年龄"].value)
4.1.3 移除排序
操作路径:
All 列下拉菜单 → Sort → Remove sort
或
在 Undo/Redo 面板中回退排序操作
4.2 数据归类(Facet/分面)
4.2.1 文本分面(Text Facet)
操作路径:
点击目标列 → Facet → Text facet
功能:
├── 左侧面板显示该列所有唯一值及其计数
├── 点击某个值可筛选出对应行
├── 支持多选(按住 Ctrl/Cmd)
└── 可按计数或字母排序
案例:对 “城市” 列创建文本分面
步骤:
1. 点击 "城市" 列下拉菜单
2. 选择 Facet → Text facet
3. 左侧面板出现分面,显示:
├── 北京 (120)
├── 上海 (98)
├── 广州 (85)
└── ...
4. 点击 "北京" 即可只显示城市为北京的行
4.2.2 数字分面(Numeric Facet)
操作路径:
点击目标列 → Facet → Numeric facet
功能:
├── 显示数值的直方图分布
├── 可拖动滑块选择数值范围
└── 自动识别最小值和最大值
案例:对 “工资” 列创建数字分面并筛选
步骤:
1. 点击 "工资" 列下拉菜单
2. 选择 Facet → Numeric facet
3. 左侧面板出现直方图
4. 拖动滑块选择范围,如 5000-10000
5. 面板下方显示:
├── choice(count)
├── 5000 —— 10000 (156)
└── 不在范围内的行自动隐藏
4.2.3 自定义分面(Custom Text Facet)
操作路径:
点击目标列 → Facet → Custom text facet...
使用 GREL 表达式自定义分组逻辑
案例:按年龄段创建分面
# GREL 表达式
# 将年龄分为 "青年(<30)"、"中年(30-50)"、"老年(>50)" 三个组
# toNumber() 将字符串转为数值
# 三元运算符:条件 ? 真值 : 假值
toNumber(value) < 30 ? "青年(<30)" : (toNumber(value) <= 50 ? "中年(30-50)" : "老年(>50)")
案例:按姓名首字母分组
# GREL 表达式
# substring(0, 1) 提取第一个字符
# toUppercase() 转为大写
value.substring(0, 1).toUppercase()
案例:按邮箱域名分组
# GREL 表达式
# split("@") 按 @ 拆分数组
# [1] 取第二个元素(即域名部分)
value.split("@")[1]
4.2.4 时间线分面(Timeline Facet)
操作路径:
点击日期列 → Facet → Timeline facet
功能:
├── 显示日期范围的直方图
├── 可选择日期粒度:year / month / day / hour
└── 拖动选择时间范围
4.2.5 散点图分面(Scatterplot Facet)
操作路径:
列下拉菜单 → Facet → Scatterplot facet
功能:
├── 在两个数值维度上绘制散点图
├── 可在图上框选区域进行筛选
└── 适合发现异常值和数据分布模式
4.3 重复检测
4.3.1 标记重复行
案例:使用 GREL 标记 “姓名” 列的重复值
# 步骤:
# 1. 点击 "姓名" 列 → Edit column → Add column based on this column
# 2. 新列名:is_duplicate
# 3. 输入以下 GREL 表达式
# 方法一:使用 facet 计数
# 先创建 Text Facet,找出计数 > 1 的值
# 方法二:使用 GREL 表达式检查
# row.columnNames 返回所有列名
# 通过 facet 统计,然后用 facet 结果筛选
使用聚类(Clustering)发现近似重复:
操作路径:
点击 "姓名" 列 → Edit cells → Cluster and edit...
聚类方法选项:
├── key collision(键碰撞法)
│ ├── fingerprint -- 指纹法(去标点、空格、统一小写后比较)
│ ├── metaphone3 -- 语音法(英文)
│ ├── Cologne-phonetic -- 科隆语音法(德文)
│ ├── Daitch-Mokotoff -- 双重语音法
│ ├── Beider-Morse -- BM 语音法
│ ├── ngram-fingerprint -- N-gram 指纹法
│ └── cologne-phonetic -- 科隆法
└── nearest neighbor(最近邻法)
├── levenshtein -- 编辑距离法
└── PPM -- 压缩距离法
案例:聚类清理姓名拼写变体
示例数据:
| 姓名 |
|-----------|
| 张 三 |(多余空格)
| 张三 |
| Zhang San |
| zhang san |
操作步骤:
1. 点击 "姓名" 列 → Edit cells → Cluster and edit...
2. Method: key collision
3. Keying function: fingerprint
4. 聚类结果将显示:
Cluster 1: "张 三", "张三" → Merge? → 合并为 "张三"
Cluster 2: "Zhang San", "zhang san" → Merge? → 合并为 "Zhang San"
5. 勾选需要合并的聚类
6. 点击 "Merge selected & re-cluster"
4.3.2 使用行号去重
# 步骤:
# 1. 添加行号列
# 2. 创建分面后结合筛选去重
# 使用 GREL 获取当前行索引
# 在 "Add column based on this column" 中使用:
row.index
4.3.3 使用 Duplicate Flag 分面
# 步骤:
# 1. 先按需要检测的列排序
# 2. 添加新列,用 GREL 标记是否与上一行相同:
# GREL 表达式
# row.index > 0 判断不是第一行
# value == rows[row.index - 1].cells["姓名"].value 判断与上一行是否相同
# 注意:此方法要求先排序
(row.index > 0) && (value == rows[row.index - 1].cells["姓名"].value)
4.4 数据填充
4.4.1 填充空值(Fill Down)
操作路径:
列下拉菜单 → Edit cells → Fill down
功能:将当前单元格的空值用上方最近的非空值填充
适用场景:合并单元格导入后的空值处理
案例:处理合并单元格导入数据
原始数据:
| 部门 | 姓名 |
|--------|------|
| 技术部 | 张三 |
| | 李四 |
| | 王五 |
| 市场部 | 赵六 |
| | 钱七 |
操作:点击 "部门" 列 → Edit cells → Fill down
结果:
| 部门 | 姓名 |
|--------|------|
| 技术部 | 张三 |
| 技术部 | 李四 |
| 技术部 | 王五 |
| 市场部 | 赵六 |
| 市场部 | 钱七 |
4.4.2 使用 GREL 填充特定条件的空值
案例:用默认值填充空单元格
# GREL 表达式(Transform)
# isNull() 或 isBlank() 判断是否为空
# 为空时填入 "未知",否则保留原值
if(isNull(value), "未知", value)
# 或使用简写(利用 OR 运算符短路特性)
# 空值时使用默认值
value.or("未知")
# 为数值型列填充默认值 0
if(isNull(value), 0, toNumber(value))
案例:用其他列的值填充空值
# GREL 表达式
# 如果 "备用电话" 为空,则使用 "主电话" 的值填充
if(isBlank(value), cells["主电话"].value, value)
4.4.3 使用插值填充
案例:用相邻值的平均值填充缺失数值
# GREL 表达式
# 获取前一行和后一行的值,取平均值填充当前空值
if(
isNull(value),
(
toNumber(rows[row.index - 1].cells["score"].value) +
toNumber(rows[row.index + 1].cells["score"].value)
) / 2,
value
)
4.5 文本过滤
4.5.1 基本文本过滤
操作路径:
列下拉菜单 → Text filter
功能:
├── 输入关键词即可实时筛选
├── 支持正则表达式
├── 多个过滤器之间为 AND 关系
└── 区分大小写开关
案例:使用正则表达式过滤邮箱
过滤条件:
输入正则表达式:^[a-zA-Z0-9._%+-]+@gmail\.com$
勾选 "regular expression"
效果:只显示邮箱为 Gmail 地址的行
4.5.2 GREL 中的常用字符串函数
以下是 OpenRefine GREL 表达式中所有常用的字符串操作函数及案例:
① value.length() — 字符串长度
# 返回字符串的长度(字符个数)
# 案例:筛选姓名长度为 2 的记录
value.length()
# 案例:在 Transform 中判断
if(value.length() < 2, "姓名过短", value)
② value.toLowercase() / value.toUppercase() — 大小写转换
# 转为小写
value.toLowercase()
# 转为大写
value.toUppercase()
# 案例:统一邮箱为小写
value.toLowercase().trim()
③ value.trim() — 去除首尾空白
# 去除首尾空格、制表符、换行符
value.trim()
# 案例:清理并标准化
value.trim().toLowercase()
④ value.contains("关键词") — 是否包含
# 返回 true/false
# 案例:检查是否包含 "错误" 字样
value.contains("错误")
# 案例:包含任一关键词
value.contains("错误") || value.contains("异常") || value.contains("失败")
# 结合正则表达式
# regex() 用于正则匹配
if(value.contains("error"), "有问题", "正常")
⑤ value.startsWith("前缀") / value.endsWith("后缀")
# 案例:检查是否以 "CN-" 开头
value.startsWith("CN-")
# 案例:检查是否以 ".pdf" 结尾
value.endsWith(".pdf")
# 案例:筛选以特定前缀开头的编号
if(value.startsWith("ORD-2024"), "2024年订单", "其他")
⑥ value.replace("旧", "新") / value.replaceAll("正则", "新")
# replace:简单文本替换(只替换第一个匹配)
value.replace("北京", "BJ")
# replaceAll:正则替换(替换所有匹配)
# 案例:去除所有非数字字符
value.replaceAll("[^0-9]", "")
# 案例:将连续空格替换为单个空格
value.replaceAll("\\s+", " ")
# 案例:脱敏手机号(保留前3后4)
# $1 和 $2 为正则捕获组
value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")
# 案例:去除 HTML 标签
value.replaceAll("<[^>]+>", "")
⑦ value.split("分隔符") — 字符串拆分为数组
# 案例:按逗号拆分标签
value.split(",")
# 案例:拆分后取第一个元素
value.split(",")[0]
# 案例:拆分后统计个数
value.split(",").length()
# 案例:拆分后对每个元素去空格
value.split(",").forEach(e, e.trim()).join(",")
# 案例:按多种分隔符拆分
# 使用正则表达式匹配逗号或分号
value.split("[,;]")
⑧ 数组的 .join("连接符") — 数组合并为字符串
# 案例:将数组用分号连接
["张三", "李四", "王五"].join("; ")
# 结果:"张三; 李四; 王五"
# 案例:拆分、去重、重新连接
value.split(",").uniques().join(", ")
⑨ value.substring(start, end) — 截取子串
# 截取索引 0 到 3(不含3)的字符
value.substring(0, 3)
# 案例:提取日期中的年份(格式:2024-01-15)
value.substring(0, 4)
# 案例:提取月份
value.substring(5, 7)
# 案例:隐藏身份证中间部分
value.substring(0, 6) + "********" + value.substring(14, 18)
⑩ value.indexOf("关键词") — 查找位置
# 返回首次出现的索引位置,未找到返回 -1
value.indexOf("@")
# 案例:提取 @ 之前的用户名部分
# @ 在 value 中的位置
# substring(0, pos) 截取 @ 之前的部分
with(value.indexOf("@"), pos,
if(pos > 0, value.substring(0, pos), value)
)
# 案例:提取 @ 之后的域名
with(value.indexOf("@"), pos,
if(pos > 0, value.substring(pos + 1, value.length()), "")
)
⑪ value.reverse() — 反转字符串
# 案例:反转文本
value.reverse()
# "Hello" → "olleH"
⑫ value.fingerprint() — 生成指纹
# 标准化文本:去标点、统一小写、去空白、排序词组
# 用于聚类和去重
value.fingerprint()
# 案例:比较两个字段是否实质相同
value.fingerprint() == cells["参考值"].value.fingerprint()
⑬ value.escape("format") — 转义
# HTML 转义
value.escape("html")
# "Tom & Jerry" → "Tom & Jerry"
# JavaScript 转义
value.escape("javascript")
# URL 编码
value.escape("url")
# "hello world" → "hello%20world"
⑭ value.unescape("format") — 反转义
# HTML 反转义
value.unescape("html")
# "Tom & Jerry" → "Tom & Jerry"
4.5.3 正则表达式在 OpenRefine 中的使用
GREL 中的正则函数:
# 1. regex() — 正则匹配,返回匹配结果数组或 null
# 案例:提取字符串中的数字
value.regex(".*(\\d+).*")
# 若 value = "ABC123DEF",返回 ["123"]
# 案例:检查是否匹配
# 返回 true/false
value.match("^[A-Z]{2}\\d{4}$")
# 检查是否两个大写字母后跟四位数字
# 2. match() — 类似 regex(),但更严格
value.match("\\d{4}-\\d{2}-\\d{2}")
# 检查是否为日期格式 YYYY-MM-DD
# 3. replaceAll() — 正则替换(前面已介绍)
# 案例:提取所有数字并用逗号连接
# 先用 regex 或 replaceAll 处理
with(
value.replaceAll("[^0-9]+", ","),
nums,
// 去除首尾多余的逗号
nums.replaceAll("^,|,$", "")
)
常用正则模式:
常见正则表达式参考:
├── ^...$ -- 精确匹配(匹配整个字符串)
├── \\d -- 匹配数字 [0-9]
├── \\d{3} -- 匹配恰好3位数字
├── \\d{3,} -- 匹配3位或更多数字
├── \\w -- 匹配单词字符 [a-zA-Z0-9_]
├── \\s -- 匹配空白字符
├── [a-z] -- 小写字母
├── [A-Z] -- 大写字母
├── [a-zA-Z] -- 任意字母
├── [^...] -- 取反(不匹配括号内字符)
├── + -- 一个或多个
├── * -- 零个或多个
├── ? -- 零个或一个(非贪婪用 .*?)
├── (\\d+) -- 捕获组(可用 $1 引用)
└── | -- 或(交替)
4.6 数据转换
4.6.1 数值转换函数
# toNumber() — 字符串转数值
toNumber("123.45")
# 结果:123.45
# toString() — 数值转字符串
toString(123)
# 结果:"123"
# round() — 四舍五入
round(3.14159, 2)
# 结果:3.14
# floor() — 向下取整
floor(3.7)
# 结果:3
# ceil() — 向上取整
ceil(3.2)
# 结果:4
# abs() — 绝对值
abs(-5)
# 结果:5
# min() / max() — 最小值/最大值
min(3, 7)
# 结果:3
max(3, 7)
# 结果:7
# sum() — 求和
sum(1, 2, 3, 4, 5)
# 结果:15
# 案例:将价格列(含货币符号)转为数值
# 去除 $ 和逗号,再转为数字
toNumber(value.replace("$", "").replaceAll(",", ""))
4.6.2 日期转换函数
# toDate() — 字符串转日期对象
# 用法:toDate("字符串", "格式")
toDate("2024-01-15", "yyyy-MM-dd")
# 结果:2024-01-15T00:00:00Z
toDate("15/01/2024", "dd/MM/yyyy")
# 结果:2024-01-15T00:00:00Z
toDate("2024年1月15日", "yyyy年M月d日")
# 结果:2024-01-15T00:00:00Z
# toString(date, "格式") — 日期格式化输出
toString(toDate(value, "yyyy-MM-dd"), "dd/MM/yyyy")
# "2024-01-15" → "15/01/2024"
# 案例:提取年份
# 方法一:字符串截取
value.substring(0, 4)
# 方法二:日期对象方法
toDate(value, "yyyy-MM-dd").year()
# 返回 2024
# 案例:计算天数差
# dateDiff(日期1, 日期2, "单位")
dateDiff(
toDate("2024-01-01", "yyyy-MM-dd"),
toDate(value, "yyyy-MM-dd"),
"days"
)
# 返回两个日期之间的天数差
# 常用日期字段方法:
# date.year() -- 提取年份
# date.month() -- 提取月份(0-11)
# date.day() -- 提取日(1-31)
# date.hours() -- 提取小时
# date.minutes() -- 提取分钟
# date.seconds() -- 提取秒
# 案例:判断日期是否在2024年
with(toDate(value, "yyyy-MM-dd"), d,
d.year() == 2024
)
4.6.3 布尔与逻辑函数
# isNull() — 判断是否为 null
isNull(value)
# isNonBlank() — 判断是否非空(不是 null 且不是空字符串)
isNonBlank(value)
# isBlank() — 判断是否为空(null 或空字符串)
isBlank(value)
# isNumeric() — 判断是否为数值
isNumeric(value)
# isTrue() / isFalse() — 判断布尔值
isTrue(value)
isFalse(value)
# 逻辑运算符
# && (与)、|| (或)、! (非)
if(isNonBlank(value) && value.length() > 5, "长文本", "短文本")
4.6.4 条件与控制流
# if(条件, 真值, 假值) — 条件判断
if(toNumber(value) > 100, "高", "低")
# 嵌套 if
if(
toNumber(value) >= 90, "优秀",
if(toNumber(value) >= 80, "良好",
if(toNumber(value) >= 60, "及格", "不及格")
)
)
# with(表达式, 变量名, 使用变量的表达式) — 局部变量
# 避免重复计算
with(toNumber(value), score,
if(score >= 90, "A",
if(score >= 80, "B",
if(score >= 70, "C",
if(score >= 60, "D", "F")
)
)
)
)
4.6.5 数组操作函数
# 分裂后的多值操作
# .length() — 数组长度
# .join("sep") — 连接为字符串
# .sort() — 排序
# .reverse() — 反转
# .uniques() — 去重
# .contains(x) — 是否包含元素
# .forEach(v, 表达式) — 遍历处理
# 案例:标签去重并排序
value.split(",").uniques().sort().join(", ")
# 案例:统计不同标签的个数
value.split(",").uniques().length()
# 案例:forEach 遍历处理每个元素
# 将每个标签去空格并转小写
value.split(",").forEach(v, v.trim().toLowercase()).join(",")
# 案例:过滤出包含特定关键词的元素
value.split(",").filter(v, v.contains("错误")).join(",")
4.6.6 跨列操作
# 访问其他列的值
# cells["列名"].value
# 案例:两列求和
toNumber(cells["数学"].value) + toNumber(cells["英语"].value)
# 案例:两列拼接
cells["姓"].value + cells["名"].value
# 案例:根据其他列的值决定当前列的值
if(
toNumber(cells["总价"].value) / toNumber(cells["数量"].value) > 100,
"高价商品",
"普通商品"
)
# 案例:访问上一行的值(需要先排序)
# rows[row.index - 1] 访问上一行
if(
row.index > 0,
value - rows[row.index - 1].cells["累计"].value,
value
)
4.6.7 综合转换案例
案例:将 “地址” 列拆分为 “省”、“市”、“区” 三列
# 假设地址格式:"北京市朝阳区建国路88号"
# 步骤一:创建新列 "省"
# 使用 Add column based on this column
# GREL 表达式:提取省/市部分
# 查找 "市" 的位置
value.substring(0, value.indexOf("市") + 1)
# 结果:"北京市"
# 步骤二:创建新列 "区"
value.substring(
value.indexOf("市") + 1,
value.indexOf("区") + 1
)
# 结果:"朝阳区"
# 步骤三:创建新列 "详细地址"
value.substring(value.indexOf("区") + 1)
# 结果:"建国路88号"
案例:标准化电话号码
# 原始数据可能包含各种格式:
# "010-12345678" / "138 1234 5678" / "(021)87654321" / "+86-13912345678"
# GREL 表达式
# 第一步:去除所有非数字字符
with(value.replaceAll("[^0-9]", ""), digits,
# 第二步:如果有国际区号前缀 86,去掉前两位
if(digits.startsWith("86") && digits.length() > 11,
digits.substring(digits.length() - 11),
digits
)
)
案例:标记异常值
# 在 "age" 列创建新列标记异常
with(toNumber(value), age,
if(
isNull(value) || age < 0 || age > 150, "异常值",
if(age < 18, "未成年", "成年")
)
)
五、综合案例——多伦多市建筑许可数据分析
5.1 分析目标
分析目标:
1. 清理多伦多市建筑许可数据中的脏数据
2. 标准化地址、日期等字段
3. 检测并处理重复记录
4. 填充缺失数据
5. 按区域和许可类型分析建筑许可的分布情况
6. 导出清洁后的数据用于可视化分析
5.2 数据获取
数据来源:多伦多市开放数据门户
URL: https://open.toronto.ca/dataset/building-permits-collected-by-the-city-of-toronto/
文件格式:CSV
字段说明:
├── PERMIT_NUM -- 许可编号
├── PERMIT_TYPE -- 许可类型
├── DESCRIPTION -- 项目描述
├── ADDRESS -- 地址
├── MUNICIPALITY -- 市辖区
├── WARD -- 选区
├── APPLICATION_DATE -- 申请日期
├── ISSUE_DATE -- 签发日期
├── ESTIMATED_VALUE -- 估算价值
├── STATUS -- 状态
└── CONTRACTOR -- 承包商
下载数据:
# 使用 curl 下载数据
curl -o building_permits.csv \
"https://ckan0.cf.opendata.inter.prod-toronto.ca/dataset/3f7e4538-4967-4f3b-8639-3f1da7e05796/resource/ba5dd371-5f7f-44fc-8495-18e0f03e3cc3/download/building-permits-toronto.csv"
在 OpenRefine 中导入:
操作路径:
1. 打开浏览器访问 http://localhost:3333
2. 点击 "Create Project"
3. 选择 "This Computer"
4. 选择 building_permits.csv 文件
5. 解析选项:
- Character encoding: UTF-8
- Separator: , (comma)
- Quote character: "
- Header rows: 1
6. 点击 "Create Project"
5.3 数据清理
5.3.1 步骤一:重命名列(标准化列名)
操作路径与 GREL:
# 将列名统一为小写,空格替换为下划线
# 通过 Undo/Redo → Apply 应用以下 JSON 操作记录:
# 重命名 "PERMIT_NUM" 为 "permit_num"
# 重命名 "PERMIT_TYPE" 为 "permit_type"
# 重命名 "DESCRIPTION" 为 "description"
# 重命名 "ADDRESS" 为 "address"
# 重命名 "MUNICIPALITY" 为 "municipality"
# 重命名 "WARD" 为 "ward"
# 重命名 "APPLICATION_DATE" 为 "application_date"
# 重命名 "ISSUE_DATE" 为 "issue_date"
# 重命名 "ESTIMATED_VALUE" 为 "estimated_value"
# 重命名 "STATUS" 为 "status"
# 重命名 "CONTRACTOR" 为 "contractor"
# JSON 操作记录(可直接在 Apply 中粘贴执行):
[
{
"op": "core/column-rename",
"oldColumnName": "PERMIT_NUM",
"newColumnName": "permit_num"
},
{
"op": "core/column-rename",
"oldColumnName": "PERMIT_TYPE",
"newColumnName": "permit_type"
},
{
"op": "core/column-rename",
"oldColumnName": "DESCRIPTION",
"newColumnName": "description"
},
{
"op": "core/column-rename",
"oldColumnName": "ADDRESS",
"newColumnName": "address"
},
{
"op": "core/column-rename",
"oldColumnName": "MUNICIPALITY",
"newColumnName": "municipality"
},
{
"op": "core/column-rename",
"oldColumnName": "WARD",
"newColumnName": "ward"
},
{
"op": "core/column-rename",
"oldColumnName": "APPLICATION_DATE",
"newColumnName": "application_date"
},
{
"op": "core/column-rename",
"oldColumnName": "ISSUE_DATE",
"newColumnName": "issue_date"
},
{
"op": "core/column-rename",
"oldColumnName": "ESTIMATED_VALUE",
"newColumnName": "estimated_value"
},
{
"op": "core/column-rename",
"oldColumnName": "STATUS",
"newColumnName": "status"
},
{
"op": "core/column-rename",
"oldColumnName": "CONTRACTOR",
"newColumnName": "contractor"
}
]
5.3.2 步骤二:清理地址字段
# 操作:点击 address 列 → Edit cells → Transform...
# GREL 表达式:
# 1. 去除首尾空格
# 2. 合并连续空格为单个空格
# 3. 统一为标题大小写(首字母大写)
with(
value.trim().replaceAll("\\s+", " "), // 去首尾空格 + 合并连续空格
cleaned,
// 分割单词,每个单词首字母大写
with(cleaned.split(" "), words,
words.forEach(w,
w.substring(0, 1).toUppercase() + w.substring(1).toLowercase()
).join(" ")
)
)
# 示例结果:
# " 123 YONGE st " → "123 Yonge St"
# " 456 BLOOR ST WEST" → "456 Bloor St West"
5.3.3 步骤三:清理数值字段
# 操作:点击 estimated_value 列 → Edit cells → Transform...
# GREL 表达式:
# 去除 $ 符号、逗号、空格,转为数值
# 处理异常值(负数、极端值)
with(
toNumber(value.replace("$", "").replaceAll(",", "").trim()),
val,
if(
isNull(val),
"", // 原始为空则保持为空
if(
val < 0,
abs(val), // 负数取绝对值
if(
val > 1000000000, // 超过10亿可能为数据错误
null, // 标记为空
val // 正常值保留
)
)
)
)
# 示例结果:
# "$1,234,567.00" → 1234567
# "N/A" → null
# "-5000" → 5000
5.3.4 步骤四:标准化日期格式
# 操作:点击 application_date 列 → Edit cells → Transform...
# GREL 表达式:
# 将各种日期格式统一为 ISO 格式 yyyy-MM-dd
with(value.trim(), d,
if(
isBlank(d),
"",
// 尝试多种格式解析
// 优先尝试 MM/dd/yyyy 格式(美式)
if(
d.contains("/"),
toString(toDate(d, "MM/dd/yyyy"), "yyyy-MM-dd"),
// 再尝试 yyyy-MM-dd 格式
if(
d.contains("-"),
toString(toDate(d, "yyyy-MM-dd"), "yyyy-MM-dd"),
// 再尝试 dd-MMM-yyyy 格式(如 15-Jan-2024)
toString(toDate(d, "dd-MMM-yyyy"), "yyyy-MM-dd")
)
)
)
)
# 示例结果:
# "01/15/2024" → "2024-01-15"
# "2024-01-15" → "2024-01-15"
# "15-Jan-2024" → "2024-01-15"
5.3.5 步骤五:检测重复记录
操作步骤:
1. 先创建分面检查重复情况
2. 点击 permit_num 列 → Facet → Text facet
3. 观察左侧面板中是否有重复的许可证编号
4. 点击 permit_num 列 → Facet → Custom text facet
使用自定义分面发现重复:
# GREL 表达式(Custom text facet)
# 如果 permit_num 出现多次,标记为 "重复"
# 先在 Text Facet 中查看 count > 1 的记录
# 然后添加标记列
# 步骤一:添加新列 is_duplicate
# 操作:permit_num 列 → Edit column → Add column based on this column
# GREL 表达式:
row.flag // 标记行标志
# 步骤二:使用聚类消除近似重复
# 操作:permit_num 列 → Edit cells → Cluster and edit...
# Method: key collision
# Keying function: fingerprint
手动标记重复行并删除:
# 添加标记列
# 操作:All → Edit column → Add column based on this column
# 列名:row_flag
# GREL 表达式:标记完全重复的行(基于 permit_num 和 address)
# 通过 facet + flag 组合使用
# 简化方法:使用 All 列分面过滤
# 1. 创建 permit_num 的 Text Facet
# 2. 在 Facet 中按 count 排序
# 3. 选择 count > 1 的项
# 4. 点击 All → Edit rows → Remove all matching rows
# (谨慎操作,建议先标记再确认)
5.3.6 步骤六:填充缺失数据
案例:填充缺失的 municipality 字段
# 操作:municipality 列 → Edit cells → Transform...
# GREL 表达式:
# 如果 municipality 为空,尝试从 address 中推断
# 假设地址中包含区域关键词
if(
isBlank(value),
// 根据地址中的关键词推断
with(cells["address"].value.toLowerCase(), addr,
if(
addr.contains("scarborough"), "Scarborough",
if(
addr.contains("north york"), "North York",
if(
addr.contains("etobicoke"), "Etobicoke",
if(
addr.contains("york"), "York",
if(
addr.contains("east york"), "East York",
"Toronto" // 默认值
)
)
)
)
)
),
value // 不为空则保留原值
)
案例:填充缺失的 estimated_value
# 操作:estimated_value 列 → Edit cells → Transform...
# GREL 表达式:
# 用同类型许可的平均值填充(简化处理:用中位数近似值)
# 此处简化为用已知的同 permit_type 的非空值填充
if(
isBlank(value),
// 基于 permit_type 的默认估值
with(cells["permit_type"].value, pType,
if(
pType.contains("New"), 850000, // 新建类许可平均值
if(
pType.contains("Renovation"), 150000, // 翻新类
if(
pType.contains("Demolition"), 50000, // 拆除类
100000 // 其他类默认值
)
)
)
),
value // 不为空则保留原值
)
5.3.7 步骤七:数据分类与分面分析
案例:按许可类型创建分面统计
操作路径:
1. 点击 permit_type 列 → Facet → Text facet
2. 左侧面板显示各类型及计数
结果示例:
├── New Building - Residential (1250)
├── New Building - Commercial (380)
├── Addition/Alteration - Residential (2100)
├── Addition/Alteration - Commercial (650)
├── Demolition (420)
└── Plumbing (800)
案例:按年份分析趋势
# 添加年份列
# 操作:application_date 列 → Edit column → Add column based on this column
# 列名:permit_year
# GREL 表达式
if(
isNonBlank(value),
toString(toDate(value, "yyyy-MM-dd"), "yyyy"),
""
)
# 然后创建年份分面
# 点击 permit_year 列 → Facet → Text facet
# 可观察每年的许可证数量变化
案例:按估算价值区间分析
# 操作:estimated_value 列 → Facet → Custom text facet...
# GREL 表达式
with(toNumber(value), v,
if(
isNull(v), "未填写",
if(v < 10000, "< 1万",
if(v < 100000, "1万-10万",
if(v < 1000000, "10万-100万",
if(v < 10000000, "100万-1000万",
"> 1000万"
)
)
)
)
)
)
5.3.8 步骤八:计算派生指标
案例:计算审批天数(从申请到签发)
# 操作:application_date 列 → Edit column → Add column based on this column
# 列名:processing_days
# GREL 表达式
with(
cells["application_date"].value, appDate,
with(
cells["issue_date"].value, issDate,
if(
isNonBlank(appDate) && isNonBlank(issDate),
// 计算两个日期之间的天数
dateDiff(
toDate(appDate, "yyyy-MM-dd"),
toDate(issDate, "yyyy-MM-dd"),
"days"
),
""
)
)
)
# 结果示例:
# application_date = "2024-01-15", issue_date = "2024-02-20"
# → processing_days = 36
案例:标记审批状态
# 操作:processing_days 列 → Edit column → Add column based on this column
# 列名:processing_category
# GREL 表达式
with(toNumber(value), days,
if(
isNull(days), "未签发",
if(
days <= 30, "正常(≤30天)",
if(
days <= 90, "较慢(31-90天)",
"超慢(>90天)"
)
)
)
)
5.3.9 步骤九:导出清理后的数据
操作路径:
1. 隐藏不需要的辅助列
- 点击辅助列 → Edit column → Hide this column
2. 导出为 CSV
- 点击右上角 Export → Comma-separated values (.csv)
3. 导出为 Excel
- 点击右上角 Export → Excel 2007+ (.xlsx)
4. 导出操作历史
- 左侧面板 Undo/Redo → Extract...
- 保存为 JSON 文件,下次可直接应用到新数据
使用模板导出为 JSON 供可视化工具使用:
// Export → Templating...
// 开始模板
{
"project": "Toronto Building Permits Analysis",
"total_records": {{grel:rows.length()}},
"data": [
// 行模板
{
"permit_number": "{{cells["permit_num"].value}}",
"type": "{{cells["permit_type"].value}}",
"address": "{{cells["address"].value}}",
"municipality": "{{cells["municipality"].value}}",
"application_date": "{{cells["application_date"].value}}",
"issue_date": "{{cells["issue_date"].value}}",
"estimated_value": {{cells["estimated_value"].value}},
"processing_days": {{cells["processing_days"].value}},
"status": "{{cells["status"].value}}"
}
// 行分隔符
,
// 结束模板
]
}
5.4 实现步骤总结
完整操作流程(JSON 操作记录,可通过 Apply 复现):
[
{"op": "core/column-rename", "oldColumnName": "PERMIT_NUM", "newColumnName": "permit_num"},
{"op": "core/column-rename", "oldColumnName": "PERMIT_TYPE", "newColumnName": "permit_type"},
{"op": "core/column-rename", "oldColumnName": "ADDRESS", "newColumnName": "address"},
{"op": "core/column-rename", "oldColumnName": "ESTIMATED_VALUE", "newColumnName": "estimated_value"},
{"op": "core/text-transform", "columnName": "address", "expression": "value.trim().replaceAll(\"\\\\s+\", \" \")"},
{"op": "core/text-transform", "columnName": "estimated_value", "expression": "toNumber(value.replace(\"$\",\"\").replaceAll(\",\",\"\").trim())"},
{"op": "core/text-transform", "columnName": "application_date", "expression": "if(isNonBlank(value), toString(toDate(value.trim(), \"MM/dd/yyyy\"), \"yyyy-MM-dd\"), \"\")"},
{"op": "core/text-transform", "columnName": "issue_date", "expression": "if(isNonBlank(value), toString(toDate(value.trim(), \"MM/dd/yyyy\"), \"yyyy-MM-dd\"), \"\")"}
]
六、GREL 表达式速查表
6.1 完整函数参考
| 分类 | 函数 | 说明 | 示例 |
|---|---|---|---|
| 字符串 | value.length() |
长度 | "abc".length() → 3 |
| 字符串 | value.contains(x) |
包含 | "hello".contains("ell") → true |
| 字符串 | value.startsWith(x) |
前缀 | "hello".startsWith("he") → true |
| 字符串 | value.endsWith(x) |
后缀 | "hello".endsWith("lo") → true |
| 字符串 | value.indexOf(x) |
查找位置 | "hello".indexOf("l") → 2 |
| 字符串 | value.lastIndexOf(x) |
末次位置 | "hello".lastIndexOf("l") → 3 |
| 字符串 | value.substring(a, b) |
截取 | "hello".substring(1, 3) → “el” |
| 字符串 | value.replace(a, b) |
替换首 | "aabb".replace("a", "c") → “cabb” |
| 字符串 | value.replaceAll(a, b) |
正则替换 | "a1b2".replaceAll("\\d", "") → “ab” |
| 字符串 | value.split(d) |
拆分 | "a,b,c".split(",") → [“a”,“b”,“c”] |
| 字符串 | value.trim() |
去空白 | " hi ".trim() → “hi” |
| 字符串 | value.toLowercase() |
转小写 | "HI".toLowercase() → “hi” |
| 字符串 | value.toUppercase() |
转大写 | "hi".toUppercase() → “HI” |
| 字符串 | value.fingerprint() |
指纹 | 标准化后比较 |
| 字符串 | value.match(regex) |
正则匹配 | "abc123".match("\\d+") → [“123”] |
| 数值 | toNumber(x) |
转数值 | toNumber("3.14") → 3.14 |
| 数值 | toString(x) |
转字符串 | toString(123) → “123” |
| 数值 | round(x, n) |
四舍五入 | round(3.146, 2) → 3.15 |
| 数值 | floor(x) |
向下取整 | floor(3.7) → 3 |
| 数值 | ceil(x) |
向上取整 | ceil(3.2) → 4 |
| 数值 | abs(x) |
绝对值 | abs(-5) → 5 |
| 日期 | toDate(s, f) |
转日期 | toDate("2024-01-15", "yyyy-MM-dd") |
| 日期 | toString(d, f) |
格式化 | toString(d, "yyyy年MM月dd日") |
| 日期 | date.year() |
年份 | d.year() → 2024 |
| 日期 | date.month() |
月份 | d.month() → 0 (0-11) |
| 日期 | date.day() |
日 | d.day() → 15 |
| 日期 | dateDiff(d1, d2, u) |
日期差 | dateDiff(d1, d2, "days") |
| 逻辑 | if(c, t, f) |
条件 | if(v>10, "高", "低") |
| 逻辑 | with(x, var, expr) |
局部变量 | with(v*2, x, x+1) |
| 逻辑 | isNull(x) |
空判断 | isNull(value) |
| 逻辑 | isBlank(x) |
空白判断 | isBlank(value) |
| 逻辑 | isNonBlank(x) |
非空判断 | isNonBlank(value) |
| 逻辑 | isNumeric(x) |
数值判断 | isNumeric(value) |
| 数组 | arr.length() |
长度 | ["a","b"].length() → 2 |
| 数组 | arr.join(d) |
连接 | ["a","b"].join(",") → “a,b” |
| 数组 | arr.contains(x) |
包含 | ["a","b"].contains("a") → true |
| 数组 | arr.sort() |
排序 | ["c","a","b"].sort() → [“a”,“b”,“c”] |
| 数组 | arr.reverse() |
反转 | ["a","b"].reverse() → [“b”,“a”] |
| 数组 | arr.uniques() |
去重 | ["a","a","b"].uniques() → [“a”,“b”] |
| 数组 | arr.forEach(v, expr) |
遍历 | 每个元素执行 expr |
| 数组 | arr.filter(v, cond) |
过滤 | 保留满足条件的元素 |
| 数组 | arr.cross(proj, c) |
交叉 | 跨项目数据关联 |
七、本章小结
知识点回顾:
├── 1. OpenRefine 是基于浏览器的开源数据清理工具
├── 2. 支持 CSV、Excel、JSON、XML 等多种格式
├── 3. 基本操作:创建项目、操作列、撤销/重做、导出
├── 4. 核心进阶功能:
│ ├── 排序(单列/多列,text/number/date)
│ ├── 分面(Text/Number/Custom/Timeline)
│ ├── 聚类(Key Collision / Nearest Neighbor)
│ ├── 文本过滤(支持正则表达式)
│ ├── 数据填充(Fill down / GREL 填充)
│ └── 数据转换(GREL/Jython/Clojure 表达式)
├── 5. GREL 表达式是 OpenRefine 的核心技能
│ ├── 字符串函数:length, contains, replace, split, trim...
│ ├── 数值函数:toNumber, round, floor, ceil, abs...
│ ├── 日期函数:toDate, toString, year, month, dateDiff...
│ ├── 逻辑控制:if, with, isNull, isBlank...
│ └── 数组操作:join, sort, uniques, forEach, filter...
├── 6. 操作历史可导出为 JSON,实现清理流程的自动化复用
└── 7. 实战案例:通过完整的多伦多建筑许可数据清理
展示了从导入→清洗→分析→导出的全流程
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)