跳至正文

BI+AI:基于 Python 的数据清理、处理的 Tableau大数据分析

XILEJUN
喜乐君 Tableau Visionary ✦ 5
📊 业务数据分析「专家」· 敏捷 BI 布道师
📚 《数据可视化分析》《业务可视化分析》多本书作者
🎓 中国地质大学(武汉)经管学院 MBA 校外导师
🤝 以 Tableau 会友,致力于构建业务分析通识框架

📚 本文配套课程 · 数据可视化分析系列

🎬 B 站课程:数据可视化分析:Tableau/SQL 原理与实践  —  https://www.bilibili.com/cheese/play/ss8093


2025-04-17 初稿 ;借助于 GPT 完成 (23:00~00:22)
2025-04-18 终稿;补充 Tableau 群集,扩展高级场景 (19:30~21:00)

作为一名非 IT 用户,过去多年我一直对 Python 讳莫如深;不过,随着我的业务分析日渐深入,随着业务场景复杂化,一些分析场景必然超过了 Tableau 的“能力圈”,其中的典型是规划求解和机器学习等。

比如,我近期的供应链客户在尝试使用 AI 优化生产工单排产(业内称之为 MRP 物料需求运算),由于涉及到多要素输入、多约束条件、多种排产可能性,不得不引入机器学习内容方可求解。为此,我准备在 AI 的帮助下,开启这个多年来回避的内容。

如何使用 Tableau + Python,完成“Tableau 能力圈”之外的计算并展现。

一、关于 Tableau 和 Python 的桥梁:Tabpy

Tableau 调用 Python 需要一个桥梁,那就是 Tabpy,按照官方介绍:

TabPy (the Tableau Python Server) is an Analytics Extension implementation which expands Tableau’s capabilities by allowing users to execute Python scripts and saved functions via Tableau’s table calculations.

TabPy(Tableau Python 服务器)是一种分析扩展实现,通过允许用户通过 Tableau 的表计算执行 Python 脚本和保存的函数,扩展了 Tableau 的功能。

早在2017年左右,Tableau Desktop 就支持Tabpy 扩展。使用多个函数SCRIPT_INT, SCRIPT_REAL, SCRIPT_STR and SCRIPT_BOOL 返回计算结果,而后直接在视图中处理。

在2022.1版本中,Tableau Desktop 推出了一个更强大的 Table Extensions 功能,它把 tabpy 的能力从视图函数扩展到数据源阶段;相比 Script 函数,“table extensions”实现了将 Python 处理返回为表(table),相当于 dataframe,从而实力大增。

特点Script 函数Table Extension
使用方式在计算字段中嵌入脚本以表的形式直接引入
结果类型单列(标量)多列(表)
场景简单脚本调用复杂计算或外部系统对接

于是,Tableau+Python 结合就能完成更复杂的内容:

  1. 高级计算与模型预测利用 Python(通过 TabPy)、R 或其他脚本语言,在 Tableau 中运行机器学习模型、预测分析、文本分析等。
  2. 自定义业务逻辑实现 Tableau 本身不支持的业务规则计算,例如复杂的评分卡、风险评级、审批规则等。
  3. 动态数据清洗与转换在 Tableau 中动态调用外部服务完成清洗逻辑,例如正则表达式处理、字符串匹配、特征提取等。
  4. 调用外部数据库或 API可通过扩展在分析过程中对接外部系统,比如调用实时库存、天气信息、汇率等动态数据。

二、部署 Tabpy 服务(简)

Tableau 中使用 Python 依赖于 Tabpy 服务,可以把它视为 Python中类似于 pandas 的“功能包”。

安装 Tabpy 方式非常简单,只需要 pip install 即可完成(pip install tabpy)。

安装之后,执行 Tabpy 即可运行。

注意:Mac 环境默认的 Python 是2.*版本,我这里同时安装了新版本 3.*,需要使用 pip3 安装。

而后在 Tableau Desktop 帮助中,找到“管理分析扩展程序连接”,点击“测试连接”即可确认。

如果要把分析结果发布到 Server 上并实现相同效果,这里还需要在 Tableau Server 中,也可以配置 TabPy 连接,从而使服务器具备分析扩展能力。

三、使用TabPy构建 Table extensions

在配置好环境之后,接下来我介绍一个简要示例。

1、简单计算示例:函数的平替测试

先说一个简单示例,这个示例可以在 Tableau 用函数计算完成,这里仅仅用来说明逻辑。

这里提供一个完整的演示过程:

user_iduser_namecredit_score
U001张三780
U002李四640
U003王五520

使用tabpy 增加一列标签,这里使用 if-else-end 判断:

import pandas as pd

# 从 Tableau 接收数据
df = pd.DataFrame(_arg1)   ##_arg1是从Tableau中传递到python的数据表

# 风险等级分类函数
def label(score):
    if score >= 750:
        return '低风险'
    elif score >= 600:
        return '中风险'
    else:
        return '高风险'

