洞悉客户心声: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,共同在数据科学领域不断进步。

相关推荐
秃头佛爷32 分钟前
Python学习大纲总结及注意事项
开发语言·python·学习
深度学习lover2 小时前
<项目代码>YOLOv8 苹果腐烂识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·苹果腐烂识别
API快乐传递者3 小时前
淘宝反爬虫机制的主要手段有哪些?
爬虫·python
阡之尘埃5 小时前
Python数据分析案例61——信贷风控评分卡模型(A卡)(scorecardpy 全面解析)
人工智能·python·机器学习·数据分析·智能风控·信贷风控
布说在见7 小时前
层次与网络的视觉对话:树图与力引导布局的双剑合璧
信息可视化·数据挖掘·数据分析
丕羽8 小时前
【Pytorch】基本语法
人工智能·pytorch·python
bryant_meng8 小时前
【python】Distribution
开发语言·python·分布函数·常用分布
m0_594526309 小时前
Python批量合并多个PDF
java·python·pdf
工业互联网专业9 小时前
Python毕业设计选题:基于Hadoop的租房数据分析系统的设计与实现
vue.js·hadoop·python·flask·毕业设计·源码·课程设计
钱钱钱端10 小时前
【压力测试】如何确定系统最大并发用户数?
自动化测试·软件测试·python·职场和发展·压力测试·postman