Python数据分析案例-药店销售数据分析

Python09

Python数据分析案例-药店销售数据分析,第1张

最近学习了Python数据分析的一些基础知识,就找了一个药品数据分析的小项目来练一下手。

数据分析的目的:

本篇文章中,假设以朝阳医院2018年销售数据为例,目的是了解朝阳医院在2018年里的销售情况,通过对朝阳区医院的药品销售数据的分析,了解朝阳医院的患者的月均消费次数,月均消费金额、客单价以及消费趋势、需求量前几位的药品等。

数据分析基本过程包括:获取数据、数据清洗、构建模型、数据可视化以及消费趋势分析。

数据准备

数据是存在Excel中的,可以使用pandas的Excel文件读取函数将数据读取到内存中,这里需要注意的是文件名和Excel中的sheet页的名字。读取完数据后可以对数据进行预览和查看一些基本信息。

获取数据:朝阳医院2018年销售数据.xlsx(非真实数据) 提取码: 6xm2

导入原始数据

数据准备

数据是存在Excel中的,可以使用pandas的Excel文件读取函数将数据读取到内存中,这里需要注意的是文件名和Excel中的sheet页的名字。读取完数据后可以对数据进行预览和查看一些基本信息。

获取数据:朝阳医院2018年销售数据.xlsx(非真实数据) 提取码: 6xm2

导入原始数据

数据清洗

数据清洗过程包括:选择子集、列名重命名、缺失数据处理、数据类型转换、数据排序及异常值处理

(1)选择子集

在我们获取到的数据中,可能数据量非常庞大,并不是每一列都有价值都需要分析,这时候就需要从整个数据中选取合适的子集进行分析,这样能从数据中获取最大价值。在本次案例中不需要选取子集,暂时可以忽略这一步。

(2)列重命名

在数据分析过程中,有些列名和数据容易混淆或产生歧义,不利于数据分析,这时候需要把列名换成容易理解的名称,可以采用rename函数实现:

(3)缺失值处理

获取的数据中很有可能存在缺失值,通过查看基本信息可以推测“购药时间”和“社保卡号”这两列存在缺失值,如果不处理这些缺失值会干扰后面的数据分析结果。

缺失数据常用的处理方式为删除含有缺失数据的记录或者利用算法去补全缺失数据。

在本次案例中为求方便,直接使用dropna函数删除缺失数据,具体如下:

(4)数据类型转换

在导入数据时为了防止导入不进来,会强制所有数据都是object类型,但实际数据分析过程中“销售数量”,“应收金额”,“实收金额”,这些列需要浮点型(float)数据,“销售时间”需要改成时间格式,因此需要对数据类型进行转换。

可以使用astype()函数转为浮点型数据:

在“销售时间”这一列数据中存在星期这样的数据,但在数据分析过程中不需要用到,因此要把销售时间列中日期和星期使用split函数进行分割,分割后的时间,返回的是Series数据类型:

此时时间是没有按顺序排列的,所以还是需要排序一下,排序之后索引会被打乱,所以也需要重置一下索引。

其中by:表示按哪一列进行排序,ascending=True表示升序排列,ascending=False表示降序排列

先查看数据的描述统计信息

通过描述统计信息可以看到,“销售数量”、“应收金额”、“实收金额”这三列数据的最小值出现了负数,这明显不符合常理,数据中存在异常值的干扰,因此要对数据进一步处理,以排除异常值的影响:

数据清洗完成后,需要利用数据构建模型(就是计算相应的业务指标),并用可视化的方式呈现结果。

月均消费次数 = 总消费次数 / 月份数(同一天内,同一个人所有消费算作一次消费)

月均消费金额 = 总消费金额 / 月份数

客单价 = 总消费金额 / 总消费次数

从结果可以看出,每天消费总额差异较大,除了个别天出现比较大笔的消费,大部分人消费情况维持在1000-2000元以内。

接下来,我销售时间先聚合再按月分组进行分析:

结果显示,7月消费金额最少,这是因为7月份的数据不完整,所以不具参考价值。

1月、4月、5月和6月的月消费金额差异不大.

2月和3月的消费金额迅速降低,这可能是2月和3月处于春节期间,大部分人都回家过年的原因。

d. 分析药品销售情况

对“商品名称”和“销售数量”这两列数据进行聚合为Series形式,方便后面统计,并按降序排序:

截取销售数量最多的前十种药品,并用条形图展示结果:

结论:对于销售量排在前几位的药品,医院应该时刻关注,保证药品不会短缺而影响患者。得到销售数量最多的前十种药品的信息,这些信息也会有助于加强医院对药房的管理。

每天的消费金额分布情况:一横轴为时间,纵轴为实收金额画散点图。

结论: 从散点图可以看出,每天消费金额在500以下的占绝大多数,个别天存在消费金额很大的情况。

</article>

# 以下程序可能要安装OpenCV2.0(并编译好并配置好环境)以及Xvid解码器才能运行

# _*_coding: cp936_*_

import cv

capture = cv.CreateFileCapture("tmp.avi")

#请确保当前目录下有tmp.avi文件

fps = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FPS)

