基於pandas的朝陽醫院藥品銷售數據分析
0、寫在前面
數據分析的基本過程包括5大步驟:
提出問題
一切數據分析的目的都是為了解決我們生活或者工作中的實際問題,明確的問題為我們後續的數據分析提供了一個大的方向和目標。
理解數據
① 採集數據;
② 導入數據;
③ 查看數據集的信息,包括描述統計信息,從整體上理解數據。
數據清洗(數據預處理)
① 選擇子集;
② 列名重命名;
③ 缺失數據處理;
④ 數據類型轉換;
⑤ 數據排序;
⑥ 異常值處理。
構建模型
對清洗過的數據進行分析。
簡單的分析就是得出一些業務指標;複雜的分析就要用到機器學習的演算法來構建模型。
數據可視化
與他人交流你的研究成果,最好的展示方式就是圖表。
下面我們就基於pandas(python的數據分析包),對朝陽醫院的藥品銷售數據進行數據分析。
1、提出問題
在實際數據分析項目中,前期我們就需要和業務人員一起討論並明確他們的需求以及各個指標的計算公式。
這裡我們主要對以下幾個業務指標進行分析:
① 月均消費次數
② 月均消費金額③ 客單價④ 消費趨勢
2、理解數據
本次的數據來自於社群,具體採集數據的方式有很多種,比如網路爬蟲、資料庫提取數據、埋點採集數據等等,這裡就不細說了。接下來我們利用 jupyter notebook 導入數據進行分析。
# 導入數據分析包import pandas as pd
註解:
這裡需要用到數據分析的常用python包——pandas
# 讀取Excel數據fileNameStr = rC:UsersRichardDesktop數據分析中級(Python)第4關 數據分析的基本過程朝陽醫院2018年銷售數據.xlsxxlsx = pd.ExcelFile(fileNameStr, dtype=object)salesDf = xlsx.parse(Sheet1,dtype=object)
註解:
① 路徑中最好不要有中文,或者特殊符號啥的,不然路徑會提示錯誤:找不到文件的路徑。為了避免此類錯誤發生,我們可以在地址路徑前加 r 或者 R 保證路徑中的反斜杠 不轉義字元。
② 此處統一先按照字元串讀入,之後轉換(dtype = object)。
# 列印出前5行,以確保數據正常運行salesDf.head()
# 查看有多少行,多少列salesDf.shape
註解:
這裡通過DataFrame的shape方法可以查看銷售數據的大小,可以看出有6578行,7列的數據。
# 查看每一列的數據類型salesDf.dtypes
3、數據清洗
數據清洗也叫作數據預處理,目的是把數據改變為我們喜歡的樣子,以便於後期的數據探索和分析。
在數據分析的工作中,有多達60%的時間是花在了分析前數據的清洗上的。這是因為業務部門給我們的原始數據很多時候並不符合我們數據分析的需求,這時候就需要進行數據的清洗,比如處理缺失數據、刪除異常值等等。
數據清洗的過程主要包含以下6大步驟:
1、選擇子集
在數據集中有很多類型的數據,有時候我們只需要選擇自己需要的數據分析就可以了。
因為本案例不需要選擇子集,所以下面的選擇子集代碼被注釋掉了。
# 注釋掉# subSalesDf = salesDf.loc[0:4,購葯時間:銷售數量]
2、列名重命名
這裡我們需要將購葯時間改為銷售時間,因為我們是以醫院的角度進行分析。
# 字典:舊列名和新列名對應關係colNameDict = {購葯時間:銷售時間}inplace=False,數據框本身不會變,而會創建一個改動後新的數據框默認的inplace是Falseinplace=True,數據框本身會改動salesDf.rename(columns=colNameDict, inplace=True)
註解:
① 這裡用到了DataFrame的rename方法。我們需要使用字典的形式將舊列名和新列名一一對應,舊列名作為字典的鍵,新列名作為字典的值。然後將該字典傳給rename方法的columns參數。
② rename方法的參數inplace:inplace=False,數據框本身不會變,而會創建一個改動後新的數據框,默認的inplace是False;inplace=True,數據框本身會改動。
# 查看前5行salesDf.head()
可以看出購葯時間已經改為了銷售時間。
3、缺失數據處理
原始數據集經常會由於記錄錯誤導致缺失某些數據,我們可以採用兩種辦法來處理:
① 直接刪除缺失的數據;
② 如果缺失數據太多,我們可以通過建立模型進行插值的辦法來補充這些數據。(以後在機器學習中具體集合演算法介紹)
Python缺失值有3種:None,NA,NaN
① Python內置的None值;
② 在pandas中,將缺失值表示為NA,表示不可用Not Available;
③ 對於數值數據,pandas使用浮點值NaN(Not a Number)表示缺失數據。
在處理數據的時候,如果遇到錯誤:說什麼float錯誤,那就是有缺失值,需要利用dropna函數處理掉缺失值。
dropna函數詳細使用地址:
pandas.DataFrame.dropna - pandas 0.22.0 documentationprint(刪除缺失值前大小,salesDf.shape)
我們需要刪除銷售時間或者社保卡號列中為空的行:
# how=any 表示在給定的任何一列中有缺失值就刪除salesDf = salesDf.dropna(subset=[銷售時間, 社保卡號], how=any)print(刪除缺失值後大小,salesDf.shape)
4、數據類型轉換
因為我們在導入數據的時候統一按照字元串格式讀取了Excel數據,現在就需要將某些列的字元串類型轉換為需要的數據類型。
顯然,我們需要將銷售數量、應收金額、實收金額列的字元串數據類型轉換為數值類型(浮點型):
salesDf.head()
print(轉換前的數據類型:
,salesDf.dtypes)
# 字元串轉換為數值類型(浮點型)salesDf[銷售數量] = salesDf[銷售數量].astype(float)salesDf[應收金額] = salesDf[應收金額].astype(float)salesDf[實收金額] = salesDf[實收金額].astype(float)print(轉換後的數據類型:
,salesDf.dtypes)
註解:
運行後,可能會報警告"SettingWithCopyWarning"。一般信息有兩列,一類是Warning警告信息,一類是Error錯誤信息。警告的信息不用管,我們只關注錯誤類型的信息。
除此之外,我們還需要將銷售時間這一列的字元串數據類型轉換為日期時間數據類型。這裡的銷售時間為「日期+星期」的格式,我們只需要日期就行,這就要用到字元串的分割方法。
當我們不知道怎麼對字元串進行分割的時候,就需要自己去搜索尋找問題的答案。這裡給出一個搜索網站,它可以間接調用Google搜索技術問題:
UOL Busca# 測試:字元串分割testList = 2018-06-03 星期五.split( )testList
根據上面這個例子,我們知道字元串的split方法是專門用來分割字元串的。我們需要給split傳入一個分隔符,這裡傳入的是空格字元串。該方法會把原始字元串分割為字元串列表,我們只需要該列表的第一個元素即可。
testList[0]
因為我們要處理的銷售時間這一列數據量很多,這就需要定義一個函數進行批量分割:
定義函數:分割銷售時間,獲取銷售日期輸入:dateColSer 銷售時間這一列,是個Series數據類型輸出:分割後的時間,返回也是個Series數據類型def splitSaledate(dateColSer): dateList = [] for value in dateColSer: dateStr = value.split( )[0] dateList.append(dateStr) # 將列錶轉化為一維數據Series類型 dateSer = pd.Series(dateList) return dateSer
# 獲取「銷售時間」這一列dateColSer = salesDf.loc[:,銷售時間]# 對字元串進行分割,獲取銷售時間dateSer = splitSaledate(dateColSer)
註解:
如果運行後報錯:AttributeError: float object has no attribute split
是因為Excel中的空的cell讀入pandas中是空值(NaN),這個NaN是個浮點類型,一般當作空值處理。
所以要先去除NaN再進行分隔字元串。
那None和NaN有什麼區別呢:
None是Python的一種數據類型,NaN是浮點類型,兩個都用作空值。
# None和NaN的區別print(None的數據類型:,type(None))from numpy import NaNprint(NaN的數據類型:,type(NaN))
dateSer[0:3]
# 修改銷售時間這一列的值salesDf.loc[:,銷售時間] = dateSer
salesDf.head()
下面我們需要把銷售時間字元串類型轉換為日期類型:
# errors=coerce:如果原始數據不符合日期的格式,轉換後的值為空值NaT# format:是你原始數據中日期的格式salesDf.loc[:,銷售時間] = pd.to_datetime(salesDf.loc[:,銷售時間], format=%Y-%m-%d, errors=coerce)
salesDf.dtypes
轉換日期過程中不符合日期格式的數值會被轉換為空值,這裡刪除列(銷售時間,社保卡號)中為空的行。
salesDf = salesDf.dropna(subset=[銷售時間,社保卡號], how=any)salesDf.shape
由此可見,數據清洗的幾個步驟不是運行一次就OK的,需要不斷反覆地根據數據清洗的目的反覆做幾次!!!
5、數據排序
print(排序前的數據集)salesDf.head()
# 按銷售日期進行升序排列salesDf = salesDf.sort_values(by=銷售時間, ascending=True)
註解:
by:按哪幾列排序
ascending=True 表示升序排列
ascending=False 表示降序排列
print(排序後的數據集)salesDf.head()
# 重命名行索引(index):排序後的行索引是之前的行號,需要修改成從0到N按順序的索引salesDf = salesDf.reset_index(drop=True)salesDf.head()
6、異常值處理
# 每一列的描述統計信息salesDf.describe()
發現銷售數量存在負數,這明顯不符合邏輯,需要進行處理。
# 刪除異常值:通過條件判斷篩選出數據# 1)查詢條件querySer = salesDf.loc[:,銷售數量] > 0# 2)應用查詢條件print(刪除異常值前:, salesDf.shape)salesDf = salesDf.loc[querySer,:]print(刪除異常值後:, salesDf.shape)
4、構建模型
業務指標1 : 月均消費次數 = 總消費次數 / 月份數
① 總消費次數
總消費次數:同一天內,同一個人發生的所有消費算作一次消費。
我們需要根據列名(銷售時間,社保卡號),如果這兩個列值同時相同,只保留1條,將重複的數據刪除。
step 1:刪除重複數據
kpi1_Df = salesDf.drop_duplicates( subset=[銷售時間, 社保卡號])
step 2:查看行數(總消費次數)
totalI = kpi1_Df.shape[0]print(總消費次數 =,totalI)
② 月份數
step 1:排序
# 按銷售時間升序排序kpi1_Df = kpi1_Df.sort_values(by=銷售時間, ascending=True)# 重命名行名(index)kpi1_Df = kpi1_Df.reset_index(drop=True)
kpi1_Df.head()
step 2:獲取時間範圍
# 最小時間值startTime = kpi1_Df.loc[0,銷售時間]# 最大時間值endTime = kpi1_Df.loc[totalI - 1,銷售時間]
step 3:計算月份數
# 天數daysI = (endTime - startTime).days# 月份數: 運算符「//」表示取整除 # 返回商的整數部分,例如 9 // 2 輸出結果是4monthsI = daysI // 30print(月份數:, monthsI)
# 業務指標1:月均消費次數 = 總消費次數 / 月份數kpi1_I = totalI // monthsIprint(業務指標1:月均消費次數 =, kpi1_I)
業務指標2:月均消費金額 = 總消費金額 / 月份數
# 總消費金額totalMoneyF = salesDf.loc[:,實收金額].sum()# 月均消費金額monthMoneyF = totalMoneyF / monthsIprint(業務指標2:月均消費金額 =,monthMoneyF)
業務指標3:客單價 = 總消費金額 / 總消費次數
客單價(per customer transaction)是指商場(超市)每一個顧客平均購買商品的金額,客單價也即是平均交易金額。
totalMoneyF:總消費金額totalI:總消費次數pct = totalMoneyF / totalIprint(客單價:,pct)
門店的銷售金額是由客單價和顧客數所決定的。因此要提升門店的銷售額,除了儘可能多的吸引顧客,增加顧客交易的次數以外,提高客單價也是非常重要的途徑。
5、總結
到目前為止我們已經完成了3個業務指標的分析計算,只剩1個業務指標——數據趨勢。它的分析需要用到pandas的更多高級功能和數據可視化方面的內容,這塊內容會在之後的課程中詳細地介紹。
推薦閱讀:
※【e葯熱點】這次,藥師真的要失業了!
※【e葯熱點】4月15日起,「連鎖藥店」辦證按新《辦法》要求執行!
※藥店運營離不開的各類數據
※蘇州24小時藥店VS24小時自助售葯機,你寵愛誰?