洞悉客户心声:Pandas标签帮你透视客户,标签化营销如虎添翼

在构建高效的数据分析体系时,我们经常会遇到两个核心概念:指标(Metrics)标签(Tags) 。指标是对业务性能的量化衡量,它们帮助我们追踪关键业务目标的达成情况。例如,我们可能会关注用户增长率、产品销售额或客户的增续投等指标。这些数字目标为我们提供了业务运行的直观快照,并允许我们对成功与否进行量化评估。

而标签,则是附加在指标上的文字描述,用于对数据进行分类和上下文化。它们像是数据的属性,使我们能够从不同的角度切入,对指标进行深入分析。例如,对于销售额这一指标,我们可以使用"地区"、"产品线"、"客户群体"等标签来细分市场表现。通过这种方式,我们不仅能够了解整体的销售情况,还能够洞察到哪些产品在特定区域或客户群体中最受欢迎,从而做出更加精细化的业务决策。

在实际应用中,指标和标签的关系可以类比于坐标系中的点和坐标轴。指标代表了我们要追踪的具体数值,而标签则定义了坐标系中的各个维度,帮助我们定位和解释这些数值。没有标签,我们只能得到一个孤立的数字;而有了标签,我们就能够将这个数字放置在一个多维度的空间中,理解其背后的含义和价值。

客户标签不仅是数据价值体现的关键途径,更是我们今天讨论的核心所在。那在数据仓库中,通过分层、归类、建模会计算出一系列的指标,而标签则可以利用pandas将指标转化为对应的标签。

上篇已经讲解了pandas的几个方法,很多朋友好奇学会了,适用的场景在哪呢,这就听我娓娓道来。

先打个底:以理解为主,不够严谨,如果看完还是不会,那一定是我讲的不够好。我会尽可能对相对复杂的代码加入注释,欢迎大家评论区相互交流啊。

一、对类别型指标进行值的替换

该函数用于对类别型指标进行值的替换,举个例子,假设用户的星座对应的字典如下:
"cnstll": {"白羊座":"11","金牛座":"12","双子座":"13","巨蟹座":"14","狮子座":"15","处女座":"16", "天枰座":"17","天蝎座":"18","射手座":"19","摩羯座":"20","水瓶座":"21","双鱼座":"22","其他":"99","NULL":"99"},

python 复制代码
import pandas as pd

def cat_process(df, cat_dict):
    '''
    该函数用于对类别型指标进行值的替换,其中:
    df      : dataframe,传入待处理的dateframe,必须包括待替换的指标列
    cat_dict: dict类型,key代表待替换的指标名称,value代表用于替换的一一对应的值的关系词典
            关系词典中必须包含"其他"和"NULL"两个key。
            原数据中没有"其他"的在"其他"中填入和"NULL"key对应的相同的value

    return df:传入的df中在cat_dict中存在的指标的值已被替换成为对应标签的值
    '''
    for key,cat_val in cat_dict.items():
        # df[key] 取某一列的值   df[key].map(cat_val) 根据这个字典或函数对 Series(索引 + 值) 中的每个元素进行映射或转换
        df[key] = df[key].map(cat_val)
        # 将空值替换成其他,inplace = True: 表示对 DataFrame 进行原地修改,即不创建新的副本
        df[key].fillna(cat_val['其他'], inplace=True)
        # 将数据类型转换为int64
        df[key] = df[key].astype('int64')
    return df


# 创建测试数据
data = {
    '姓名': ['张三', '李四', '王五', '赵六', '钱七'],
    '星座': ['狮子座', '天蝎座', '双子座', '摩羯座', '水瓶座']
}

df = pd.DataFrame(data)

# 星座的字典
cat_dict = {
    "星座": {
        "白羊座": "11",
        "金牛座": "12",
        "双子座": "13",
        "巨蟹座": "14",
        "狮子座": "15",
        "处女座": "16",
        "天枰座": "17",
        "天蝎座": "18",
        "射手座": "19",
        "摩羯座": "20",
        "水瓶座": "21",
        "双鱼座": "22",
        "其他": "99",
        "NULL": "99"
    }
}