totalFrameNumber =cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_COUNT)

frameWidth = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH)

frameHeight = cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT)

print fps, totalFrameNumber, frameWidth, frameHeight

frame = cv.QueryFrame(capture)

while frame:

cv.ShowImage('Title', frame)

frame = cv.QueryFrame(capture)

if cv.WaitKey(1000 / fps) == 27:# ESC键退出视频播放

cv.DestroyWindow('Title')

break

一个文件夹整理工具wxPython版本(不清楚软件用法情况下可以打开该软件看看界面但请不要使用里面的功能,后果自负。)

# -*- coding: cp936 -*-

# file:aa.py

import os, shutil, sys

import wx

sourceDir = ''

sourceFiles = ''

preWord = 0

def browse(event):

global sourceDir, textPreWord, textDirName

dialog = wx.DirDialog(None, u'选择待处理文件夹', style = wx.OPEN)

if dialog.ShowModal() == wx.ID_OK:

sourceDir = dialog.GetPath().strip('\"')

textDirName.SetLabel(sourceDir)

dialog.Destroy()

textPreWord.SetFocus()

def okRun(event):

global preWord, sourceDir, sourceFiles, textPreWord

preWord = int(textPreWord.GetValue())

if preWord <= 0:

wx.MessageBox(u'请正确输入前缀字符个数!', u'出错啦', style = wx.ICON_ERROR | wx.OK)

textPreWord.SetFocus()

return

sourceDir = textDirName.GetValue()

sourceFiles = os.listdir(sourceDir)

for currentFile in sourceFiles:

tmp = currentFile

currentFile = sourceDir + '\\' + currentFile

currentFile = currentFile.strip('\"')

if os.path.isdir(currentFile):

continue

targetDir = '%s\\%s'%(sourceDir, tmp[:preWord])

if not os.path.exists(targetDir):

os.mkdir(targetDir)

shutil.move(currentFile, targetDir)

wx.MessageBox(u'任务完成!', u'好消息', style = wx.ICON_ERROR | wx.OK)

def onChange(event):

global dir1, textDirName

p = dir1.GetPath()

textDirName.SetLabel(p)

app = wx.App(0)

win = wx.Frame(None, title = u'文件整理', size = (400, 450))

bg = wx.Panel(win)

btnBrowse = wx.Button(bg, label = u'浏览')

btnBrowse.Bind(wx.EVT_BUTTON, browse)

btnRun = wx.Button(bg, label = u'整理')

btnRun.Bind(wx.EVT_BUTTON, okRun)

dir1 = wx.GenericDirCtrl(bg, -1, dir='', style=wx.DIRCTRL_DIR_ONLY)

tree = dir1.GetTreeCtrl()

dir1.Bind(wx.wx.EVT_TREE_SEL_CHANGED, onChange, id = tree.GetId())

textPreWord = wx.TextCtrl(bg)

textDirName = wx.TextCtrl(bg)

textPreWord.SetFocus()

class MyFileDropTarget(wx.FileDropTarget):#声明释放到的目标

def __init__(self, window):

wx.FileDropTarget.__init__(self)

self.window = window

def OnDropFiles(self, x, y, filenames):#释放文件处理函数数据

for name in filenames:

if not os.path.isdir(name):

wx.MessageBox(u'只能选择文件夹!', '出错啦', style = wx.ICON_ERROR | wx.OK)

return

self.window.SetValue(name)

dt = MyFileDropTarget(textDirName)

textDirName.SetDropTarget(dt)

hbox0 = wx.BoxSizer()

hbox0.Add(dir1, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)

hbox1 = wx.BoxSizer()

hbox1.Add(textDirName, proportion = 1, flag = wx.EXPAND, border = 5)

hbox1.Add(btnBrowse, proportion = 0, flag = wx.LEFT, border = 5)

hbox2 = wx.BoxSizer()

hbox2.Add(textPreWord, proportion = 1, flag = wx.EXPAND, border = 5)

hbox2.Add(btnRun, proportion = 0, flag = wx.LEFT, border = 5)

vbox = wx.BoxSizer(wx.VERTICAL)

vbox.Add(hbox0, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)

vbox.Add(wx.StaticText(bg, -1, u'\n 选择待处理文件夹路径:'))

vbox.Add(hbox1, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 5)

vbox.Add(wx.StaticText(bg, -1, u'\n\n 前缀字符个数:'))

vbox.Add(hbox2, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 5)

vbox.Add(wx.StaticText(bg, -1, u'\n'))

bg.SetSizer(vbox)

win.Center()

win.Show()

app.MainLoop()

#py2exe打包代码如下:

from distutils.core import setup

import py2exe

setup(windows=["aa.py"])

打包好了之后有18Mb。

一个文件内容批量替换摸查器(wxPython版本):

# _*_ coding:cp936 _*_

from string import join, split

import os

import wx

def getAllFiles(adir):

tmp = []

for parent, dirs, files in os.walk(adir):

for afile in files:

tmp.append(os.path.join(parent, afile))

return tmp

def browse(event):

dialog = wx.DirDialog(None, '选择待处理文件夹', style=wx.OPEN)

