利用 Pandas 进行简单数据分析流程

本文来自于 猴子数据分析社群的通关作业,因为课程是用 R 语言教的,我是用 Python 实现了一遍,所以参考的文档也都列了出来,总结的也挺不容易的,欢迎同学吐槽。

文章主要简单实现了一遍初级的数据分析过程,首先是利用 pandas 读取 excel 文件,之后简单的通过去空值等过程简单处理了数据内容,最后计算了了几个常用的业绩指标。

文章算是实现了一个最初级的数据分析过程,比起啃一本完整的教材还是不能实践,不如今早开始将知识投入到使用过程中,形成宏观知识架构方便后续补充学习,同时还能熟悉知识点和知识点之间的协作过程。

1. 读取 excel

# coding=utf-8

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
file_name = "../源代码和数据/朝阳医院2016年销售数据.xlsx"

xls_file = pd.ExcelFile(file_name, dtype='object') # 统一先按照str读入,之后转换

table = xls_file.parse('Sheet1', dtype='object')

# file_name = "../源代码和数据/朝阳医院2016年销售数据.xlsx"
# table = pd.read_excel(file_name, sheeetname = 'Sheet1', dtype='object')
print type(xls_file)
print type(table)

<class 'pandas.io.excel.ExcelFile'>
<class 'pandas.core.frame.DataFrame'>

table.head()

元数据好像没有空值,所以我自己加了两行

2. 数据预处理

2.1 查看基本信息

print table.shape
print table.index
print table.columns

(6579, 7)
RangeIndex(start=0, stop=6579, step=1)
Index([u'购药时间', u'社保卡号', u'商品编码', u'商品名称', u'销售数量', u'应收金额', u'实收金额'], dtype='object')

print table.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6579 entries, 0 to 6578
Data columns (total 7 columns):
购药时间 6577 non-null object
社保卡号 6578 non-null object
商品编码 6577 non-null object
商品名称 6578 non-null object
销售数量 6577 non-null object
应收金额 6577 non-null object
实收金额 6577 non-null object
dtypes: object(7)
memory usage: 359.9+ KB
None

print table.count()

购药时间 6577
社保卡号 6578
商品编码 6577
商品名称 6578
销售数量 6577
应收金额 6577
实收金额 6577
dtype: int64

2.2 列重命名

pandas 的 rename 方法 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.rename.html

col = {u'购药时间':'time',\
u'社保卡号':'cardno',\
u'商品编码':'drugId',\
u'商品名称':'drugName',\
u'销售数量':'saleNumber',\
u'应收金额':'virtualmoney',\
u'实收金额':'actualmoney'}
table.rename(columns = col, inplace = True)
table.head()

2.3 删除缺失值

pandas 的 dropna 方法 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.dropna.html?highlight=dropna#pandas.DataFrame.dropna|

dropna1 = table.dropna()
dropna2 = table.dropna(how = 'all') # 参数设定是,所有值为 NA 才删除掉
dropna1.head()
dropna2.head()

2.4 处理日期

# 定义一个把 time 行中日期和星期分开的函数,分别返回日期和星期构成的 list
def split_datetime_weekday(t_w_column):

datetime_list = [x.split()[0] for x in t_w_column ] # 列表推导式的简写
weekday_list = [x.split()[1] for x in t_w_column]

return datetime_list, weekday_list

datetime_list, weekday_list = split_datetime_weekday(dropna1.loc[:,'time'])
dropna1.loc[:,'datetime'] = pd.to_datetime(datetime_list)
# 这里直接用了一个 .to_datetime 方法,将所有数据的改成了 datetime64 的类型
dropna1.loc[:, 'weekday'] = weekday_list
dropna1.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 6577 entries, 2 to 6578
Data columns (total 9 columns):
time 6577 non-null object
cardno 6577 non-null object
drugId 6577 non-null object
drugName 6577 non-null object
saleNumber 6577 non-null object
virtualmoney 6577 non-null object
actualmoney 6577 non-null object
datetime 6577 non-null datetime64[ns]
weekday 6577 non-null object
dtypes: datetime64ns, object(8)
memory usage: 513.8+ KB

2.5 数据类型转换