# 调用 cat_process 函数
processed_df = cat_process(df, cat_dict)

# 打印处理后的 DataFrame
print(processed_df)

运行结果

复制代码
   姓名  星座
0  张三  15
1  李四  18
2  王五  13
3  赵六  20
4  钱七  21

可能会有人提出疑问:为什么要经历这样的转换过程?将中文值转换为数字岂非增加了复杂性?然而事实并非如此。采用数字存储具有以下几点好处:

  • 存储效率: 数字通常比文本占用更少的存储空间。使用数字代码可以减少数据库的存储需求,提高存储效率。
  • 查询性能: 数字类型的数据在数据库中进行查询和排序时比文本类型更快。这对于需要频繁进行数据分析和报告的大型数据集尤其重要。
  • 数据一致性: 使用数字代码可以避免由于文本标签的不同写法(如大小写、空格、特殊字符等)引起的数据不一致问题。
  • 安全性: 在某些情况下,将敏感信息(如客户信息)以数字代码的形式存储可以提高数据的安全性。
  • 数据处理: 在进行数据分析和挖掘时,数字类型的数据更容易进行计算和统计,如使用聚合函数、执行数学运算等。
  • 扩展性: 数字代码可以更容易地扩展以适应新的标签或分类,而不需要修改数据库结构。
  • 兼容性: 数字代码可以与不同的数据库系统和数据分析工具兼容,便于数据的迁移和交换。
    总的来说,将客户标签存储为数字可以提高数据库的效率、节省存储空间,同时有助于保护客户隐私和简化数据处理过程。

这一种方法适用于多种场景,比如客户来源、公司类型、居住省份、性别、生肖等等。

二、对数值型指标进行缺失值的填充和分箱处理

该函数用于对数值型指标进行缺失值的填充和分箱处理,举个例子,我们对客户的最后一笔交易距今时长做一个分箱处理:

区间对应的字典值如下:

标签编码 标签值
11 1年以下
12 [1年,3年)
13 [3年以上)
99 未投资

那我们的执行代码如下:

python 复制代码
import pandas as pd
import numpy as np

