之前的日報是通過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)
成果一:主要指標每日銷量
成果二:所有門店累計和昨日銷量