if dialog.ShowModal() == wx.ID_OK:

aDir = dialog.GetPath()

textDir.SetLabel(aDir)

dialog.Destroy()

def check():

promp.SetForegroundColour('#FF0000')

promp.SetLabel(' 搜索中...')

listFound.ClearAll()

tmp = []

files = getAllFiles(textDir.GetValue())

for filename in files:

try:

afile = open(filename, 'r')

except IOError:

print '打开文件错误。'

continue

try:

content = afile.read().decode('utf-8')

afile.close()

except:

try:

afile = open(filename, 'r')

content = afile.read().decode('cp936')

afile.close()

except:

continue

if textNeedChar.GetValue() in content:

tmp.append(filename)

listFound.InsertStringItem(0, filename)

parentDir.SetForegroundColour('#FF0000')

# parentDir.SetLabel(' 正在搜索:'+filename)

return tmp

def onFind(event):

check()

parentDir.SetLabel(' 搜索结果:')

promp.SetLabel(' 任务完成。')

wx.MessageBox(listFound.GetItemCount() and '任务完成,找到%d个文件。' % listFound.GetItemCount()

or '未找到包含"%s"的文件!' % textNeedChar.GetValue().encode('cp936'), '查找结束',

style=wx.ICON_INFORMATION | wx.OK)

def onReplace(event):

dg = wx.TextEntryDialog(bg, '替换成:', '提示', style=wx.OK | wx.CANCEL)

if dg.ShowModal() != wx.ID_OK:

return

files = check()

userWord = dg.GetValue()

#if not userWord:

#wx.MessageBox('请输入要替换为何字!', style=wx.ICON_ERROR | wx.OK)

#onReplace(event)

#return

for filename in files:

try:

afile = open(filename, 'r')

except IOError:

print '打开文件错误。'

continue

try:

content = afile.read().decode('utf-8')

afile.close()

except:

try:

afile = open(filename, 'r')

content = afile.read().decode('cp936')

afile.close()

except IOError:

print '打开文件错误。'

continue

content = content.replace(textNeedChar.GetValue(), userWord)

try:

content = content.encode('utf-8')

except:

pass

open(filename, 'w').write(content)

parentDir.SetLabel(' 替换的文件:')

promp.SetLabel(' 任务完成。')

wx.MessageBox(listFound.GetItemCount() and '任务完成,替换了%d个文件。' % listFound.GetItemCount()

or '未找到包含"%s"的文件!' % textNeedChar.GetValue().encode('cp936'), '替换结束', style=wx.ICON_INFORMATION | wx.OK)

app = wx.App(0)

win = wx.Frame(None, title='文件整理', size=(400, 450))

bg = wx.Panel(win)

btnBrowse = wx.Button(bg, label='浏览')

btnBrowse.Bind(wx.EVT_BUTTON, browse)

btnFind = wx.Button(bg, label='查找')

btnFind.Bind(wx.EVT_BUTTON, onFind)

btnFind.SetDefault()

btnReplace = wx.Button(bg, label='替换')

btnReplace.Bind(wx.EVT_BUTTON, onReplace)

textNeedChar = wx.TextCtrl(bg, value='你好')

textDir = wx.TextCtrl(bg, value='E:\Workspace\Python\Python\e')

parentDir = wx.StaticText(bg)

promp = wx.StaticText(bg)

listFound = wx.ListCtrl(bg, style=wx.VERTICAL)

class MyFileDropTarget(wx.FileDropTarget):#声明释放到的目标

def __init__(self, window):

wx.FileDropTarget.__init__(self)

self.window = window

def OnDropFiles(self, x, y, filenames):#释放文件处理函数数据

for name in filenames:

'''if not os.path.isdir(name):

wx.MessageBox('只能选择文件夹!', '出错啦', style=wx.ICON_ERROR | wx.OK)

return '''

self.window.SetValue(name)

dt = MyFileDropTarget(textDir)

textDir.SetDropTarget(dt)

hbox1 = wx.BoxSizer()

hbox1.Add(textDir, proportion=1, flag=wx.EXPAND, border=5)

hbox1.Add(btnBrowse, proportion=0, flag=wx.LEFT, border=5)

hbox2 = wx.BoxSizer()

hbox2.Add(textNeedChar, proportion=1, flag=wx.EXPAND, border=5)

hbox2.Add(btnFind, proportion=0, flag=wx.LEFT, border=5)

hbox2.Add(btnReplace, proportion=0, flag=wx.LEFT, border=5)

vbox = wx.BoxSizer(wx.VERTICAL)

vbox.Add(wx.StaticText(bg, -1, '\n 选择待处理文件夹(可拖动文件夹到下面区域中):'))

vbox.Add(hbox1, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)

vbox.Add(wx.StaticText(bg, -1, '\n\n 要替换 / 查找的文字:'))

vbox.Add(hbox2, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)

vbox.Add(promp, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)

vbox.Add(parentDir, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)

vbox.Add(listFound, proportion=1,

flag=wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, border=5)

bg.SetSizer(vbox)

win.Center()

win.Show()

app.MainLoop()