dropna1.loc[:,'saleNumber'] = dropna1['saleNumber'].astype('float64')
dropna1.loc[:,'virtualmoney'] = dropna1['virtualmoney'].astype('float64')
dropna1.loc[:,'actualmoney'] = dropna1['actualmoney'].astype('float64')
  • 之前的改变数据类型的方式有报错,还是改成了文档推荐的赋值方式,两种区别也简要写了一下,详细可以阅读官方文档

  1. 是先索引第一层级[colom1],返回了 Dataframe 的对象,然后对这个对象再次索引 [column2],所以可以看做是一种连续两次的线性操作
  2. loc 索引则是利用了一个组合的索引,pandas 可以把这个返回对象当做一个整体处理,同时速度上也比第一种快。
dropna1.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 6577 entries, 2 to 6578
Data columns (total 9 columns):
time 6577 non-null object
cardno 6577 non-null object
drugId 6577 non-null object
drugName 6577 non-null object
saleNumber 6577 non-null float64
virtualmoney 6577 non-null float64
actualmoney 6577 non-null float64
datetime 6577 non-null datetime64[ns]
weekday 6577 non-null object
dtypes: datetime64ns, float64(3), object(5)
memory usage: 513.8+ KB

2.6 排序

dropna1.sort_values("time").head(3)

3 简单数据分析

本节是对一些指标的分析,我自己也加了一些可视化的内容,可视化不是重点,只是简单画了一下,并没有做美化,甚至连标签都没改。。

  • 月均消费次数
  • 月均消费金额
  • 客单价
  • 消费趋势

3.1 月均消费次数

要点:

  • 同一个日期和同一个社保卡号的多个消费记录算作一次消费,
  • 可以将单独两列抽出来单独分析,先去重复,然后再计数
  • 这个分析可以画一个每个月的消费次数的折线图

data_consume_unique 是将 datetime cardno 去重后复制出来的新的 Dataframe

data_consume_unique = dropna1.drop_duplicates(subset=['datetime', 'cardno']).copy(deep = True)
consume_time_date_ser = pd.Series(list(data_consume_unique['cardno']), index = data_consume_unique['datetime'])
# 排序之后将日期结尾 减去 日期开头,计算总天数
date_interval = consume_time_date_ser.sort_index().index[-1]-consume_time_date_ser.sort_index().index[0]
# 利用总天使,计算总月数,其中 timedelta 数据类型的 attribute 参见官方文档
# 这里用到了 days,提取出了总天数
month_count = date_interval.days/30 + 1 # 我觉得这个月份应该是要 + 1 的,猴子老师课程里面没加

month_consume = consume_time_date_ser.count()/month_count
print month_consume

771

  • 通过时间序列的 month 分组,之后用 .count() 来计算组内总和,也就是每月消费次数
month_time = consume_time_date_ser.groupby(consume_time_date_ser.index.month).count()
plt.plot(month_time.index, month_time)
plt.show()

3.2 月均消费金额

  • 月均消费金额 = 总消费金额 / 月份数
  • 这里可以画一个每月消费总额的柱状图
total_money = dropna1['actualmoney'].sum()
month_money = total_money / month_count
print month_money # 43518.6085714

43518.6085714

  • 构建以实收金额 actualmoney 的时间序列 data_consume_actual
data_consume_actual = pd.Series(list(dropna1['actualmoney']), index = list(dropna1['datetime']))
  • 通过 month 分组,对组内数据进行求和,求和结果为每月的实收金额总和
month_consume = data_consume_actual.groupby(data_consume_actual.index.month).sum()
plt.plot(month_consume.index, month_consume)
plt.show()

3.3 客单价

  • 客单价(per customer transaction)是指商场(超市)每一个顾客平均购买商品的金额,客单价也即是平均交易金额。
consume_num = len(dropna1['cardno'].unique())
print consume_num # 所有不重复医保卡号码,总数量
pct = total_money / consume_num
print pct # 客单价

2426
125.568944765

3.4 消费趋势

  • 分组也是根据 week 进行的分组,之后求和,类似于上面按照月的来求和
  • 画出随着 week 变化与 实收金额 actualmoney 的变化趋势
week_consume = data_consume_actual.groupby(data_consume_actual.index.week).sum()
plt.plot(week_consume.index, week_consume)
plt.show()

这个图还是有点问题的,因为开头几天还归属2015年的周数计算,所以信息中有53周的数据,折线也就直接拉到53周了。

与网友相关的关于切片,复制之类的讨论:

欢迎关注我的微信公众号 :practice_yuyang,不定期更新数据分析学习心得,学习过程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容