def num_process(df, num_dict, num_null):
    '''
    该函数用于对数值型指标进行缺失值的填充和分箱处理,其中:
    df      : dataframe,传入待处理的dateframe,必须包括待分箱的指标列
    num_dict: dict类型,key代表待分箱的指标名称,value代表分箱的切分点
    tag_null:dict类型,key代表待分箱的指标名称,value代表该指标下需单独分组的缺失值

    return df:传入的df中在num_dict中存在的指标的值已被替换成为对应标签的值
    '''
    # num_dict:{'last_trans_mon_dur': [0, 12, 36, inf]}

    for key, cat_val in num_dict.items():
        null_ind = []
        if num_null.get(key) is not None:
            # 找到数据框中列为 key 且数值等于 num_null[key] 的行的索引,并转换为列表形式。 例如 [0, 1, 2] 此处代表找出和json文件中缺失值一样的数据对应的索引
            null_ind2 = list(df[df[key] == num_null[key]].index)  # 主要是找出 不正常的数据 脏数据, 如果数据质量不错,这里就不会执行
            # 将数据框中列为 key 且数值等于 num_null[key] 的值替换为 98。 其中 num_null里键key对应的值 inplace = True 代表在原数据上修改,默认为False
            df[key].replace(num_null[key], 98, inplace=True)
            null_ind.extend(null_ind2)  # 将列表往后扩展
            print("①.null_ind:%s" % null_ind)
        df[key] = df[key].astype('str')  # 将数据框类型转换为str
        df[key].replace('NULL', '99', inplace=True)  # 数据框中指标为 key 且数值等于 'NULL' 的值替换为 99。
        null_ind1 = list(
            df[df[key] == '99'].index)  # 找到数据框中列为 key 且数值等于 99 的行的索引,并转换为列表形式。 例如 [0, 1, 2] 目的也是为了找出null的数据
        null_ind.extend(null_ind1)
        this_col = df[key].astype('float')  # 将数据框类型转换为浮点型  例如 99 - 99.0
        print("②.this_col:%s" % this_col)
        print("☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆")

        unnull_ind = df.index.difference(null_ind)  # 查找两个索引的集合差异
        print("③.unnull_ind:%s" % unnull_ind)
        print("④.this_col[unnull_ind]:%s" % this_col[unnull_ind])
        print("⑤.cat_val,:%s" % cat_val)
        # 对非缺失值进行分箱处理,使用 pd.cut() 函数将数值分箱,参数 right=False 表示左闭右开区间,生成的标签从 11 开始递增。
        '''
        cat_val:[0, 12, 36, inf]
        range(11, 10+len(cat_val)):range 函数,生成一个从 11 开始、到 10+len(cat_val)-1 结束的整数序列。
        其中,len(cat_val) 表示分箱的数量,加上 10 是为了确保生成的标签与分箱的数量对应,并且从 11 开始编号。
        str(x) for 这部分是一个列表推导式的语法结构,表示对 range() 生成的每个元素 x 执行字符串化操作,并将结果组成一个新的列表。

        如果 cat_val 是 [0, 12, 36, float('inf')],那么生成的标签就是 ['11', '12', '13']。

        ['11', '12', '13'] 因为这一列为 last_trans_mon_dur 最后一笔交易距今时长 cat_val为[0, 12, 36, inf]
        所以pd.cut切割的区间为[0, 12), [12, 36), [36, float('inf')) 
                对应的字典值为 1年以下 [1年,3年) [3年以上)
        '''
        this_col[unnull_ind] = pd.cut(this_col[unnull_ind], cat_val, right=False,
                                      labels=[str(x) for x in range(11, 10 + len(cat_val))])
        # this_col[unnull_ind] 此时为 last_trans_mon_dur 列转换后的数值 即从原来的月份小数转化为了 11 12 13
        df[key] = this_col.astype(
            'int64')  # 将last_trans_mon_dur的新值赋予df中last_trans_mon_dur这一列,同时转换为将数据类型转换为 int64
    return df

# 创建测试数据
data = {
    'name': ['张三', '李四', '王五', '赵六'],
    'last_trans_mon_dur': [2, 18, -2, 30]
}
df = pd.DataFrame(data)

# 定义分箱字典和缺失值字典
num_dict = {'last_trans_mon_dur': [0, 12, 36, np.inf]}
num_null = {'last_trans_mon_dur': -2}

# 处理数据
processed_df = num_process(df, num_dict, num_null)

# 打印结果
print(processed_df)

运行结果

ini 复制代码
①.null_ind:[2]
②.this_col:0     2.0
1    18.0
2    98.0
3    30.0
Name: last_trans_mon_dur, dtype: float64
☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆☆₊⁺🦴🐾₊⁺ ✩°。⋆✰⋆⁺₊⋆ †☾⋆
③.unnull_ind:Int64Index([0, 1, 3], dtype='int64')
④.this_col[unnull_ind]:0     2.0
1    18.0
3    30.0
Name: last_trans_mon_dur, dtype: float64
⑤.cat_val,:[0, 12, 36, inf]
  name  last_trans_mon_dur
0   张三                  11
1   李四                  12
2   王五                  98
3   赵六                  12

显而易见,结果已经发生了明显的变化:原本的具体数值已经转换为了标签编码。例如,原本数值为2个月的数据,现在对应的标签编码为11,代表着1年以下的时长。

将指标转换为标签编码有几个好处:

  • 简化解释: 标签编码将原本复杂的数值转换为了易于理解的分类标签,使得数据解释更加直观和简单。
  • 降低误差: 通过将连续的数值转换为有限的分类,可以降低由于数据误差或测量不准确性而引起的影响。
  • 增强模型泛化能力: 在某些机器学习模型中,将指标转换为标签编码可以提高模型的泛化能力,使其更适应不同的数据分布和模式。
  • 方便数据分析: 标签编码使得数据更容易被聚合和比较,从而方便进行数据分析和可视化。
    这样表达更加清晰,也更容易理解。

