背景
1.笔者在搭建看板时发现一个板块涉及两个以上相似布局的柱状图,容易产生头晕的感觉,使得注意力无法集中,更别提让业务部同事能一眼看到当前业务问题。帕累托图表则很好地解决了这一点。2.当初想直接用PowerBI的DaX做,但很难查到适合自己的教程,要么太简单不满足自身场景,要么太难完全看不懂,索性用pandas处理了。3.总结一些时间处理的问题。曾有段时间比较忙,这一块没和同事解释清楚,让他慢慢啃,他最后也没懂。关键是当我坐他电脑旁想教他时,自己一时半会儿也看不懂自己写的东西,结果这一块数据处理流程就没教了,这回复盘一下流程。
基本要求
处理基础数据
import pandas as pd
import numpy as np
df = pd.read_csv(r'alldata(随机抽15万样本).csv',low_memory=False)
df['时间戳']= pd.to_datetime(df['时间戳'])
df['年度'] = (df['时间戳'].dt.year).astype(str)
df['月度'] = df['时间戳'].dt.year.astype(str)+'/'+df['时间戳'].dt.month.astype(str)
df['月度'] = pd.to_datetime(df['月度'],format='%Y/%M').dt.strftime('%Y-%M')
df
处理时间
·首先构造每年月末的Series
DateIndex = pd.date_range('2022-01-01','2022-12-31',freq='M')
DateIndex_2021 = pd.date_range('2021-01-01','2021-12-31',freq='M')
DateIndex,DateIndex_2021
·写条件分支
当数据未更新到本月最后一天时,直接选择2021和2022年的数据。
否则,选择更新日到月底的数据。如何选择?基本思路为,df[~((df['时间']>更新日)&(df['时间]<更新日下一个月份的一号))&(df['年度']==2021)],注意加粗的为一个整体。
更新日为4.28,取它的month-1 = 3,3索引在DateIndex_2021所对应的值为2021-4-30加一天则为2021-05-01。1.为什么不直接选每月初的一号,写这教程的时候,顺便去网上看了下,依旧没有理想的资料。2.为什么用不用 ≤ 而用<。pandas处理年、月没什么问题,一旦涉及日期相关的数据,结果可能会出现错误,用 < 符号比较稳妥
DateIndex = pd.date_range('2022-01-01','2022-12-31',freq='M')
DateIndex_2021 = pd.date_range('2021-01-01','2021-12-31',freq='M')
#DateIndexString = DateIndex.astype(str)
if pd.Series(str(df['时间戳'].max())[:10]).isin(DateIndex)[0]:#如果当前月未走完
df_recentyear = df[(df['年度']=='2021')|(df['年度']=='2022')]
df_recentyear['月份'] = df_recentyear['诊断时间'].dt.month
else:
same_2021 = (df[~((df['时间戳']>='2021-{}'.format(str(df['时间戳'].max()+pd.DateOffset(days=1))[5:10]))
&(df['时间戳']<str(DateIndex_2021[df['时间戳'].max().month-1]+pd.DateOffset(days=1))[:10]))
&(df['年度']=='2021')]
)
same_2022 = df[(df['时间戳']>='2022-01-01')
&(df['时间戳']<str(df['时间戳'].max()+ pd.DateOffset(days=1))[:10])
]
df_recentyear = pd.concat([same_2021,same_2022])
df_recentyear['月份'] = df_recentyear['时间戳'].dt.month
处理帕累托
做透视表
按【行】城市、产品、月份,【列】年度铺成透视表,然后新增列计算差值,其中差值既有大于0,也有小于0的记录。
(df_recentyear
.pipe(lambda x:pd.crosstab([x['城市'],x['产品'],x['月份']],x['年度']))
.reset_index()
.assign(差值 = lambda x:x['2022']-x['2021'])
)
筛选小于0,即同期减少的数据,并分组求和
求和的是差值,分组的城市、产品、月份,最后还要记得排序
(df_recentyear
.pipe(lambda x:pd.crosstab([x['城市'],x['产品'],x['月份']],x['年度']))
.reset_index()
.assign(差值 = lambda x:x['2022']-x['2021'])
.query("差值<0")
.groupby(['城市','产品','月份'])
.agg(**{'城市差值':pd.NamedAgg(column='差值',aggfunc='sum'),
}
)
.reset_index()
.sort_values(by='城市差值')
).query("月份==4 and 产品=='A'")
查看4月份产品A的数据结构
接着处理不同城市、产品、月份差值总计,并计算百分比和累计百分比
ABC_city = (df_recentyear
.pipe(lambda x:pd.crosstab([x['城市'],x['产品'],x['月份']],x['年度']))
.reset_index()
.assign(差值 = lambda x:x['2022']-x['2021'])
.query("差值<0")
.groupby(['城市','产品','月份'])
.agg(**{'城市差值':pd.NamedAgg(column='差值',aggfunc='sum'),
}
)
.reset_index()
.sort_values(by='城市差值')
.assign(总计 = lambda x:x.groupby(['产品','月份'])['城市差值'].transform('sum')
,百分比 = lambda x:x['城市差值']/x['总计'])
.assign(累计百分比 = lambda x:x.groupby(['产品','月份'])['百分比'].cumsum())
)##['城市差值'].sum()
ABC_city.query("月份==4 and 产品=='A'")
到这里,2021和2022的列不见了。
最后验证数据,处理成最终想要达到的效果
再做一个透视表,并用ABC_city对它进行左连接,那最终的结果decrease_ABC就是同期减少的数据。
city_sametime = pd.crosstab([df_recentyear['城市']
,df_recentyear['产品']
,df_recentyear['月份']
]
,df_recentyear['年度']
).reset_index()
decrease_ABC = ABC_city.merge(city_sametime).assign(类型='减少')
decrease_ABC#.query("月份==4 and 产品=='A'")
使用相同方法,构造一份差值>0的数据
剩下的步骤一摸一样,最后用concat进行拼接即可
最后PowerBI里面拖拽一下,给累计百分比增加个ABC的类型即可
总结
用pandas处理一次帕累托(ABC)图表不是那么容易,中间不少求和运算、排序操作比较绕。
不足
可以加一根柱子来表示影响本次增加或减少的用户究竟有多少个?