之前的日报是通过Python+VBA+Power BI三者结合做出来的,有人提出来是否可以全部用Python来实现。
理论上是可以实现的,但是结合在一起会比较累,而且Power Query虽然用起来会有点卡,但是可视化拖曳操作真心方便,维护也简单,所以做了好几天了。
目前已实现功能:
1、单份清单的数据处理(merge、昨日数据提取、pivot_table等);
2、门店维度的日、月数据展现;
3、所有销售数据的每日展现;
4、新增门店、套餐的补充;
5、将结果数据补充到专门的报表中;
暂未实现的功能:
1、一些比较复杂的指标实现,比如一个表的某一些数据根据条件筛选后和另一个表结合获得一个新的字段;
这个功能必须通过个性化写出来,暂时想不出如何做成函数。
2、图片展示;
import pandas as pd
from datetime import timedelta
from datetime import date
import datetime
import warnings
import openpyxl
import re
import os
warnings.filterwarnings("ignore")
import traceback
pd.options.display.max_columns = None
def get_station_and_manager(store_name):
'''根据门店名称获得对应的分局和渠道经理(仅限中小)'''
for i in range(len(address_list)):
if address_list[i]['地址'] in store_name:
station = address_list[i]['所属分局']
manager = address_list[i]['中小渠道经理']
return station, manager
station, manager = '未定', '未定'
return station, manager
def check_num(arr):
'''判断字符串内是否有数字'''
pattern = re.compile('[0-9]+')
for v in arr:
match = pattern.findall(v)
if match:
return 1
return 0
def check_yy(name):
'''判断字符串里是否有营业、专营字样'''
if '营业' in name: return 1
elif '专营' in name: return 1
else: return 0
def get_store_information(store_name, channel):
'''获得门店的具体信息,包括门店标准名称、所属代理商、所属分局和所属渠道经理(限中小)'''
num_check = check_num(store_name)
yy_check = check_yy(store_name)
if num_check == 1 and channel == '中小渠道':
store_format_name = store_name
agent = store_name[:2]
station, manager = get_station_and_manager(store_format_name)
elif num_check == 1 and channel == '开放渠道':
store_format_name, agent, station, manager = '开放渠道其他', '其他', '其他', '其他'
elif num_check == 1 and channel == '专营渠道':
store_format_name, agent, station, manager = '专营渠道其他', '其他', '其他', '其他'
elif num_check == 0 and channel == '中小渠道':
store_format_name, agent, station, manager = '中小渠道其他', '其他', '其他', '其他'
elif num_check == 0 and channel == '专营渠道'and yy_check:
store_format_name = store_name
find_sh = store_name.find('上海')
agent = store_name[:find_sh]
station = get_station_and_manager(store_format_name)[0]
manager = '未定'
elif num_check == 0 and channel == '开放渠道' and yy_check:
store_format_name, agent, station, manager = '开放渠道其他', '其他', '其他', '其他'
elif num_check == 0 and channel == '专营渠道' and not yy_check:
store_format_name, agent, station, manager = '专营渠道其他', '其他', '其他', '其他'
elif num_check == 0 and channel == '开放渠道' and not yy_check:
store_format_name = store_name
find_sh = store_name.find('上海')
agent = store_name[:find_sh]
station = get_station_and_manager(store_format_name)[0]
manager = '其他'
else:
store_format_name = store_name
find_sh = store_name.find('上海')
agent = store_name[:find_sh]
station, manager = '其他', '其他'
store_format_name = store_format_name.replace('(终端)', '').replace('_失效', '')
return store_format_name, agent, station, manager
def find_max_row(ws, col):
'''找到这一列中的最大行'''
row = ws.max_row+1
while True:
for i in range(1, row):
if ws[col+str(i)].value is None:
return i - 1
def find_column(ws, name):
'''找到某个指标所在的列'''
max_col = ws.max_column+1
for i in range(1, max_col):
if ws.cell(row=3, column=i).value == name:
return i
return 0
def value_into_ribao(base_store):
'''把结果导出到日报中'''
wb = openpyxl.load_workbook(r'C:\Users\Administrator\Desktop\报表\日报\日报模板(测试用).xlsx')
ws = wb['门店维度']
store_len = len(base_store)
for column in base_store.columns:
right_column = find_column(ws, column) #注:日报内的单元格名字必须和目标名一模一样
# print(right_column)
if right_column == 0:
continue
else:
for i in range(0, store_len):
ws.cell(row=(4+i), column=right_column).value = base_store[column][i]
wb.save(r'C:\Users\Administrator\Desktop\报表\日报\日报模板(测试用).xlsx')
def value_into_match(sheetname, value_list, tc_type):
'''把新增的门店、套餐等加入《数据说明与匹配公式》中'''
wb = openpyxl.load_workbook(r'C:\Users\Administrator\Desktop\报表\数据\数据说明与匹配公式.xlsx')
ws = wb[sheetname]
row = ws.max_row
value_len = len(value_list)
for i in range(0, value_len):
if sheetname == '部门匹配表':
store_name, channel = value_list[i][0], value_list[i][1]
ws['A' + str(row + i + 1)].value = store_name + channel
ws['B' + str(row+i+1)].value = store_name
ws['C' + str(row+i+1)].value = channel
ws['D' + str(row + i + 1)].value, ws['E' + str(row + i + 1)].value, ws['F' + str(row + i + 1)].value, ws['G' + str(row + i + 1)].value= get_store_information(store_name, channel)
elif sheetname == '套餐匹配表' and tc_type == '移动':
ws['A' + str(row+i+1)].value = value_list[i]
elif sheetname == '套餐匹配表' and tc_type == '宽带':
row = find_max_row(ws, "F")
ws['F' + str(row+i+1)].value = value_list[i]
wb.save(r'C:\Users\Administrator\Desktop\报表\数据\数据说明与匹配公式.xlsx')
def get_date_df():
'''获得从本月月头到昨天的时间列表'''
yesterday = date.today() - timedelta(days=1) # 昨天的日期
first_day = datetime.datetime((date.today() - timedelta(days=1)).year, (date.today() - timedelta(days=1)).month, 1) # 本月第一天的日期
day_count = yesterday.day - first_day.day + 1 # 本月已产生的天数
date_list = []
for i in range(0, day_count):
date_list.append((first_day + timedelta(days=i)).strftime("%Y" + '-' + "%m" + '-' + "%d"))
date_df = pd.DataFrame(date_list, columns=['日期'])
return date_df
def yesterday_format(format_type):
'''获得昨天的标准日期:2019-4-27'''
today = date.today()
oneday = timedelta(days = 1)
yesterday = today - oneday
if format_type == 'type1':
date_yes = yesterday.strftime("%Y" + '-' + "%m" + '-' + "%d") #昨天的标准格式:2019-4-27
elif format_type == 'type2':
date_yes = yesterday.strftime("%Y" + "%m" + "%d") #昨天的另一种格式:20191101
elif format_type == 'type3':
yesterday = yesterday - oneday
date_yes = yesterday.strftime("%Y" + "%m" + "%d") #前天的标准格式:20191101(小翼管家的数据只到前天)
return date_yes
def everyday_sales(df_name, list_name, date_df):
'''获得每日销量
:param df_name: DataFrame的名字
:param list_name: 清单的名字
'''
quju = list_dict[list_name][0] #区局对应的字段
quju_select = list_dict[list_name][1] #区局字段中需要筛选的内容
date_time = list_dict[list_name][2] #日期对应的字段
qd_name = list_dict[list_name][3] # 渠道管理细分对应的字段
qdxl = list_dict[list_name][4] # 渠道小类对应的字段
count_name = list_dict[list_name][6] # 汇总统计对应的字段
aggfunc = list_dict[list_name][7] # 统计方法
if len(list_dict[list_name])> 8:
sp_need = list_dict[list_name][8] #特殊需求
#第一步:提取清单
if qd_name and qdxl:
df_name.loc[df_name[qdxl] == '终端零售店(开放)', qd_name] = '中小渠道'
elif qdxl:
df_name = df_name.merge(qd_match, how='left', left_on=qdxl, right_on='渠道小类')
qd_name = '渠道管理细分'
temp_list = df_name[(df_name[quju] == quju_select) & (df_name[qd_name].isin(['专营渠道', '开放渠道', '中小渠道']))]
#第二步:提取数据透视表并配到日期中
if list_name == '主要销售品': #对于主要销售品,需要区分出宽带在线等5个子销售品
for key, value in sp_need.items():
value_len = len(value)
temp_list[key] = 0
for i in range(0, value_len):
temp_list.loc[temp_list['销售品名称'].str.contains(value[i]), key] = 1
temp_pivot = temp_list.pivot_table(index=date_time, values=['宽带在线', '5G套餐', '5G包', '橙分期', '叠叠乐'], aggfunc=aggfunc).reset_index()
date_df = pd.concat([date_df, temp_pivot[['宽带在线', '5G套餐', '5G包', '橙分期', '叠叠乐']]], axis=1)
else:
temp_pivot = temp_list.pivot_table(index=date_time, values=count_name, aggfunc=aggfunc).reset_index().rename(columns={count_name:list_name})
date_df = pd.concat([date_df, temp_pivot[[list_name]]], axis=1)
return date_df
def store_sales(df_name, list_name, base_store):
'''昨天销量统计
:param df_name: DataFrame的名字
:param list_name: 清单的名字
'''
quju = list_dict[list_name][0] #区局对应的字段
quju_select = list_dict[list_name][1] #区局字段中需要筛选的内容
date_time = list_dict[list_name][2] #日期对应的字段
qd_name = list_dict[list_name][3] # 渠道管理细分对应的字段
qdxl = list_dict[list_name][4] # 渠道小类对应的字段
store_name = list_dict[list_name][5] # 门店对应的字段
count_name = list_dict[list_name][6] # 汇总统计对应的字段
aggfunc = list_dict[list_name][7] # 统计方法
if len(list_dict[list_name])> 8:
sp_need = list_dict[list_name][8] #特殊需求
#第一步:获得标准的渠道管理细分,同时根据区局和渠道管理细分进行清单筛选,再进行部门匹配获得“所属部门”
if qd_name and qdxl:
df_name.loc[df_name[qdxl] == '终端零售店(开放)', qd_name] = '中小渠道'
elif qdxl:
df_name = df_name.merge(qd_match, how='left', left_on=qdxl, right_on='渠道小类')
qd_name = '渠道管理细分'
temp_list = df_name[(df_name[quju] == quju_select) & (df_name[qd_name].isin(['专营渠道', '开放渠道', '中小渠道']))]
temp_list = pd.merge(temp_list, store_match, how='left', left_on=[store_name, qd_name], right_on=['发展部门名称', '渠道管理细分'])
if list_name == '移动': #对于移动清单,需要额外获得叠叠乐新增副卡叠加率
temp_list = pd.merge(temp_list, tc_match[['套餐名称(移动)', '套餐归类1', '套餐归类(日报)', '月租费']],
how='left', left_on='套餐名称', right_on='套餐名称(移动)')
global new_ydtc #新移动套餐
new_ydtc = temp_list['套餐名称'][temp_list['套餐名称(移动)'].isnull().values==True].drop_duplicates()
global new_store #新增门店(需补充在《数据说明与匹配公式》中)
new_store = temp_list[[store_name, qd_name]][temp_list['所属部门'].isnull().values==True].drop_duplicates(subset=None)
elif list_name == '宽带':
temp_list = pd.merge(temp_list, tc_match[['套餐名称(宽带)', '套餐归类', '是否融合套餐', '月租费(不分摊)', '是否融合129及以上套餐']],
how='left', left_on='营销活动名称', right_on='套餐名称(宽带)')
global new_kdtc #新宽带套餐
new_kdtc = temp_list['营销活动名称'][temp_list['套餐名称(宽带)'].isnull().values==True].drop_duplicates()
#第二步:根据不同的清单,对不同的时间字段进行标准格式化,并获得昨天/前天的日期
if list_name == '小翼管家':
temp_list[date_time] = temp_list[date_time].astype('str')
date_yes = yesterday_format('type3')
elif list_name == '播播TV':
temp_list[date_time] = temp_list[date_time].astype('str')
date_yes = yesterday_format('type2')
else:
date_yes = yesterday_format('type1')
temp_list.loc[:, date_time] = pd.to_datetime(temp_list[date_time], format="%Y" + '-' + "%m" + '-' + "%d") # 把统计日期进行标准化(2019-04-25)
#第三步:根据“所属部门”和日期,获得昨天和月累计的数据透视表,并将数据匹配到总表中
if list_name == '主要销售品': #对于主要销售品,需要区分出宽带在线等5个子销售品
for key, value in sp_need.items():
value_len = len(value)
temp_list[key] = 0
for i in range(0, value_len):
temp_list.loc[temp_list['销售品名称'].str.contains(value[i]), key] = 1
temp_pivot = temp_list.pivot_table(index='所属部门', values=['宽带在线', '5G套餐', '5G包', '橙分期', '叠叠乐'], aggfunc=aggfunc).reset_index().rename(columns={'宽带在线': '月宽带在线', '5G套餐': '月5G套餐', '5G包': '月5G包', '橙分期': '月橙分期', '叠叠乐': '月叠叠乐'})
yes_list = temp_list.loc[temp_list[date_time] == date_yes]
yes_pivot = yes_list.pivot_table(index='所属部门', values=['宽带在线', '5G套餐', '5G包', '橙分期', '叠叠乐'], aggfunc=aggfunc).reset_index().rename(columns={'宽带在线': '日宽带在线', '5G套餐': '日5G套餐', '5G包': '日5G包', '橙分期': '日橙分期', '叠叠乐': '日叠叠乐'})
elif list_name == '宽带': #对于宽带清单,需要额外获得129及以上融合
temp_pivot = temp_list.pivot_table(index='所属部门', values=[count_name, '是否融合129及以上套餐'], aggfunc=aggfunc).reset_index().rename(columns={count_name:'月宽带', '是否融合129及以上套餐': '月129及以上套餐'})
yes_list = temp_list.loc[temp_list[date_time] == date_yes]
yes_pivot = yes_list.pivot_table(index='所属部门', values=[count_name, '是否融合129及以上套餐'], aggfunc=aggfunc).reset_index().rename(columns={count_name:'日宽带', '是否融合129及以上套餐': '日129及以上套餐'})
else:
temp_pivot = temp_list.pivot_table(index='所属部门', values=count_name, aggfunc=aggfunc).reset_index().rename(columns={count_name:'月{}'.format(list_name)})
yes_list = temp_list.loc[temp_list[date_time] == date_yes]
yes_pivot = yes_list.pivot_table(index='所属部门', values=count_name, aggfunc=aggfunc).reset_index().rename(columns={count_name:'日{}'.format(list_name)})
base_store = base_store.merge(temp_pivot, how='left', on='所属部门')
base_store = base_store.merge(yes_pivot, how='left', on='所属部门')
base_store = base_store.fillna(0)
return base_store
if __name__ == "__main__":
# 以下清单是需要处理的
cdma = pd.read_csv(r"C:\Users\Administrator\Desktop\报表\数据\cdma.xls", encoding='gbk', sep='\t')
kd = pd.read_csv(r"C:\Users\Administrator\Desktop\报表\数据\kd.xls", encoding='gbk', sep='\t')
yun = pd.read_csv(r"C:\Users\Administrator\Desktop\报表\数据\家庭云.xls", encoding='gbk', sep='\t')
chaiji = pd.read_csv(r"C:\Users\Administrator\Desktop\报表\数据\宽带主动拆机.xls", encoding='gbk', sep='\t')
wifi = pd.read_csv(r"C:\Users\Administrator\Desktop\报表\数据\全屋wifi.xls", encoding='gbk', sep='\t')
kanjia = pd.read_csv(r"C:\Users\Administrator\Desktop\报表\数据\天翼看家.xls", encoding='gbk', sep='\t')
guanjia = pd.read_csv(r"C:\Users\Administrator\Desktop\报表\数据\小翼管家.xls", encoding='gbk', sep='\t')
bobotv = pd.read_excel(r"C:\Users\Administrator\Desktop\报表\数据\播播TV.xlsx", encoding='gbk', skiprows=2)
xiaoshoupin = pd.read_csv(r"C:\Users\Administrator\Desktop\报表\数据\主要销售品.xls", encoding='gbk', sep='\t')
#以下清单是基础匹配表,需要经常维护
store_match = pd.read_excel(r"C:\Users\Administrator\Desktop\报表\数据\数据说明与匹配公式.xlsx", sheet_name='部门匹配表', encoding='gbk')
qd_match = pd.read_excel(r"C:\Users\Administrator\Desktop\报表\数据\数据说明与匹配公式.xlsx", sheet_name='渠道大小类对应表', encoding='gbk')
tc_match = pd.read_excel(r"C:\Users\Administrator\Desktop\报表\数据\数据说明与匹配公式.xlsx", sheet_name='套餐匹配表', encoding='gbk')
base_store = pd.read_excel(r"C:\Users\Administrator\Desktop\报表\日报\日报模板(测试用).xlsx", sheet_name='门店维度', encoding='gbk',skiprows=2, usecols=[4], skip_footer=1).rename(columns={'Unnamed: 4': '所属部门'})
address_match_table = pd.read_excel(r"C:\Users\Administrator\Desktop\报表\数据\数据说明与匹配公式.xlsx", '地址分局匹配表')[
['地址', '所属分局', '中小渠道经理']]
address_list = address_match_table.to_dict(orient='records')
# 把地址分局匹配表转化成[{'地址':'祖冲之路', '所属分局': '张江', '中小渠道经理', '未定'}, {...}, {...}, .....]这种格式
#以下是基础设置,根据实际情况不断修改
base_dict = [[cdma, '移动'], [kd, '宽带'], [chaiji, '宽带主动拆机'], [yun, '家庭云'], [wifi, '全屋WIFI'], [kanjia, '天翼看家'],
[guanjia, '小翼管家'], [bobotv, '播播TV'], [xiaoshoupin, '主要销售品']]
list_dict = {'移动': ['销售区局', '浦东电信局', '统计日期', '渠道管理细分', '发展渠道小类', '发展部门名称', '订单号', 'count'],
'宽带': ['促销区局', '浦东电信局', '完工日期', '渠道管理细分', '发展渠道小类', '促销部门', '订单号', 'count'],
'主要销售品': ['(初始)发展区局', '浦东', '安装日期', '', '(初始)发展渠道小类描述', '(初始)发展部门名称', '(初始)订单号', 'sum',
{'宽带在线': '宽带在线', '5G套餐': ['5G畅享', '5G体验', '极速239'], '5G包': '5G升级会员', '橙分期': '金融分期',
'叠叠乐': '承诺消费'}],
'宽带主动拆机': ['区局', '浦东', '完工日期', '', '受理渠道小类', '受理部门', '订单号', 'count'],
'家庭云': ['发展区局', '浦东电信局', '促销时间', '发展渠道管理细分', '', '发展门店', '资产ID', 'count'],
'全屋WIFI': ['发展区局', '浦东电信局', '促销时间', '发展渠道管理细分', '', '发展门店', '订单号', 'count'],
'天翼看家': ['发展区局', '浦东电信局', '促销时间', '发展渠道管理细分', '', '发展门店', '订单号', 'count'],
'小翼管家': ['协销区局', '浦东', '统计日期', '协销渠道', '', '门店', '是否协销', 'count'],
'播播TV': ['区局', '浦东电信局', '注册时间', '渠道细分', '', '发展部门', '累计完成数据', 'sum']}
ribao_list = ['所属部门', '日移动', '月移动', '日宽带', '月宽带', '日宽带主动拆机', '月宽带主动拆机', '日129及以上套餐', '月129及以上套餐', '日家庭云', '月家庭云', '日全屋WIFI', '月全屋WIFI', '日小翼管家', '月小翼管家', '日天翼看家', '月天翼看家', '日播播TV', '月播播TV', '日宽带在线', '月宽带在线', '日5G套餐', '月5G套餐', '日5G包', '月5G包', '日橙分期', '月橙分期', '日叠叠乐', '月叠叠乐']
# 通过遍历,获得每日销量
date_df = get_date_df()
for base_list in base_dict:
date_df = everyday_sales(df_name=base_list[0], list_name=base_list[1], date_df=date_df)
#通过遍历base_dict,将每一个清单进行处理并merge到base_store中
for base_list in base_dict:
base_store = store_sales(df_name=base_list[0], list_name=base_list[1], base_store=base_store)
#通过遍历ribao_list,判断是否有空列(如昨天没有销量,就可能会空列),如果有就填充
for ribao_column in ribao_list:
if ribao_column in base_store.columns:
continue
else:
base_store[ribao_column] = 0
base_store = base_store[ribao_list]
#保存在一个文件中
with pd.ExcelWriter(r"C:\Users\Administrator\Desktop\报表\日报\门店销量.xlsx") as writer:
date_df.to_excel(writer, sheet_name='日发展', encoding='gbk', index=False)
base_store.to_excel(writer, sheet_name='门店维度', encoding='gbk', index=False)
new_ydtc.to_excel(writer, sheet_name='新移动套餐', encoding='gbk', index=False)
new_kdtc.to_excel(writer, sheet_name='新宽带套餐', encoding='gbk', index=False)
new_store.to_excel(writer, sheet_name='新门店', encoding='gbk', index=False)
print('已完成报表制作!')
#判断是否有新门店/套餐,同时放入《数据说明与匹配公式》里
print("本次新增移动套餐:\n{}".format('无'if len(new_ydtc.values) == 0 else new_ydtc.values))
print("本次新增宽带套餐:\n{}".format('无'if len(new_kdtc.values) == 0 else new_kdtc.values))
print("本次新增门店:\n{}".format('无'if len(new_store.values) == 0 else new_store.values))
value_into_match('套餐匹配表', new_ydtc.values, '移动')
value_into_match('套餐匹配表', new_kdtc.values, '宽带')
value_into_match('部门匹配表', new_store.values, '')
value_into_ribao(base_store)