教程:清理和格式化文本

如何使用自定义表达式来清理不一致、非结构化或空白的文本。

假设 Metabase 想为我们可爱的社区举办一场晚宴。主菜方面,我们有牛排或鹰嘴豆炖菜两种选择,配菜方面,我们有英杰拉或烤蔬菜。我们已经发送了一份包含菜单选项的调查问卷,以便每个人都能告诉我们他们想吃什么。

不幸的是,我们在表单中忘记设置数据验证,所以回复是这样的

| Response ID | Main                                | Side                    |
|-------------|-------------------------------------|-------------------------|
| 1           | beef tibs                           | injera                  |
| 2           | chickpea stew                       | grilled vegetables      |
| 3           | BEEF TIBS WITH CHICKPEA STEW PLEASE |                         |
| 4           |                                     | Grilled Vegetables      |
| 5           | Surprise me.                        |                         |

我们想要

  1. 清理和合并主菜和配菜的值,以便我们可以统计最受欢迎的餐点。
  2. 处理无效的回复(例如,多道主菜,或菜单上不存在的主菜)。
  3. 跟踪那些提交了信息缺失回复的客人。

总的来说,我们希望最终得到一个如下表所示的表格(向右滚动查看完整表格)

| Response ID | Main                                | Side                    | Order                                 | Follow up? |
|-------------|-------------------------------------|-------------------------|---------------------------------------|------------|
| 1           | beef tibs                           | injera                  | beef tibs with injera                 | no         |
| 2           | chickpea stew                       | grilled vegetables      | chickpea stew with grilled vegetables | no         |
| 3           | BEEF TIBS WITH CHICKPEA STEW PLEASE |                         | beef tibs only                        | yes        |
| 4           |                                     | Grilled Vegetables      | grilled vegetables only               | yes        |
| 5           | Surprise me.                        |                         |                                       | yes        |

搜索和提取文本

假设唯一有效的主菜选项是牛排和鹰嘴豆炖菜。我们可以使用 regexextract 函数来检查每个回复中的有效菜单选项。

要搜索 **主菜** 列中的“牛排”值,我们将创建一个 自定义列,其正则表达式模式为 (?i)(beef tibs)。此正则表达式模式会进行不区分大小写的检查,以确定“beef tibs”是否出现在回复中的任何位置。

使用以下命令创建 **Beef** 自定义列

regexextract([Main], "(?i)(beef tibs)")

你应该得到如下输出

| Response ID | Main                                | Beef      |
|-------------|-------------------------------------|-----------|
| 1           | beef tibs                           | beef tibs |
| 2           | chickpea stew                       |           |
| 3           | BEEF TIBS WITH CHICKPEA STEW PLEASE | BEEF TIBS |
| 4           |                                     |           |
| 5           | Surprise me.                        |           |

然后,我们要搜索 **主菜** 列中的有效值“chickpea stew”。

创建 **Chickpea** 列

regexextract([Main], "(?i)(chickpea stew)")

得到如下输出

| Response ID | Main                                | Chickpea      |
|-------------|-------------------------------------|---------------|
| 1           | beef tibs                           |               |
| 2           | chickpea stew                       | chickpea stew |
| 3           | BEEF TIBS WITH CHICKPEA STEW PLEASE | CHICKPEA STEW |
| 4           |                                     |               |
| 5           | Surprise me.                        |               |

合并不同列中的值

接下来,我们将创建一个名为 **Main (Clean)** 的列,用于整合每位客人回复中的有效主菜。我们希望设置一些逻辑,以便如果 **Main** 包含

  • 一个有效选项(牛排或鹰嘴豆炖菜),则将 **Main (Clean)** 填为此选项。
  • 多个有效选项,则将第一个(最左边)有效选项填入 **Main (Clean)**。
  • 没有有效选项,则将 **Main (Clean)** 填为空白值(空字符串)。

为了创建 **Main (Clean)**,我们将使用 coalesce 函数来处理上述三种情况,并将整个表达式用 lower 函数包裹起来,以标准化所有内容为小写。

lower(coalesce([Beef],[Chickpea],""))

这将得到如下输出(向右滚动查看完整表格)

| Response ID | Main                                | Beef      | Chickpea      | Main (Clean)   |
|-------------|-------------------------------------|-----------|---------------|----------------|
| 1           | beef tibs                           | beef tibs |               | beef tibs      |
| 2           | chickpea stew                       |           | chickpea stew | chickpea stew  |
| 3           | BEEF TIBS WITH CHICKPEA STEW PLEASE | BEEF TIBS | CHICKPEA STEW | beef tibs      |
| 4           |                                     |           |               |                |
| 5           | Surprise me.                        |           |               |                |

提取文本并合并结果

我们将以与 **Main** 列相同的方式处理 **Side** 列。首先,使用 regexextract 函数来搜索并返回 **Side** 列中的有效值。

创建 **Injera** 自定义列

regexextract([Side], "(?i)injera")

创建 **Vegetables** 自定义列

regexextract([Side], "(?i)(grilled vegetables)")

得到如下输出

| Response ID | Side               | Injera | Vegetables         |
|-------------|--------------------|--------|--------------------|
| 1           | injera             | injera |                    |
| 2           | grilled vegetables |        | grilled vegetables |
| 3           |                    |        |                    |
| 4           | Grilled Vegetables |        | Grilled Vegetables |
| 5           |                    |        |                    |

然后,使用 coalesce 函数lower 函数来处理人们输入了部分、多个或无效的配菜选项的情况,并将所有值转换为小写