这一种方法适用于多种场景,比如用户年龄(少年、青年、中青年、中年、中老年、老年)、当前客户价值等级(优质活跃客户、优质潜力客户、优质维持客户、优质挽留客户、普通...、低价值...)、客户贡献度等级(高忠诚、一般忠诚、低忠诚)、当前客户贡献度等级环比趋势(上升、稳定、下降)等等。

三、对数值型指标进行判断

该函数用于对数值型指标进行判断,大于输入的词典中的阙值的判断为1,否则为0。

举个例子,现在要判定客户是否为存续客户,如果存续金额>0则为1,否则为0。

python 复制代码
import pandas as pd

def boo_process(df, boo_dict):
    '''
    该函数用于对数值型指标进行判断,大于输入的词典中的阙值的判断为1,否则为0,其中:
    df      : dataframe,传入待处理的dateframe,必须包括待处理的指标列
    boo_dict: dict类型,key代表待判断的指标名称,value代表该指标对应的阙值

    return df:传入的df中在num_dict中存在的指标的值已被替换成为对应标签的值
    '''
    for key, val in boo_dict.items():
        print("布尔指标:%s" % key)
        df[key].replace('NULL', 0, inplace=True)  # NULL值转换为0
        df[key] = (df[key] > val).astype('int64')  # 查找df[key]对应的值中 大于0的都标记为1
    return df


# 生成测试数据
data = {'姓名': ['张三', '李四', '王五'],
        '存续金额': [200000, 880000, 0]}
df = pd.DataFrame(data)

# 定义阈值字典 存续金额
boo_dict = {'存续金额': 0}

df_processed = boo_process(df, boo_dict)
print(df_processed)

运行结果

makefile 复制代码
布尔指标:存续金额
   姓名  存续金额
0  张三     1
1  李四     1
2  王五     0

显而易见,结果已经发生了明显的变化:原本的具体数值已经转换为了0和1。

这一种方法适用于多种场景,比如历史最大交易金额(以100万为阈值,大于100万为1,反之为0)、是否购买过美妆(以美妆交易金额0为阈值,大于0为1,反之为0)等等。

四、json文件配置及读取

学习了上述三个方法后,您会发现其实传入的数据都是以 JSON 文件的形式提供的。为了降低后期的运维成本并提高代码的规范性,可以将需要处理的同类型指标统一放在 JSON 文件中。

例如,可以创建几个 JSON 文件,文件名分别为:

  • 布尔型标签.json
json 复制代码
 {
  "max_trans_amt": "最大交易金额",
  "max_trans_amt": 10000000
 }
  • 数值型标签.json
json 复制代码
{
     "his_max_aum_amt": "历史最大存续金额" 
    ,"his_max_aum_amt": [0,0.0001,1000000,3000000,6000000,10000000,30000000,60000000,100000000,Infinity]
    ,"year_old": "年龄"
    ,"year_old": [0, 18, 30, 45, 60, 75, Infinity]
}
  • 特殊缺失值.json
ruby 复制代码
{
    "curr_hold_amt_mom":"当前存续资产金额环比"
    ,"curr_hold_amt_mom":-2
    ,"curr_hold_amt_yoy":"当前存续资产同比"
    ,"curr_hold_amt_yoy":-2
}
  • 类别型标签.json
