数据清理工具—— 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 &amp; Jerry"

# JavaScript 转义
value.escape("javascript")

# URL 编码
value.escape("url")
# "hello world" → "hello%20world"

value.unescape("format") — 反转义

# HTML 反转义
value.unescape("html")
# "Tom &amp; 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. 实战案例:通过完整的多伦多建筑许可数据清理
      展示了从导入→清洗→分析→导出的全流程
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