# 应用风险等级逻辑
df['risk_level'] = df['credit_score'].apply(label)

# 返回所有字段,含新字段 risk_level
return df.to_dict(orient='list')

如下所示,增加一列的结果如下:

user_iduser_namecredit_scorerisk_level
U001张三780低风险
U002李四640中风险
U003王五520高风险

关键问题:在实际使用 Table Extensions 时,如果只返回部分字段(如仅 risk_level),将无法与原始数据关联,所以要么保留主键从而关联,要么直接用 tabpy 结果作为主表。

2、高级案例:使用 Python 完成本文聚类

上述简单的逻辑判断显然不需要 tabpy 中的 Python 能力,一些高级场景才需要。

这里介绍一个聚类场景(当然,Tableau Desktop 也有对应功能,稍等说明)。

在笔者的客户项目中,有如下的“来料检验退货明细”,每一次检验都会标记退货原因RejectionComment。如下所示,假设我想把杂乱无章的RejectionComment按照语义上的相近特征,划分为5个组;这属于文本归类(Text Classification)或语义聚类问题,这就需要“聚类分析”(Cluster)。

喜乐君说:Cluster 在中文中有很多翻译,典型的有“簇”“聚类”“群集”等,表示具有相同特征的集合。

在 Tableau 中借助 Table Extension(Python + NLP) ,使用一个高级版本:使用中文分词(如 jieba)+ TF-IDF 聚类方式,可以将RejectionComment 划分为5个组。

1)使用 Table Extension(Python + NLP)

在 Tableau Desktop 中,先拖入 New Table Extensions,然后使用自定义 SQL构建底表,再在右侧的窗口中输入 Python 代码并 Apply 即可。

在这里,我使用了中文分词、聚类方式,增加了一个新字段 cluster,最终返回[‘id’, ‘RejectionCode’, ‘RejectionComment’, ‘CommentCluster’]的数据表。

## 注意,这个仅仅是第一个版本的代码,后面会升级为准确的代码。
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
import jieba

# 将 Tableau 传入的数据转换为 DataFrame
df = pd.DataFrame(_arg1)

# 中文分词处理
df['RejectionComment'] = df['RejectionComment'].fillna("").astype(str)
corpus = [" ".join(jieba.lcut(text)) for text in df['RejectionComment']]

# TF-IDF 特征提取
vectorizer = TfidfVectorizer(max_features=100)
X = vectorizer.fit_transform(corpus)

# KMeans 聚类
n_clusters = 5  # 可根据实际需求调整
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init='auto')
df['CommentCluster'] = kmeans.fit_predict(X)

# 返回结果,必须是 dict,字段名要与 Tableau 中字段一致
return df[['id', 'RejectionCode', 'RejectionComment', 'CommentCluster']].to_dict(orient='list')

在技术领域,[‘id’, ‘RejectionCode’, ‘RejectionComment’, ‘CommentCluster’]的专有名词是数组,这里表示数据表的标题。

完成的Output 数据表如下所示,这里出现了几个问题:1)原来的 RejectionQuantity 字段丢失了;2)(最重要的)键字段 id 和 parentid 字段由于太长,在传递到 Python 中之后没有正确返回。因此需要修正。

借助于 GPT 的帮助,在尝试了很多次之后(大约耗费一刻钟),终于给出了满足需求的答案。

GPT:从数据源传入 Python 脚本时,长整型数值(如 id, ParentId)在超出 JavaScript 可安全整数范围(即 ±2⁵³ ≈ ±9.007×10¹⁵)时,可能在传输或 JSON 序列化过程中丢失精度。因此,为了完全避免数据精度问题,应将这些字段转为字符串处理和返回,以实现数据的“长文本化”安全传递。

import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
import jieba

# 将 Tableau 传入的数据转换为 DataFrame
df = pd.DataFrame(_arg1)

# 转换整数为字符串,确保长整型不丢失精度
df['id'] = df['id'].astype(str)
df['ParentId'] = df['ParentId'].astype(str)

# 数值字段 RejectedQuantity 和 RejectionCode 保留原类型
df['RejectedQuantity'] = pd.to_numeric(df['RejectedQuantity'], errors='coerce')
df['RejectionCode'] = pd.to_numeric(df['RejectionCode'], errors='coerce').astype('Int64')

# 中文分词与聚类处理
df['RejectionComment'] = df['RejectionComment'].fillna("").astype(str)
corpus = [" ".join(jieba.lcut(text)) for text in df['RejectionComment']]

# TF-IDF 特征提取
vectorizer = TfidfVectorizer(max_features=100)
X = vectorizer.fit_transform(corpus)

# KMeans 聚类
n_clusters = 5
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init='auto')
df['CommentCluster'] = kmeans.fit_predict(X)

# 返回所有字段(保留精度,含聚类)
return df.to_dict(orient='list')

基于新的字段,就可以构建可视化图形了。

