本文來自于 猴子數(shù)據(jù)分析社群的通關(guān)作業(yè)秤朗,因?yàn)檎n程是用 R 語言教的丹莲,我是用 Python 實(shí)現(xiàn)了一遍春寿,所以參考的文檔也都列了出來朗涩,總結(jié)的也挺不容易的,歡迎同學(xué)吐槽绑改。
- 數(shù)據(jù)來源與猴子聊人物的微信公眾號(hào)谢床,這里也直接列出來方便一點(diǎn)的鏈接 https://pan.baidu.com/s/1qYFL8O8#list/path=%2F
文章主要簡(jiǎn)單實(shí)現(xiàn)了一遍初級(jí)的數(shù)據(jù)分析過程,首先是利用 pandas 讀取 excel 文件厘线,之后簡(jiǎn)單的通過去空值等過程簡(jiǎn)單處理了數(shù)據(jù)內(nèi)容识腿,最后計(jì)算了了幾個(gè)常用的業(yè)績(jī)指標(biāo)。
文章算是實(shí)現(xiàn)了一個(gè)最初級(jí)的數(shù)據(jù)分析過程造壮,比起啃一本完整的教材還是不能實(shí)踐渡讼,不如今早開始將知識(shí)投入到使用過程中,形成宏觀知識(shí)架構(gòu)方便后續(xù)補(bǔ)充學(xué)習(xí)耳璧,同時(shí)還能熟悉知識(shí)點(diǎn)和知識(shí)點(diǎn)之間的協(xié)作過程成箫。
1. 讀取 excel
# coding=utf-8
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
file_name = "../源代碼和數(shù)據(jù)/朝陽醫(yī)院2016年銷售數(shù)據(jù).xlsx"
xls_file = pd.ExcelFile(file_name, dtype='object') # 統(tǒng)一先按照str讀入,之后轉(zhuǎn)換
table = xls_file.parse('Sheet1', dtype='object')
# file_name = "../源代碼和數(shù)據(jù)/朝陽醫(yī)院2016年銷售數(shù)據(jù).xlsx"
# table = pd.read_excel(file_name, sheeetname = 'Sheet1', dtype='object')
- 上面注釋掉的代碼是知乎網(wǎng)友 Dv Liu 提醒的另外一種讀取 excel 方法旨枯,在只讀取一個(gè) sheet 的時(shí)候很方便蹬昌。
https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_excel.html](http://link.zhihu.com/?target=https%3A//pandas.pydata.org/pandas-docs/stable/generated/pandas.read_excel.html
print type(xls_file)
print type(table)
<class 'pandas.io.excel.ExcelFile'>
<class 'pandas.core.frame.DataFrame'>
table.head()
元數(shù)據(jù)好像沒有空值,所以我自己加了兩行
2. 數(shù)據(jù)預(yù)處理
2.1 查看基本信息
print table.shape
print table.index
print table.columns
(6579, 7)
RangeIndex(start=0, stop=6579, step=1)
Index([u'購藥時(shí)間', u'社迸矢簦卡號(hào)', u'商品編碼', u'商品名稱', u'銷售數(shù)量', u'應(yīng)收金額', u'實(shí)收金額'], dtype='object')
print table.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6579 entries, 0 to 6578
Data columns (total 7 columns):
購藥時(shí)間 6577 non-null object
社痹矸罚卡號(hào) 6578 non-null object
商品編碼 6577 non-null object
商品名稱 6578 non-null object
銷售數(shù)量 6577 non-null object
應(yīng)收金額 6577 non-null object
實(shí)收金額 6577 non-null object
dtypes: object(7)
memory usage: 359.9+ KB
None
print table.count()
購藥時(shí)間 6577
社保卡號(hào) 6578
商品編碼 6577
商品名稱 6578
銷售數(shù)量 6577
應(yīng)收金額 6577
實(shí)收金額 6577
dtype: int64
2.2 列重命名
pandas 的 rename 方法 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.rename.html
col = {u'購藥時(shí)間':'time',\
u'社崩バ冢卡號(hào)':'cardno',\
u'商品編碼':'drugId',\
u'商品名稱':'drugName',\
u'銷售數(shù)量':'saleNumber',\
u'應(yīng)收金額':'virtualmoney',\
u'實(shí)收金額':'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') # 參數(shù)設(shè)定是明刷,所有值為 NA 才刪除掉
dropna1.head()
dropna2.head()
2.4 處理日期
- 一些列表推導(dǎo)式的參考內(nèi)容 https://docs.python.org/2.7/tutorial/datastructures.html?highlight=list%20comprehensions
- .to_datetime 方法 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.to_datetime.html
# 定義一個(gè)把 time 行中日期和星期分開的函數(shù),分別返回日期和星期構(gòu)成的 list
def split_datetime_weekday(t_w_column):
datetime_list = [x.split()[0] for x in t_w_column ] # 列表推導(dǎo)式的簡(jiǎn)寫
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)
# 這里直接用了一個(gè) .to_datetime 方法满粗,將所有數(shù)據(jù)的改成了 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 數(shù)據(jù)類型轉(zhuǎn)換
saleNumber virtualmoney actualmoney 三個(gè) columns 的數(shù)據(jù)類型轉(zhuǎn)換
dropna1.loc[:,'saleNumber'] = dropna1['saleNumber'].astype('float64')
dropna1.loc[:,'virtualmoney'] = dropna1['virtualmoney'].astype('float64')
dropna1.loc[:,'actualmoney'] = dropna1['actualmoney'].astype('float64')
- 之前的改變數(shù)據(jù)類型的方式有報(bào)錯(cuò)辈末,還是改成了文檔推薦的賦值方式,兩種區(qū)別也簡(jiǎn)要寫了一下,詳細(xì)可以閱讀官方文檔
http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
對(duì)比兩種索引形式 1. df[colom1][column2] 2. df.loc[index:column] 為什么 后者更好
- 是先索引第一層級(jí)[colom1]本冲,返回了 Dataframe 的對(duì)象,然后對(duì)這個(gè)對(duì)象再次索引 [column2],所以可以看做是一種連續(xù)兩次的線性操作
- loc 索引則是利用了一個(gè)組合的索引劫扒,pandas 可以把這個(gè)返回對(duì)象當(dāng)做一個(gè)整體處理檬洞,同時(shí)速度上也比第一種快。
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 排序
按銷售時(shí)間對(duì)數(shù)據(jù)進(jìn)行降序排列
老的 sort 方法好像已經(jīng)改名成為 sort_values 了沟饥,找了半天添怔。https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.sort_values.html?highlight=sort_values#pandas.DataFrame.sort_values
DataFrame.sort_values(by, axis=0, ascending=True, inplace=False, kind='quicksort', na_position='last')
dropna1.sort_values("time").head(3)
3 簡(jiǎn)單數(shù)據(jù)分析
本節(jié)是對(duì)一些指標(biāo)的分析,我自己也加了一些可視化的內(nèi)容贤旷,可視化不是重點(diǎn)广料,只是簡(jiǎn)單畫了一下,并沒有做美化幼驶,甚至連標(biāo)簽都沒改艾杏。。
- 月均消費(fèi)次數(shù)
- 月均消費(fèi)金額
- 客單價(jià)
- 消費(fèi)趨勢(shì)
3.1 月均消費(fèi)次數(shù)
要點(diǎn):
- 同一個(gè)日期和同一個(gè)社敝言澹卡號(hào)的多個(gè)消費(fèi)記錄算作一次消費(fèi)购桑,
- 可以將單獨(dú)兩列抽出來單獨(dú)分析,先去重復(fù)氏淑,然后再計(jì)數(shù)
- 這個(gè)分析可以畫一個(gè)每個(gè)月的消費(fèi)次數(shù)的折線圖
data_consume_unique 是將 datetime cardno 去重后復(fù)制出來的新的 Dataframe
data_consume_unique = dropna1.drop_duplicates(subset=['datetime', 'cardno']).copy(deep = True)
- drop_duplicates 的說明 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.drop_duplicates.html 選取 datetime 和 cardno 作為篩選的子列
- consume_time_date_ser 是將 datetime 作為 index勃蜘,構(gòu)建的 cardno 的時(shí)間序列。
- 注:構(gòu)建時(shí)間序列的內(nèi)容一定要轉(zhuǎn)換成列表假残。我自己也是探索了半天缭贡,用 Series 的數(shù)據(jù),填充的都是 NA辉懒。
consume_time_date_ser = pd.Series(list(data_consume_unique['cardno']), index = data_consume_unique['datetime'])
- 之前用了 sort_values 方法阳惹,果然也有 sort_index https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.sort_index.html,這里通過排序時(shí)間序列的 index眶俩,分別取出開始日期和結(jié)束日期
- timedelta 數(shù)據(jù)類型的 一些特性操作 https://docs.python.org/2.7/library/datetime.html?highlight=timedelta#datetime.timedelta穆端,這里用了
.days
提取出了時(shí)間區(qū)間的天數(shù)
# 排序之后將日期結(jié)尾 減去 日期開頭,計(jì)算總天數(shù)
date_interval = consume_time_date_ser.sort_index().index[-1]-consume_time_date_ser.sort_index().index[0]
# 利用總天使仿便,計(jì)算總月數(shù)体啰,其中 timedelta 數(shù)據(jù)類型的 attribute 參見官方文檔
# 這里用到了 days,提取出了總天數(shù)
month_count = date_interval.days/30 + 1 # 我覺得這個(gè)月份應(yīng)該是要 + 1 的嗽仪,猴子老師課程里面沒加
month_consume = consume_time_date_ser.count()/month_count
print month_consume
771
- 通過時(shí)間序列的 month 分組荒勇,之后用
.count()
來計(jì)算組內(nèi)總和,也就是每月消費(fèi)次數(shù)
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 月均消費(fèi)金額
- 月均消費(fèi)金額 = 總消費(fèi)金額 / 月份數(shù)
- 這里可以畫一個(gè)每月消費(fèi)總額的柱狀圖
total_money = dropna1['actualmoney'].sum()
month_money = total_money / month_count
print month_money # 43518.6085714
43518.6085714
- 構(gòu)建以實(shí)收金額 actualmoney 的時(shí)間序列 data_consume_actual
data_consume_actual = pd.Series(list(dropna1['actualmoney']), index = list(dropna1['datetime']))
- 通過 month 分組,對(duì)組內(nèi)數(shù)據(jù)進(jìn)行求和闻坚,求和結(jié)果為每月的實(shí)收金額總和
month_consume = data_consume_actual.groupby(data_consume_actual.index.month).sum()
plt.plot(month_consume.index, month_consume)
plt.show()
3.3 客單價(jià)
- 客單價(jià)(per customer transaction)是指商場(chǎng)(超市)每一個(gè)顧客平均購買商品的金額沽翔,客單價(jià)也即是平均交易金額。
consume_num = len(dropna1['cardno'].unique())
print consume_num # 所有不重復(fù)醫(yī)保卡號(hào)碼仅偎,總數(shù)量
pct = total_money / consume_num
print pct # 客單價(jià)
2426
125.568944765
3.4 消費(fèi)趨勢(shì)
- 分組也是根據(jù) week 進(jìn)行的分組跨蟹,之后求和,類似于上面按照月的來求和
- 畫出隨著 week 變化與 實(shí)收金額 actualmoney 的變化趨勢(shì)
week_consume = data_consume_actual.groupby(data_consume_actual.index.week).sum()
plt.plot(week_consume.index, week_consume)
plt.show()
這個(gè)圖還是有點(diǎn)問題的橘沥,因?yàn)殚_頭幾天還歸屬2015年的周數(shù)計(jì)算窗轩,所以信息中有53周的數(shù)據(jù),折線也就直接拉到53周了座咆。
與網(wǎng)友相關(guān)的關(guān)于切片痢艺,復(fù)制之類的討論:
歡迎關(guān)注我的微信公眾號(hào) :practice_yuyang,不定期更新數(shù)據(jù)分析學(xué)習(xí)心得介陶,學(xué)習(xí)過程堤舒。