清理和格式化文本
如何使用自定义表达式清理不一致、非结构化或空白的文本。
假设 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. | |
我们想要
- 清理并合并主菜和配菜的值,以便我们可以计算最受欢迎的菜肴。
- 处理在某种程度上无效的回复(例如,多个主菜,或菜单上不存在的主菜)。
- 跟踪提交了缺少信息的回复的客人。
总的来说,我们希望最终得到一个如下所示的表格(向右滚动以查看完整表格)
| 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
函数来检查每个回复中是否有有效的菜单选项。
为了在“主菜”列中搜索值“beef tibs”,我们将使用正则表达式模式 (?i)(beef tibs)
创建一个自定义列。此正则表达式模式执行不区分大小写的检查,以查看“beef tibs”是否出现在回复中的任何位置。
使用以下内容创建“牛肉”自定义列
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”。
创建“鹰嘴豆”列
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. | |
合并来自不同列的值
接下来,我们将创建一个名为“主菜(已清理)”的列,该列将合并每位客人回复的有效主菜。 我们想要设置一些逻辑,以便如果“主菜”包含
- 单个有效选项(牛肉提布斯或鹰嘴豆炖菜),然后用该选项填充“主菜(已清理)”。
- 多个有效选项,然后将第一个(最左边的)有效选项放入“主菜(已清理)”中。
- 没有有效选项,然后用空白值(空字符串)填充“主菜(已清理)”。
为了创建“主菜(已清理)”,我们将使用 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. | | | |
提取文本并合并结果
我们将以与“主菜”列相同的方式处理“配菜”列。首先,使用 regexextract
函数搜索并从“配菜”列返回有效值。
创建“英吉拉”自定义列
regexextract([Side], "(?i)injera")
和“蔬菜”自定义列
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
函数一起使用,以处理人们放置了部分、多个或没有有效配菜选项的情况,并将所有值转换为小写
创建“配菜(已清理)”自定义列
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 | | | | |
合并来自不同列的值
最后,我们想通过检查每种情况来生成完整订单
- 如果“主菜(已清理)”和“配菜(已清理)”都包含有效选项,则返回“主菜配配菜”。
- 如果只有一个有效选项,则返回“仅主菜”或“仅配菜”。
- 如果没有有效选项,则将订单留空(返回一个空字符串)。
为了检查列是否为空,我们将使用 isempty
函数。
例如,要检查“主菜(已清理)”是否为空
isempty([Main (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
函数按顺序评估每个表达式,并在找到第一个有效案例后立即停止。- 如果您将第一个案例与第二个案例交换,则表达式将确认“配菜(已清理)”为空并立即返回“仅主菜”,而无需检查“主菜(已清理)”是否也为空。
最后,为了填写每位客人的最终订单,我们将使用 concat
函数将“主菜(已清理)”和“配菜(已清理)”的值与其他单词(包括空格)链接起来。
使用以下内容创建“订单”列
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. | | | | |
标记具有空白值的行
假设我们想添加一个名为“跟进?”的列,以跟踪缺少有效主菜、配菜或两者的订单。这意味着我们需要检查“订单”、“主菜(已清理)”或“配菜(已清理)”中是否有任何一项为空。
我们可以将 isempty
函数与 OR
运算符结合使用,如果这三列中的任何一列为空,则返回“是”,如果所有列都填充了有效数据,则返回“否”。
使用以下内容创建“跟进?”
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 |
最佳实践和提示
在本教程中,每次我们需要提取、组合或标记我们的文本数据时,我们都会创建一个新的自定义列。我们还将更简单的函数(例如 lower
和 isempty
)与其他函数组合在一起。一般来说,我们建议每次使用带有多个参数的函数(如 case
、regexextract
和 coalesce
)时都创建一个新的自定义列,因为
- 您可以确认您的表达式是否按预期工作。
- 逻辑更易于阅读和更新。
而且,如果您习惯于在其他工具(如 SQL、电子表格或 Python)中使用函数,请查看自定义表达式文档中的“相关函数”部分。 例如,您可以学习如何使用 case
的相关函数将 if-then 逻辑转换为 Metabase 表达式。