json 复制代码
{
    "gender":"性别",
    "gender" :{"男":"1","女":"0","其他":"9","NULL":"9"},
    "zodiac": "生肖",
    "zodiac": {"11":"11","12":"12","13":"13","14":"14","15":"15","16":"16",
        "17":"17","18":"18","19":"19","20":"20","21":"21","22":"22","其他":"99","NULL":"99"},
    "cnstll": "星座",
    "cnstll": {"白羊座":"11","金牛座":"12","双子座":"13","巨蟹座":"14","狮子座":"15","处女座":"16", 
        "天枰座":"17","天蝎座":"18","射手座":"19","摩羯座":"20","水瓶座":"21","双鱼座":"22","其他":"99","NULL":"99"},
    "ctry": "居民身份",
    "ctry": {"中国":"1","中国香港和中国澳门":"2","中国台湾":"3","其他":"4","NULL":"9"},
    "prov":  "居住省份",
    "prov":  {"北京" : "11","天津" : "12","河北" : "13","山西" : "14","内蒙古" : "15"
        ,"辽宁" : "21","吉林" : "22","黑龙江" : "23","上海" : "31","江苏" : "32","浙江" : "33"
        ,"安徽" : "34","福建" : "35","江西" : "36","山东" : "37","河南" : "41","湖北" : "42"
        ,"湖南" : "43","广东" : "44","广西" : "45","海南" : "46","重庆" : "50","四川" : "51"
        ,"贵州" : "52","云南" : "53","西藏" : "54","陕西" : "61","甘肃" : "62","青海" : "63"
        ,"宁夏" : "64","新疆" : "65","其他":"98","NULL":"99"},
    "city": "城市类别",
    "city": {"北京":"1","上海":"1","广州":"1","深圳":"1",
        "成都":"2","重庆":"2","杭州":"2","武汉":"2","西安":"2","天津":"2","苏州":"2","南京":"2","郑州":"2","长沙":"2","东莞":"2","沈阳":"2","青岛":"2","合肥":"2","佛山":"2",
        "宁波":"3","昆明":"3","福州":"3","无锡":"3","厦门":"3","济南":"3","大连":"3","哈尔滨":"3","温州":"3","石家庄":"3","泉州":"3","南宁":"3","长春":"3","南昌":"3","贵阳":"3","金华":"3",
        "常州":"3","嘉兴":"3","南通":"3","徐州":"3","太原":"3","珠海":"3","中山":"3","保定":"3","兰州":"3","台州":"3","绍兴":"3","烟台":"3","廊坊":"3","其他":"4","NULL":"9"},
    "mrtl_stat":"婚姻情况",
    "mrtl_stat":{ "02":"1","01":"2","其他":"9","NULL":"9"}
}

在任何情况下,都应该考虑下"Why",这样做的好处其实是多方面的:

  • 降低维护成本: 将指标信息保存在外部 JSON 文件中,减少了直接修改代码的需求。如果需要新增或者修改指标,只需修改对应的 JSON 文件,不用改动代码逻辑,降低了维护成本。
  • 提高代码规范性: 将指标信息和代码逻辑分离,使代码更加清晰易懂,符合良好的编程规范和设计原则,便于团队协作和后续维护。
  • 灵活性和可扩展性: 通过 JSON 文件动态加载指标信息,使得系统更加灵活可配置,便于应对不同的需求和变化。新增指标或修改阈值只需更新 JSON 文件,无需修改代码,提高了系统的可扩展性和适应性。

既然有了json文件,代码自然要增加一块读取的功能

python 复制代码
import os, json

data_file_path_test = r"D:\code\test"

with open(os.path.join(data_file_path_test, '特殊缺失值.json'), encoding="utf-8") as f:  # 类别型标签_itg
    cat_dict = json.load(f)  # 将文件中的 JSON 数据加载并解析成 Python 对象  字典值
    print(cat_dict)

运行结果

arduino 复制代码
{'curr_hold_amt_mom': -2, 'curr_hold_amt_yoy': -2}

五、pandas横表转竖表

最后这段代码的主要作用是将数据从横表转换为竖表,这样做是为了在处理完客户标签后,以竖表的方式更清晰地展示数据。

python 复制代码
import pandas as pd


