在构建高效的数据分析体系时,我们经常会遇到两个核心概念:指标(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,共同在数据科学领域不断进步。