创建 **Side (Clean)** 自定义列

lower(coalesce([Injera],[Vegetables], ""))

得到

| Response ID | Side               | Injera | Vegetables         | Side (Clean)       |
|-------------|--------------------|--------|--------------------|--------------------|
| 1           | injera             | injera |                    | injera             |
| 2           | grilled vegetables |        | grilled vegetables | grilled vegetables |
| 3           |                    |        |                    |                    |
| 4           | Grilled Vegetables |        | Grilled Vegetables | grilled vegetables |
| 5           |                    |        |                    |                    |

合并不同列中的值

最后,我们将通过检查每种情况来生成完整的订单

  • 如果 **Main (Clean)** 和 **Side (Clean)** 都包含一个有效选项,则返回“main with side”。
  • 如果只有一个有效选项,则返回“main only”或“side only”。
  • 如果没有有效选项,则将订单留空(返回空字符串)。

要检查一个列是否为空,我们将使用 isempty 函数

例如,检查 **Main (Clean)** 是否为空

isempty([Main (Clean)])

要检查 **Main (Clean)** 和 **Side (Clean)** 是否 *都* 为空,你可以使用 AND 来组合表达式

isempty([Main (Clean)]) AND isempty([Side (Clean)])

isempty 目前 只能在另一个函数中使用,所以我们需要将我们的每个检查都放在一个 case 函数中。我们暂时放一些占位符文本作为输出

case(
    (isempty([Main (Clean)]) AND isempty([Side (Clean)])), "",
    isempty([Side (Clean)]), "main only",
    isempty([Main (Clean)]), "side only",
    "main with side"
)

请注意,case 的顺序很重要,因为

  • case 函数按顺序评估每个表达式,并在找到 *第一个* 有效 case 后停止。
  • 如果你将第一个 case 与第二个 case 交换,表达式将确认 **Side (Clean)** 为空,并立即返回“main only”,而不会检查 **Main (Clean)** 是否也为空。

最后,为了填充每位客人的最终订单,我们将使用 concat 函数将 **Main (Clean)** 和 **Side (Clean)** 的值与其他词语(包括空格)链接起来。

使用以下命令创建 **Order** 列

case(
    (isempty([Main (Clean)]) AND isempty([Side (Clean)])), "",
    isempty([Side (Clean)]), concat([Main (Clean)], " only"),
    isempty([Main (Clean)]), concat([Side (Clean)], " only"),
    concat([Main (Clean)], " with ", [Side (Clean)])
)

总的来说,这将为我们提供一组格式化的列,如下所示(向右滚动查看完整表格)

| Response ID | Main                                | Side               | Main (Clean)    | Side (Clean)       | Order                                 |
|-------------|-------------------------------------|--------------------|-----------------|--------------------|---------------------------------------|
| 1           | beef tibs                           | injera             | beef tibs       | injera             | beef tibs with injera                 |
| 2           | chickpea stew                       | grilled vegetables | chickpea stew   | grilled vegetables | chickpea stew with grilled vegetables |
| 3           | BEEF TIBS WITH CHICKPEA STEW PLEASE |                    | beef tibs       |                    | beef tibs only                        |
| 4           |                                     | Grilled Vegetables |                 | grilled vegetables | grilled vegetables only               |
| 5           | Surprise me.                        |                    |                 |                    |                                       |

标记空白值的行

假设我们想添加一个名为 **Follow up?** 的列来跟踪缺少有效主菜、配菜或两者都缺的订单。这意味着我们需要检查 **Order**、**Main (Clean)** 或 **Side (Clean)** 是否为空。

我们可以结合使用 isempty 函数OR 运算符,如果三个列中的任何一个为空,则返回“yes”,如果所有列都填满了有效数据,则返回“no”。

使用以下命令创建 **Follow up?**

case(( isempty([Order])
    OR isempty([Main (Clean)])
    OR isempty([Side (Clean)])), "yes", "no")

最终结果(向右滚动查看完整表格)

| Response ID | Main                                | Side                    | Order                                 | Follow up? |
|-------------|-------------------------------------|-------------------------|---------------------------------------|------------|
| 1           | beef tibs                           | injera                  | beef tibs with injera                 | no         |
| 2           | chickpea stew                       | grilled vegetables      | chickpea stew with grilled vegetables | no         |
| 3           | BEEF TIBS WITH CHICKPEA STEW PLEASE |                         | beef tibs                             | yes        |
| 4           |                                     | vegetables              | grilled vegetables                    | yes        |
| 5           | Surprise me.                        |                         |                                       | yes        |

最佳实践和技巧

在本教程中,我们每次需要提取、合并或标记文本数据时都创建了一个新的自定义列。我们也组合了更简单的函数(例如 lowerisempty)与其他函数。总的来说,我们建议每次使用具有多个参数的函数(例如 caseregexextractcoalesce)时都创建一个新的自定义列,因为

  • 你可以确认你的表达式是否按预期工作。
  • 逻辑更容易阅读和更新。

而且,如果你习惯于使用其他工具中的函数,例如 SQL、电子表格或 Python,请查看自定义表达式文档中的 **相关函数** 部分。例如,你可以学习如何使用 case 相关的函数将 if-then 逻辑转换为 Metabase 表达式。

延伸阅读

这有帮助吗?

感谢您的反馈!
订阅新闻通讯
Metabase 的更新和新闻
© . This site is unofficial and not affiliated with Metabase, Inc.