def dataframe_transfer(df, tag_nm, key_list=['cust_no', 'crt_dt', 'cust_crm_no'], var_nm='tag_cd',
                       value_nm='tag_val_cd'):
    '''
    该函数用于将标签横表转换为竖表,其中:
    df          : dataframe,传入待处理的dateframe
    tag_nm      :list类型,代表标签名称
    key_list   :list类型,不做处理的关键字段列表,默认为['cust_no','crt_dt','cust_crm_no']
    var_nm      :str类型,转化后生成的新标签列名称,默认为'tag_cd'
    value_nm    : str类型,转化后生成的新标签值列名称,默认为'tag_val_cd'

    return df:传入的df后面新增处理完的新标签
    '''
    col = []
    col.extend(key_list)
    col.extend(tag_nm)
    ddf = df.loc[:, col].copy()
    ddf.loc[:, tag_nm] = ddf.loc[:, tag_nm].astype(str)
    ddf = pd.melt(ddf, id_vars=key_list, value_vars=tag_nm, var_name=var_nm, value_name=value_nm)
    return ddf


# 创建测试数据
data = {
    'cust_no': [1, 2, 3, 4],
    'crt_dt': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04'],
    'cust_crm_no': ['CRM001', 'CRM002', 'CRM003', 'CRM004'],
    'tag1': ['A', 'B', 'C', 'D'],
    'tag2': ['X', 'Y', 'Z', 'W']
}

df = pd.DataFrame(data)

# 显示原始数据
print("原始数据:")
print(df)

# 调用函数进行转换
tag_nm = ['tag1', 'tag2']
result_df = dataframe_transfer(df, tag_nm)

# 打印转换后的结果
print("\n转换后的结果:")
print(result_df)

运行结果

yaml 复制代码
原始数据:
   cust_no      crt_dt cust_crm_no tag1 tag2
0        1  2024-01-01      CRM001    A    X
1        2  2024-01-02      CRM002    B    Y
2        3  2024-01-03      CRM003    C    Z
3        4  2024-01-04      CRM004    D    W

转换后的结果:
   cust_no      crt_dt cust_crm_no tag_cd tag_val_cd
0        1  2024-01-01      CRM001   tag1          A
1        2  2024-01-02      CRM002   tag1          B
2        3  2024-01-03      CRM003   tag1          C
3        4  2024-01-04      CRM004   tag1          D
4        1  2024-01-01      CRM001   tag2          X
5        2  2024-01-02      CRM002   tag2          Y
6        3  2024-01-03      CRM003   tag2          Z
7        4  2024-01-04      CRM004   tag2          W

今天的讲解就进入尾声了,本篇介绍了如何利用Pandas将指标数据巧妙地转化为标签。这只是Pandas在数据处理中的一个简单应用场景,而Pandas在Python数据分析和数据科学领域的功能远不止于此。希望大家能够保持对Python的探索和学习热情,继续深入了解和应用Pandas,共同在数据科学领域不断进步。

相关推荐
草莓橙子碗19 分钟前
Claude 使用指南
开发语言·python
工具分享27 分钟前
陪跑跟品爆单AI选品助手,高效跟品会赔本吗?
人工智能·python
COOLMO研究AI40 分钟前
Python 如何实现 AI API 的提示词(Prompt)版本管理与热更新
人工智能·python·prompt
老郑聊AI业财智造44 分钟前
Transformer 技术架构与源码分析
人工智能·python·深度学习·语言模型·架构·transformer·软件工程
程序员三藏1 小时前
自动化测试用例编写详解
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例
230万光年的思念2 小时前
分子动力学模拟参考文献
python
HongXu_CaiYi2 小时前
0103-python相关-函数、文件
python
vortex53 小时前
从Conda到UV:Python项目依赖管理的现代演进
python·conda·uv
何以解忧,唯有..3 小时前
Python time 模块详解:时间处理与日期操作指南
开发语言·python
梅雅达编程笔记4 小时前
Day 18 · 综合实战 B:AI 客服 Agent(专栏收官)
python·智能客服·ai agent·意图识别·ai客服·大模型应用·ai办公自动化