資料分析:某地醫院藥品銷售業務資料分析
資料分析:某地醫院藥品銷售業務資料分析
本篇文章以朝陽醫院2018年銷售資料為例,目的是瞭解朝陽醫院在2018年裡的銷售情況幾個業務指標
- 月均消費次數
- 月均消費金額
- 客單價
- 消費趨勢
資料分析的步驟:提出問題→理解資料→資料清洗→構建模型→資料視覺化
一.確定業務問題
我們知道,資料分析是指用適當的統計分析方法對收集來的大量資料進行分析,提取有用資訊和形成結論而對資料加以詳細研究和概括總結的過程。
那麼,與之對應的資料分析基本過程包括:獲取資料、資料清洗、構建模型、資料視覺化以及消費趨勢等
二:資料概覽
# 2018年朝陽醫院資料消費金額趨勢圖 import matplotlib.pyplot as plt from pandas import Series,DataFrame import pandas as pd import numpy as np fileNameStr='F:\\Downloads\朝陽醫院2018年銷售資料.xlsx' xls=pd.ExcelFile(fileNameStr,dtype='object') salesDf = xls.parse('Sheet1',dtype='object') salesDf.info()
列印結果
<class 'pandas.core.frame.DataFrame'> RangeIndex: 6578 entries, 0 to 6577 Data columns (total 7 columns): 購藥時間 6576 non-null object 社保卡號 6576 non-null float64 商品編碼 6577 non-null float64 商品名稱 6577 non-null object 銷售數量 6577 non-null float64 應收金額 6577 non-null float64 實收金額 6577 non-null float64 dtypes: float64(5), object(2) memory usage: 359.8+ KB
資料概覽
salesDf.head()
列印結果
購藥時間 社保卡號 商品編碼 商品名稱 銷售數量 應收金額 實收金額
0 2018-01-01 星期五 001616528 236701 強力VC銀翹片 6 82.8 69
1 2018-01-02 星期六 001616528 236701 清熱解毒口服液 1 28 24.64
2 2018-01-06 星期三 0012602828 236701 感康 2 16.8 15
3 2018-01-11 星期一 0010070343428 236701 三九感冒靈 1 28 28
4 2018-01-15 星期五 00101554328 236701 三九感冒靈 8 224 208
# 行、列數 salesDf.shape (6578, 7) salesDf.index RangeIndex(start=0, stop=6578, step=1) s salesDf.columns Index(['購藥時間', '社保卡號', '商品編碼', '商品名稱', '銷售數量', '應收金額', '實收金額'], dtype='object') salesDf.count() 購藥時間 6576 社保卡號 6576 商品編碼 6577 商品名稱 6577 銷售數量 6577 應收金額 6577 實收金額 6577 dtype: int64
資料缺失: 總共有6578行7列資料,但是“購藥時間”和“社保卡號”這兩列只有6576個數據,而“商品編碼”一直到“實收金額”這些列都是隻有6577個數據, 資料中存在缺失值,可以推斷出資料中存在一行缺失值,此外“購藥時間”和“社保卡號”這兩列都各自存在一個缺失資料。
在任何資料分析的操作步驟中,為保證資料分析準確性,資料清洗步驟就顯得尤為重要。
三.資料清洗
資料清洗過程,或稱資料預處理,主要包括以下幾個步驟
- 選擇子集
- 列名重新命名
- 刪除缺失資料
- 資料型別轉換
- 資料排序
- 異常值處理
1選擇子集
在我們獲取到的資料中,可能資料量非常龐大,並不是每一列都有價值都需要分析,這時候就需要從整個資料中選取合適的子集進行分析,這樣能從資料中獲取最大價值。
2列名重新命名
在資料分析過程中,有些列名和資料容易混淆或產生歧義,不利於資料分析,這時候需要把列名換成容易理解的名稱,可以採用rename函式實現:
salesDf.rename(columns ={'購藥時間':'銷售時間'},inplace=True) #inplace=True,資料框本身會改動
salesDf.head()
列印結果
銷售時間 社保卡號 商品編碼 商品名稱 銷售數量 應收金額 實收金額
0 2018-01-01 星期五 1.616528e+06 236701.0 強力VC銀翹片 6.0 82.8 69.00
1 2018-01-02 星期六 1.616528e+06 236701.0 清熱解毒口服液 1.0 28.0 24.64
2 2018-01-06 星期三 1.260283e+07 236701.0 感康 2.0 16.8 15.00
3 2018-01-11 星期一 1.007034e+10 236701.0 三九感冒靈 1.0 28.0 28.00
4 2018-01-15 星期五 1.015543e+08 236701.0 三九感冒靈 8.0 224.0 208.00
3 缺失資料處理
任何一個得到的資料都很有可能會有缺失值,刪除列(銷售時間,社保卡號)中為空的行,使用dropna刪除缺失資料
print('刪除缺失值前大小',salesDf.shape)
# how='any' 給定的任何一列中有缺失值就刪除
salesDf=salesDf.dropna(subset=['銷售時間','社保卡號'],how='any')
print('刪除缺失後大小',salesDf.shape)
列印結果
刪除缺失值前大小 (6578, 7)
刪除缺失後大小 (6575, 7)
4 資料型別處理
在匯入的時候為了防止有些資料匯入不進來,所以強制所有資料都是object型別,但在實際分析上這樣是不可能的。
通過觀察,銷售數量,應收金額,實收金額,應該改成float型別,銷售時間應該清理後改成時間型別,對於改變成float型別的幾列,使用astype函式,程式碼如下。
salesDf['銷售數量']=salesDf['銷售數量'].astype('float')
salesDf['應收金額']=salesDf['應收金額'].astype('float')
salesDf['實收金額']=salesDf['實收金額'].astype('float')
print('轉換後的資料型別:\n',salesDf.dtypes)
列印結果
`轉換後的資料型別:
銷售時間 object
社保卡號 object
商品編碼 object
商品名稱 object
銷售數量 float64
應收金額 float64
實收金額 float64
dtype: object
而銷售時間那一列,則需要進行處理後才能轉換為時間型別,把銷售時間的日期和星期分開
分割時間列,定義函式:分割銷售日期,獲取銷售日期
def splitSaletime(timeColSer):
timeList=[]
for value in timeColSer:
#例如2018-01-01 星期五,分割後為:2018-01-01
dateStr=value.split(' ')[0]
timeList.append(dateStr)
timeSer=pd.Series(timeList)
return timeSer
獲取“銷售時間”這一列,對字串進行分割,獲取銷售日期
timeSer=salesDf.loc[:,'銷售時間']
dateSer=splitSaletime(timeSer)
修改銷售時間這一列的值
列印結果
dateSer[0:3]
0 2018-01-01
1 2018-01-02
2 2018-01-06
dtype: object
獲取分割之後的銷售日期,少了星期時間字元
salesDf.loc[:,'銷售時間']=dateSer
salesDf.head()
列印結果
銷售時間 社保卡號 商品編碼 商品名稱 銷售數量 應收金額 實收金額
0 2018-01-01 001616528 236701 強力VC銀翹片 6.0 82.8 69.00
1 2018-01-02 001616528 236701 清熱解毒口服液 1.0 28.0 24.64
2 2018-01-06 0012602828 236701 感康 2.0 16.8 15.00
3 2018-01-11 0010070343428 236701 三九感冒靈 1.0 28.0 28.00
4 2018-01-15 00101554328 236701 三九感冒靈 8.0 224.0 208.00
5 資料排序
使用sort_values進行排序,by:按哪幾列排序,ascending=True 表示升序排列,ascending=False表示降序排列
#按銷售時間進行升序排列
salesDf=salesDf.sort_values(by='銷售時間',ascending=True)
#檢視排序後的前10行
salesDf.head(10)
列印結果
銷售時間 社保卡號 商品編碼 商品名稱 銷售數量 應收金額 實收金額
0 2018-01-01 001616528 236701 強力VC銀翹片 6.0 82.8 69.0
3436 2018-01-01 0010616728 865099 硝苯地平片(心痛定) 2.0 3.4 3.0
1190 2018-01-01 0010073966328 861409 非洛地平緩釋片(波依定) 5.0 162.5 145.0
3859 2018-01-01 0010073966328 866634 硝苯地平控釋片(欣然) 6.0 111.0 92.5
3888 2018-01-01 0010014289328 866851 纈沙坦分散片(易達樂) 1.0 26.0 23.0
894 2018-01-01 0013331728 861405 苯磺酸氨氯地平片(絡活喜) 2.0 69.0 62.0
893 2018-01-01 0011743428 861405 苯磺酸氨氯地平片(絡活喜) 1.0 34.5 31.0
4368 2018-01-01 00103283128 870921 卡託普利片 1.0 2.4 2.2
4562 2018-01-01 0010074599128 874684 厄貝沙坦氫氯噻嗪片(依倫平) 5.0 118.0 118.0
5039 2018-01-01 0010017493928 868042 馬來酸左旋氨氯地平片(玄寧) 1.0 46.0 46.0
重新命名行名(index),使用reset_index修改成從0到N按順序排序的索引值index
salesDf=salesDf.reset_index(drop=True)
檢視資料 salesDf.head(6)
銷售時間 社保卡號 商品編碼 商品名稱 銷售數量 應收金額 實收金額
0 2018-01-01 001616528 236701 強力VC銀翹片 6.0 82.8 69.0
1 2018-01-01 0010616728 865099 硝苯地平片(心痛定) 2.0 3.4 3.0
2 2018-01-01 0010073966328 861409 非洛地平緩釋片(波依定) 5.0 162.5 145.0
3 2018-01-01 0010073966328 866634 硝苯地平控釋片(欣然) 6.0 111.0 92.5
4 2018-01-01 0010014289328 866851 纈沙坦分散片(易達樂) 1.0 26.0 23.0
5 2018-01-01 0013331728 861405 苯磺酸氨氯地平片(絡活喜) 2.0 69.0 62.0
6 異常值處理
檢視彙總資料描述,其中銷售數量值不能小於0
salesDf.describe()
列印結果
銷售數量 應收金額 實收金額
count 6549.000000 6549.000000 6549.000000
mean 2.384486 50.449076 46.284370
std 2.375227 87.696401 81.058426
min -10.000000 -374.000000 -374.000000
25% 1.000000 14.000000 12.320000
50% 2.000000 28.000000 26.500000
75% 2.000000 59.600000 53.000000
max 50.000000 2950.000000 2650.000000
通過條件判斷來刪除異常值
querySer=salesDf.loc[:,'銷售數量']>0
print('刪除異常值前:',salesDf.shape)
salesDf=salesDf.loc[querySer,:]
print('刪除異常值後:',salesDf.shape)
# 列印結果
刪除異常值前: (6549, 7)
刪除異常值後: (6506, 7)
資料的預處理工作完成,接下來分析業務的各個指標
四 構建資料模型
1.月份數
業務指標1:月均消費次數=總消費次數 / 月份數
在計算總的消費次數當中將每個人每天的不同消費記錄作為消費一次,用drop_duplicates去掉同一天同一個人的重複消費記錄
根據列名(銷售時間,社群卡號),如果這兩個列值同時相同,只保留1條,將重複的資料刪除
kpi1_Df=salesDf.drop_duplicates(subset=['銷售時間', '社保卡號'])
#總消費次數
totalI=kpi1_Df.shape[0]
print('總消費次數=',totalI)
# 列印結果:總消費次數= 5342
# 計算月份數
#按銷售時間升序排序
kpi1_Df=kpi1_Df.sort_values(by='銷售時間',ascending=True)
#重新命名行名,索引排序
kpi1_Df=kpi1_Df.reset_index(drop=True)
kpi1_Df.head()
列印結果
銷售時間 社保卡號 商品編碼 商品名稱 銷售數量 應收金額 實收金額
0 2018-01-01 001616528 236701 強力VC銀翹片 6.0 82.8 69.0
1 2018-01-01 0012697828 861464 複方利血平片(複方降壓片) 4.0 10.0 9.4
2 2018-01-01 0010060654328 861458 複方利血平氨苯蝶啶片(北京降壓0號) 1.0 10.3 9.2
3 2018-01-01 0011811728 861456 酒石酸美託洛爾片(倍他樂克) 1.0 7.0 6.3
4 2018-01-01 0013448228 861507 苯磺酸氨氯地平片(安內真) 1.0 9.5 8.5
計算總月份數,第一行時間與結尾時間之差除以30取整
startTime=kpi1_Df.loc[0,'銷售時間']
#最大時間值
endTime=kpi1_Df.loc[totalI-1,'銷售時間']
#天數
daysI=(endTime-startTime).days
#月份數: 運算子“//”表示取整除
#返回商的整數部分,例如9//2 輸出結果是4
monthsI=daysI//30
print('月份數:',monthsI)
月份數: 6
2.月均消費次數
業務指標2:月均消費次數=總消費次數 / 月份數
計算月均消費次數
kpi1_I=totalI // monthsI
print('業務指標2:月均消費次數=',kpi1_I)
# 列印結果
業務指標2:月均消費次數= 890
3.月均消費金額
指標3:月均消費金額 = 總消費金額 / 月份數
#總消費金額
totalMoneyF=salesDf.loc[:,'實收金額'].sum()
#月均消費金額
monthMoneyF=totalMoneyF / monthsI
print('業務指標3:月均消費金額=',monthMoneyF)
業務指標3:月均消費金額= 50668.35166666666
4.客單價
指標4:客單價=總消費金額 / 總消費次數
客單價(per customer transaction)是指商場(超市)每一個顧客平均購買商品的金額,客單價也即是平均交易金額。
'''
totalMoneyF:總消費金額
totalI:總消費次數
'''
pct=totalMoneyF / totalI
print('客單價:',pct)
客單價: 56.909417821040805
5.消費趨勢圖
#在進行操作之前,先把資料複製到另一個數據框中,防止對之前清洗後的資料框造成影響
groupDf=salesDf
#第1步:重新命名行名(index)為銷售時間所在列的值
groupDf.index=groupDf['銷售時間']
groupDf.head()
列印結果
銷售時間 社保卡號 商品編碼 商品名稱 銷售數量 應收金額 實收金額
銷售時間
2018-01-01 2018-01-01 001616528 236701 強力VC銀翹片 6.0 82.8 69.0
2018-01-01 2018-01-01 0010616728 865099 硝苯地平片(心痛定) 2.0 3.4 3.0
2018-01-01 2018-01-01 0010073966328 861409 非洛地平緩釋片(波依定) 5.0 162.5 145.0
2018-01-01 2018-01-01 0010073966328 866634 硝苯地平控釋片(欣然) 6.0 111.0 92.5
2018-01-01 2018-01-01 0010014289328 866851 纈沙坦分散片(易達樂) 1.0 26.0 23.0
分組
gb=groupDf.groupby(groupDf.index.month)
gb
# 列印結果
<pandas.core.groupby.DataFrameGroupBy object at 0x000000000ED4CC18>
#第3步:應用函式,計算每個月的消費總額
mounthDf=gb.sum()
mounthDf
列印結果
銷售數量 應收金額 實收金額
銷售時間
1 2527.0 53561.6 49461.19
2 1858.0 42028.8 38790.38
3 2225.0 45318.0 41597.51
4 3005.0 54296.3 48787.84
5 2225.0 51263.4 46925.27
6 2328.0 52300.8 48327.70
7 1483.0 32568.0 30120.22
選取每個月的應收金額和實收金額的消費總額
mounthDf=DataFrame(mounthDf,columns=['應收金額','實收金額'])
mounthDf
列印結果
應收金額 實收金額
銷售時間
1 53561.6 49461.19
2 42028.8 38790.38
3 45318.0 41597.51
4 54296.3 48787.84
5 51263.4 46925.27
6 52300.8 48327.70
7 32568.0 30120.22
五 資料視覺化
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei'] #防止中文亂碼
mounthDf.plot(title='2018年朝陽醫院資料消費金額趨勢圖',figsize=(15,8),fontsize=20)
我們可以發現,週五週六的銷售總額要顯著的的高於其他日期,即週五週六應該前來買藥的人更多,銷售的藥品更多。
1月和第七月消費總金額是最高的,在第七月消費金額最低。
醫藥銷售量和天氣變化有一定的影響,尤其在冬季天氣寒冷和初春季節,容易受到天氣影響,氣溫變化大,市民容易感冒,從而在醫藥行業銷售更多了醫藥,銷售量上升,在氣溫平穩時期銷售量下降。
醫藥銷售金額會受到節日、天氣、重大活動等因素的影響。
六 資料建模
# 2018年朝陽醫院資料消費金額趨勢圖
import matplotlib.pyplot as plt
from pandas import Series,DataFrame
import pandas as pd
import numpy as np
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei'] #防止中文亂碼
fileNameStr='F:\\Downloads\朝陽醫院2018年銷售資料.xlsx'
xls=pd.ExcelFile(fileNameStr,dtype='object')
salesDf = xls.parse('Sheet1',dtype='object')
def splitSaletime(timeColSer):
timeList=[]
for value in timeColSer:
#例如2018-01-01 星期五,分割後為:2018-01-01
dateStr=value.split(' ')[0]
timeList.append(dateStr)
timeSer=pd.Series(timeList)
return timeSer
salesDf.rename(columns ={'購藥時間':'銷售時間'},inplace=True) #inplace=True,資料框本身會改動
#how='any' 給定的任何一列中有缺失值就刪除
salesDf=salesDf.dropna(subset=['銷售時間','社保卡號'],how='any')
salesDf['銷售數量']=salesDf['銷售數量'].astype('float')
salesDf['應收金額']=salesDf['應收金額'].astype('float')
salesDf['實收金額']=salesDf['實收金額'].astype('float')
timeSer=salesDf.loc[:,'銷售時間']
dateSer=splitSaletime(timeSer)
salesDf.loc[:,'銷售時間']=dateSer
salesDf.loc[:,'銷售時間']=pd.to_datetime(salesDf.loc[:,'銷售時間'],
format='%Y-%m-%d', errors='coerce')
salesDf=salesDf.dropna(subset=['銷售時間','社保卡號'],how='any')
#按銷售時間進行升序排列
salesDf=salesDf.sort_values(by='銷售時間',ascending=True)
salesDf=salesDf.reset_index(drop=True)
# 刪除異常值
querySer=salesDf.loc[:,'銷售數量']>0
salesDf=salesDf.loc[querySer,:]
kpi1_Df=salesDf.drop_duplicates(subset=['銷售時間', '社保卡號'])
#總消費次數
totalI=kpi1_Df.shape[0]
#按銷售時間升序排序
kpi1_Df=kpi1_Df.sort_values(by='銷售時間',ascending=True)
#重新命名行名,索引排序
kpi1_Df=kpi1_Df.reset_index(drop=True)
startTime=kpi1_Df.loc[0,'銷售時間']
#最大時間值
endTime=kpi1_Df.loc[totalI-1,'銷售時間']
#天數
daysI=(endTime-startTime).days
#月份數: 運算子“//”表示取整除
#返回商的整數部分,例如9//2 輸出結果是4
monthsI=daysI//30
# 業務指標2:月均消費次數
kpi1_I=totalI // monthsI
#總消費金額
totalMoneyF=salesDf.loc[:,'實收金額'].sum()
#業務指標3:月均消費金額
monthMoneyF=totalMoneyF / monthsI
'''
totalMoneyF:總消費金額
totalI:總消費次數
'''
pct=totalMoneyF / totalI
#在進行操作之前,先把資料複製到另一個數據框中,防止對之前清洗後的資料框造成影響
groupDf=salesDf
#第1步:重新命名行名(index)為銷售時間所在列的值
groupDf.index=groupDf['銷售時間']
gb=groupDf.groupby(groupDf.index.month)
#第3步:應用函式,計算每個月的消費總額
mounthDf=gb.sum()
mounthDf=DataFrame(mounthDf,columns=['應收金額','實收金額'])
mounthDf.plot(title='2018年朝陽醫院資料消費金額趨勢圖',figsize=(15,8),fontsize=20)
b一隻阿