看上去有些凌乱,可以尝试换一下 marks 中的字段位置,如下所示:

在上图中,我突出了 Cluster= 4 的这个“簇”,借助于comment 内容可以看出,它们大多与“尺寸”问题相关。这就是基本文本关键词的处理逻辑。

 扩展:中文分词 和 特征提取(TF-IDF 向量化) 的基本原理和处理流程

中文不像英文有空格分隔单词,因此在进行文本分析(如聚类、分类)前,需要先将连续的中文句子拆分成词语(分词)。Jieba 是一个优秀的中文分词库,通过基于词频词典 + HMM(隐马尔可夫模型) 的方法,对句子进行分词。

## 输入
import jieba

text = "注胶口毛边,比例约10%。"
words = jieba.lcut(text)
print(words)

## 输出
['注胶口', '毛边', ',', '比例', '约', '10', '%。']

将文本拆分为单词之后,就可以根据词频分类,这就是TF-IDF 方法( Inverse Document Frequency,逆文档频率)。这里TF = Term Frequency,词频,如果某个词在当前文本中频繁出现,就可以作为关键词。

三、对比学习 Tableau Desktop 原生的群集功能

这里,喜乐君还有要强调 Python 返回的文本簇和 Desktop 自带的“群集”的差别。

也许会有人以为,Tableau 原本就有 Cluster 功能,大可不必使用 Python 处理——我原本也有这样的怀疑。事实上,我们不能用 Tableau 原生功能完成一切。

这里先用原生的群集功能绘图如下:

在点图的基础上,拖入 Cluster,即可快速创建群集!

但是,务必注意的是,Tableau 默认的群集是以视图中的聚合字段为变量依据的,如图中的“CNT(Table extensions)”(即数据表行数)字段。

那能否将“RejectionComment”拖入变量中,按照文本意思分类呢?

答案是不可以,基于文本内容提取关键词并分组,这个已经超过了 Tableau 原生的能力。这就是Python 的魅力。

四、更进一步:取长补短

假设我们想要结合 Python 和 Tableau 的优势,既有提起 comment 中的关键词并分类,又要将其作为颜色分类突出展示,此时仅仅有 Cluster 的1、2、3、4……数字就不够了。

可以在 Python 代码中,要求新增一个 Category 的数据列,和 Cluster 对应使用。

Q:好的,既然使用的“词频”,那能否按照 每个 comment 中的 Term,将它们重新分组。 

假设30个 RejectionComment 中都包含“尺寸” 这个 Term,那么就可以将它们划分到“尺寸”的分组中,并生成一个新字段 RejectCommentCategory。

按照这个逻辑,将词频超过20的划分为一个分组,其他的表示为“其他”。

借助于 GPT 帮助,可以完成如下的 Python 处理逻辑。有几个关键:

1)将超长的 id 转化为 String,避免传递中丢失

2)文本中应该删除中英文标点符号和空格(否则逗号、空格也会作为一类)

如下所示:

import pandas as pd
import jieba
import re
from collections import Counter

# 从 Tableau 接收数据
df = pd.DataFrame(_arg1)

# 强制将长整型字段转换为字符串,避免精度丢失
df['id'] = df['id'].astype(str)
df['ParentId'] = df['ParentId'].astype(str)

# 清洗文本:移除中英文标点 + 空格
def clean_text(text):
    if not isinstance(text, str):
        return ""
    # 移除中文标点、英文标点、空格和特殊符号
    text = re.sub(r'[\s+\.\!\/_,$%^*+\"\'“”‘’《》<>【】#¥%……&*()。,、!?;;:()\-]+', '', text)
    return text

df['RejectionComment'] = df['RejectionComment'].fillna("").astype(str).apply(clean_text)

# 分词
df['Tokens'] = df['RejectionComment'].apply(lambda x: jieba.lcut(x))

# 统计词频
all_tokens = [token for tokens in df['Tokens'] for token in tokens]
token_freq = Counter(all_tokens)

# 提取词频超过 20 的高频词
high_freq_terms = {term for term, freq in token_freq.items() if freq > 20}

# 定义分类函数
def classify_comment(tokens):
    matched = [term for term in tokens if term in high_freq_terms]
    return matched[0] if matched else '其他'

# 应用分类
df['RejectCommentCategory'] = df['Tokens'].apply(classify_comment)

# 删除中间列
df.drop(columns=['Tokens'], inplace=True)

# 返回所有字段
return df.to_dict(orient='list')

在这之前,我发现了一个问题,在我的提示词下,GPT 已经修正:标点符号误判为高频词。中文标点(如,。!)和英文标点(如,、.)会被 jieba.lcut() 保留下来,导致它们也被统计为高频词。

在这个基础上,就可以使用 Tableau 完成如下的分类:

很明显,这里还有一些AI 不能理解的问题,比如“不”和“不符”,显然不应该作为关键词。还可以据此做一些更好地清理。

参考文献: