library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(ggplot2)
library(chron)
library(reshape2)
makdata<-read.csv("E:/Rspace/data/某超市销售数据.csv",head=T)
knitr::kable(head(makdata,15))
| 收银员名称 | 单据号 | 销售时间 | 商品货号 | 商品名称 | 销售单价 | 销售数量 | 销售金额 |
|---|---|---|---|---|---|---|---|
| 陈晨 | 20000001 | 2.014102e+13 | 14-91 | 绿箭口香糖 | 1.7 | 1 | 1.7 |
| 陈晨 | 20000002 | 2.014102e+13 | 994000522 | 百乐宝奶昔 巧克力口味雪糕 | 4.4 | 1 | 4.4 |
| 陈晨 | 20000003 | 2.014102e+13 | 508-813 | 多力葵花籽油 | 86.9 | 1 | 86.9 |
| 陈晨 | 20000004 | 2.014102e+13 | 504-736 | 海天酱油(金标生抽王) | 7.2 | 1 | 7.2 |
| 陈晨 | 20000005 | 2.014102e+13 | 2406-39 | 雀巢丝滑拿铁咖啡饮料 | 5.6 | 1 | 5.6 |
| 陈晨 | 20000006 | 2.014102e+13 | 1052-848 | 卫岗原味低脂酸奶 | 4.3 | 1 | 4.3 |
| 陈晨 | 20000006 | 2.014102e+13 | 941-180 | 五香茶叶蛋(小吃) | 1.5 | 2 | 3.0 |
| 陈晨 | 20000007 | 2.014102e+13 | 9003-415 | 白猫洗洁精 | 5.0 | 1 | 5.0 |
| 陈晨 | 20000007 | 2.014102e+13 | 504-51 | 恒顺白醋 | 4.6 | 1 | 4.6 |
| 陈晨 | 20000007 | 2.014102e+13 | 996300578 | 雕牌加香透明皂 | 8.5 | 1 | 8.5 |
| 陈晨 | 20000007 | 2.014102e+13 | 996300578 | 雕牌加香透明皂 | 8.5 | 1 | 8.5 |
| 陈晨 | 20000007 | 2.014102e+13 | 5001-502 | 华润苏果中号购物袋 | 0.2 | 1 | 0.2 |
| 陈晨 | 20000007 | 2.014102e+13 | 8002-40 | 洁云福瑞200抽塑包面纸-单包装(大幅) | 6.0 | 1 | 6.0 |
| 陈晨 | 20000008 | 2.014102e+13 | 508-157 | 金龙鱼黄金比例食用调和油 | 60.9 | 1 | 60.9 |
| 陈晨 | 20000009 | 2.014102e+13 | 9214-446 | 奥妙净蓝全效水清莲香洗衣粉 | 12.2 | 1 | 12.2 |
str(makdata)
## 'data.frame': 65535 obs. of 8 variables:
## $ 收银员名称: Factor w/ 5 levels " 陈晨"," 刘珍霞",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ 单据号 : int 20000001 20000002 20000003 20000004 20000005 20000006 20000006 20000007 20000007 20000007 ...
## $ 销售时间 : num 2.01e+13 2.01e+13 2.01e+13 2.01e+13 2.01e+13 ...
## $ 商品货号 : Factor w/ 2240 levels " 1-301"," 1-303",..: 236 1555 813 785 602 75 1352 1192 780 2190 ...
## $ 商品名称 : Factor w/ 2125 levels " 100支家庭装吸管",..: 1082 122 316 487 1419 1775 1818 116 598 293 ...
## $ 销售单价 : num 1.7 4.4 86.9 7.2 5.6 4.3 1.5 5 4.6 8.5 ...
## $ 销售数量 : num 1 1 1 1 1 1 2 1 1 1 ...
## $ 销售金额 : num 1.7 4.4 86.9 7.2 5.6 4.3 3 5 4.6 8.5 ...
发现销售时间、商品名称、销售数量、销售金额中有不规范信息,需要进行清理,并检查缺失值。尝试用which()根据商品名称等检索时,无法检索,发现商品名称前有空格。所以对数据进行以下操作:
sum(is.na(makdata))
## [1] 0
maktime<-as.character(makdata$销售时间)
makdata$销售时间<-strptime(maktime,"%Y%m%d%H%M%S")
makdata$商品名称<-sub("\\s","",makdata$商品名称, fixed = F)
makdata$商品货号<-sub("\\s","",makdata$商品货号,fixed = F)
发现、查看并删除无效商品:
invdata1<-subset(makdata,makdata$商品名称=="合计分舍去")
nrow(invdata1)
## [1] 618
invdata2<-subset(makdata,makdata$商品名称=="积点印花")
nrow(invdata2)
## [1] 265
makdata<-subset(makdata,makdata$商品名称!="合计分舍去"&makdata$商品名称!="积点印花")
summary(makdata)
## 收银员名称 单据号 销售时间
## 陈晨 :31088 Min. :20000001 Min. :2014-10-18 07:20:43
## 刘珍霞 : 9968 1st Qu.:20007943 1st Qu.:2014-11-17 19:54:49
## 欧阳莉芳:20946 Median :20016072 Median :2014-12-25 11:28:42
## 王金萍 : 2357 Mean :20016018 Mean :2014-12-24 10:28:34
## 朱姣艳 : 293 3rd Qu.:20023996 3rd Qu.:2015-01-30 09:41:53
## Max. :20031878 Max. :2015-02-28 18:43:36
## 商品货号 商品名称 销售单价 销售数量
## Length:64652 Length:64652 Min. : 0.000 Min. :-20.000
## Class :character Class :character 1st Qu.: 2.800 1st Qu.: 1.000
## Mode :character Mode :character Median : 4.300 Median : 1.000
## Mean : 7.359 Mean : 1.174
## 3rd Qu.: 8.000 3rd Qu.: 1.000
## Max. :660.000 Max. :300.000
## 销售金额
## Min. :-288.000
## 1st Qu.: 3.000
## Median : 4.500
## Mean : 8.167
## 3rd Qu.: 8.500
## Max. : 900.000
同一订单中很多单品销售数量没有合并,所以需要合并同一订单中商品,同时发现存在退货产品,同一订单中的退货可以在合并同一订单商品时解决了,而跨订单的退货很难匹配消除,可以查看整体数量,退货不多可以直接删除,对整体影响不大,所以对数据以下操作:
valdata<-makdata %>%
select(c(销售数量,销售金额)) %>%
aggregate(by=list(单据号=makdata$单据号,商品货号=makdata$商品货号),FUN=sum) %>%
arrange(单据号) %>%
merge(unique(makdata[,1:5]),by=c("单据号","商品货号"),all=FALSE) %>%
mutate(销售单价=销售金额/销售数量)
nrow(subset(valdata,valdata$销售金额<0))
## [1] 74
valdata<-subset(valdata,valdata$销售金额>0)
summary(valdata)
## 单据号 商品货号 销售数量 销售金额
## Min. :20000001 Length:57563 Min. : 0.120 Min. : 0.030
## 1st Qu.:20007878 Class :character 1st Qu.: 1.000 1st Qu.: 3.200
## Median :20015887 Mode :character Median : 1.000 Median : 5.000
## Mean :20015930 Mean : 1.318 Mean : 9.189
## 3rd Qu.:20023918 3rd Qu.: 1.000 3rd Qu.: 9.500
## Max. :20031878 Max. :300.000 Max. :900.000
## 收银员名称 销售时间 商品名称
## 陈晨 :27430 Min. :2014-10-18 07:20:43 Length:57563
## 刘珍霞 : 8836 1st Qu.:2014-11-17 17:43:11 Class :character
## 欧阳莉芳:18891 Median :2014-12-24 13:15:49 Mode :character
## 王金萍 : 2141 Mean :2014-12-24 01:03:25
## 朱姣艳 : 265 3rd Qu.:2015-01-29 19:42:59
## Max. :2015-02-28 18:43:36
## 销售单价
## Min. : 0.030
## 1st Qu.: 2.900
## Median : 4.400
## Mean : 7.386
## 3rd Qu.: 8.200
## Max. :660.000
其中valdata(validdata),是对原始数据基本数据清理后的整洁数据。后面很多数据都将来自这里。根据summary()信息,对很多chr变量因子化会方便以后分析和作图,所以:
valdata[,1]<-as.factor(valdata[,1])
valdata[,2]<-as.factor(valdata[,2])
valdata[,5]<-as.factor(valdata[,5])
valdata[,7]<-as.factor(valdata[,7])
此时得到整洁的数据valdata,下面针对数据特征进行分析
根据源数据提供的每笔交易的时间,订单号,商品名称,销售量和销售额的信息可以通过简单的数据变型和加工得到以下易于进行二次分析的变量:
时间类型数据:时间序列,和所在日、月、周、与小时 交易类型数据:销售金额,销售商品数,顾客数 商品信息数据:商品名称,商品销售量
本文针对这三种类型数据进行不同组合,提供了以下几个分析角度: 1.时间类型数据与交易类型数据结合(附图1-附图5) 1.1(销售金额、商品数按种类、顾客数)-(2014-10-18至2015-02-28)关系 1.2(销售金额、商品数按种类、顾客数)-(1日-31日)关系 1.3(销售金额、商品数按种类、顾客数)-(10月-2月)关系 1.4(销售金额、商品数按种类、顾客数)-(周一-周日)关系 1.5(销售金额、商品数按种类、顾客数)-(6时-21时)关系
2.时间类型数据与商品信息数据(附图6-附图10) 2.1热销产品-(2014-10-18至2015-02-28)关系 2.2热销产品-(1日-31日)关系 2.3热销产品-(10月-2月)关系 2.4热销产品-(周一-周日)关系 2.5热销产品-(6时-21时)关系
3.交易类型数据与商品信息数据(附图11) 3.1销售量-商品类型数目关系 3.2关联规则挖掘
这里使用了chron包的days(),weekdays()等直接从POSIX格式时间获得对应时间元素的函数,并为方便后续作图将各因子设为有序因子。
valdata<-mutate(valdata,byday=days(销售时间),byweek=weekdays(销售时间),bymonths=months(销售时间),byhours=hours(销售时间))
valdata[,9]<-as.factor(valdata[,9])
valdata[,10]<-factor(valdata[,10],order=TRUE,levels = c("星期一","星期二","星期三","星期四","星期五","星期六","星期日"))#
valdata[,11]<-factor(valdata[,11],order=TRUE,levels = c("十月","十一月","十二月","一月","二月"))
valdata[,12]<-as.factor(valdata[,12])
valdata$销售时间<-as.Date(valdata$销售时间)
1.1(销售金额、商品数按种类、顾客数)-(2014-10-18至2015-02-28)关系 首先根据销售时间对valdata中数据进行金额和数量汇总。 这里使用了dplyr包中的group_by(),summarise(),和管道符%>%,分别进行分组,汇总和结果传递。其中每一订单号视为一个独立用户的购买,所以用不同的订单号计算顾客数。
smbt<-tapply(valdata$销售金额,valdata$销售时间,sum)
snbt<-tapply(valdata$单据号,valdata$销售时间,length)
sbt<-rbind(smbt,snbt)%>%t()%>%as.data.frame()
names(sbt)<-c("销售金额","商品数按种类")
sbt$时间<-rownames(sbt)
rownames(sbt)<-NULL
sbt$时间<-strptime(sbt$时间,"%Y-%m-%d")
spbt<-valdata%>%
group_by(销售时间)%>%
summarise(订单数=n_distinct(单据号))
names(spbt)<-c("时间","顾客数")
sbt<-cbind(sbt,spbt[,2])
str(sbt)
## 'data.frame': 134 obs. of 4 variables:
## $ 销售金额 : num 9031 5669 4104 3105 4096 ...
## $ 商品数按种类: num 985 699 597 415 473 448 530 587 633 403 ...
## $ 时间 : POSIXlt, format: "2014-10-18" "2014-10-19" ...
## $ 顾客数 : int 505 372 332 236 263 263 272 328 342 224 ...
作图
head(sbt,5)
## 销售金额 商品数按种类 时间 顾客数
## 1 9030.78 985 2014-10-18 505
## 2 5669.19 699 2014-10-19 372
## 3 4103.67 597 2014-10-20 332
## 4 3105.26 415 2014-10-21 236
## 5 4095.77 473 2014-10-22 263
sbt[,c(1,2,4)]<-scale(sbt[,c(1,2,4)])
sbt$时间<-as.Date(sbt$时间)
md<-melt(sbt,id.vars=c("时间"),
measure.vars=c("销售金额", "商品数按种类", "顾客数"),
variable.name="项目类型", value.name="值")
x<-md$时间
y<-md$值
z<-md$项目类型
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="标准化后值",x="时间",color="项目类型")+
theme(legend.position=c(0.5,1),legend.justification=c(0.5,1))+
ggtitle("按时间销售情况汇总图")
从该图可以发现: 1.2月18日明显的销售额和顾客数背离,猜测该日为节日,采购年货导致客单价提高,后续可以继续验证 2.每日销售具有波浪式周期性,周期为一天 3.2014-11-01销售明显下滑,猜测该日为双十一活动日,购买显著下降 4.10月-2月销售整体上呈现先下降后上升的特征,猜测和气温有关
同理按日
smbd<-tapply(valdata$销售金额,valdata$byday,sum)
snbd<-tapply(valdata$单据号,valdata$byday,length)
sbd<-valdata%>%
select(销售时间,byday)%>%
unique()%>%
.$byday%>%
table()%>%
rbind(smbd,snbd,.)%>%
t()%>%
as.data.frame()
names(sbd)<-c("销售金额","商品数按种类","频数")
sbd<-sbd%>%
mutate(该号日均销售额=销售金额/频数,该号日均销售商品数=商品数按种类/频数)%>%
cbind(时间=row.names(.),.)
row.names(sbd)=NULL
spbd<-valdata%>%
group_by(byday)%>%
summarise(订单数=n_distinct(单据号))
names(spbd)<-c("时间","顾客数")
sbd<-cbind(sbd,spbd[,2])
sbd$时间<-factor(sbd$时间,order=TRUE,levels = c(1:31))
str(sbd)
## 'data.frame': 31 obs. of 7 variables:
## $ 时间 : Ord.factor w/ 31 levels "1"<"2"<"3"<"4"<..: 1 2 3 4 5 6 7 8 9 10 ...
## $ 销售金额 : num 15833 14534 12057 12152 12760 ...
## $ 商品数按种类 : num 1828 1717 1424 1537 1545 ...
## $ 频数 : num 4 4 4 4 4 4 4 4 4 4 ...
## $ 该号日均销售额 : num 3958 3633 3014 3038 3190 ...
## $ 该号日均销售商品数: num 457 429 356 384 386 ...
## $ 顾客数 : int 951 884 812 878 855 948 1005 947 971 970 ...
head(sbd,5)
## 时间 销售金额 商品数按种类 频数 该号日均销售额 该号日均销售商品数 顾客数
## 1 1 15833.14 1828 4 3958.285 457.00 951
## 2 2 14533.61 1717 4 3633.403 429.25 884
## 3 3 12056.84 1424 4 3014.210 356.00 812
## 4 4 12152.15 1537 4 3038.037 384.25 878
## 5 5 12760.02 1545 4 3190.005 386.25 855
sbd[,5:7]<-scale(sbd[,5:7])
md<-melt(sbd, id.vars=c("时间"),
measure.vars=c("该号日均销售额", "该号日均销售商品数", "顾客数"),
variable.name="项目类型", value.name="值")
x<-md$时间
y<-md$值
z<-md$项目类型
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="标准化后值",x="时间",color="项目类型")+
theme(legend.position=c(0,1),legend.justification=c(0,1))+
ggtitle("按日期销售情况汇总图")
从该图可以发现: 1.18号销售额远好于其它日期,猜测为特定节日,查看日历验证为2015年除夕,为采购年货高峰。 2.日均销售额、商品数、顾客数有波浪式周期性,周期类似 3.每月月中销售普遍好于月初和月末,猜测可能人月初发工资,因而有月中高于月初和月末的现象 4.月中客单价明显高于月中和月末,猜测同上,刚发工资购买力较高。
同理按周
smbw<-tapply(valdata$销售金额,valdata$byweek,sum)
snbw<-tapply(valdata$单据号,valdata$byweek,length)
sbw<-valdata%>%
select(销售时间,byweek)%>%
unique()%>%
.$byweek%>%
table()%>%
rbind(smbw,snbw,.)%>%
t()%>%
as.data.frame()
names(sbw)<-c("销售金额","商品数按种类","频数")
sbw$时间<-row.names(sbw)
row.names(sbw)=NULL
sbw<-mutate(sbw,日均销售额=销售金额/频数,日均销售商品数=商品数按种类/频数)
spbw<-valdata%>%
group_by(byweek)%>%
summarise(订单数=n_distinct(单据号))
names(spbw)<-c("时间","顾客数")
sbw<-cbind(sbw,spbw[,2])
sbw$时间<-factor(sbw$时间,order=TRUE,levels = c("星期一","星期二","星期三","星期四","星期五","星期六","星期日"))
head(sbw,5)
## 销售金额 商品数按种类 频数 时间 日均销售额 日均销售商品数 顾客数
## 1 65838.90 7233 19 星期一 3465.205 380.6842 4033
## 2 72619.70 7559 19 星期二 3822.089 397.8421 4218
## 3 67517.55 7568 19 星期三 3553.555 398.3158 4186
## 4 75441.84 7991 19 星期四 3970.623 420.5789 4403
## 5 70081.75 7878 19 星期五 3688.513 414.6316 4325
sbw[,5:7]<-scale(sbw[,5:7])
md<-melt(sbw, id.vars=c("时间"),
measure.vars=c("日均销售额", "日均销售商品数", "顾客数"),
variable.name="项目类型", value.name="值")
x<-md$时间
y<-md$值
z<-md$项目类型
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="标准化后值",x="时间",color="项目类型")+
theme(legend.position=c(0,1),legend.justification=c(0,1))+
ggtitle("按周销售情况汇总图")
从该图可以发现: 1.日均销售额、商品数和顾客数具有波浪式周期性,周期为1天 2.周末的销售情况远好于工作日 3.销售随着周一到周五递升,周六达到高峰
同理按月
smbm<-tapply(valdata$销售金额,valdata$bymonths,sum)
snbm<-tapply(valdata$单据号,valdata$bymonths,length)
sbm<-valdata%>%
select(销售时间,bymonths)%>%
unique()%>%
.$bymonths%>%
table()%>%
rbind(smbm,snbm,.)%>%
t()%>%
as.data.frame()
names(sbm)<-c("销售金额","商品数按种类","频数")
sbm$时间<-row.names(sbm)
row.names(sbm)=NULL
sbm<-mutate(sbm,该月日均销售额=销售金额/频数,该月日均销售商品数=商品数按种类/频数)
spbm<-valdata%>%
group_by(bymonths)%>%
summarise(订单数=n_distinct(单据号))
names(spbm)<-c("时间","顾客数")
sbm<-cbind(sbm,spbm[,2])
sbm$时间<-factor(sbm$时间,order=TRUE,levels = c("十月","十一月","十二月","一月","二月"))
head(sbm,5)
## 销售金额 商品数按种类 频数 时间 该月日均销售额 该月日均销售商品数
## 1 55174.45 7337 14 十月 3941.032 524.0714
## 2 93858.53 12627 30 十一月 3128.618 420.9000
## 3 89763.30 11826 31 十二月 2895.590 381.4839
## 4 115335.64 12335 31 一月 3720.505 397.9032
## 5 174797.29 13438 28 二月 6242.760 479.9286
## 顾客数
## 1 4027
## 2 6906
## 3 6513
## 4 6887
## 5 7349
sbm[,5:7]<-scale(sbm[,5:7])
md<-melt(sbm, id.vars=c("时间"),
measure.vars=c("该月日均销售额", "该月日均销售商品数", "顾客数"),
variable.name="项目类型", value.name="值")
x<-md$时间
y<-md$值
z<-md$项目类型
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="标准化后值",x="时间",color="项目类型")+
theme(legend.position=c(0.5,1),legend.justification=c(0.5,1))+
ggtitle("按月销售情况汇总图")
从该图可以发现: 1.该月日均销售额、该月日均销售数、顾客数不同月销售差异较大 2.日均销售额和日均商品数在十二月达到低点回升,猜测可能随温度降低,人们能量消耗降低,去超市频次不变而单次购买变少。 3.一月二月虽然气温依然很低却销售回升可能和节日有关 4.二月销售额显著高于其他月份,猜测节日采购年货有关 5.二月顾客数没有随销售额显著增加,猜测该超市为社区型中小超市,消费群体稳定
同理按小时
smbh<-tapply(valdata$销售金额,valdata$byhours,sum)
snbh<-tapply(valdata$单据号,valdata$byhours,length)
sbh<-valdata%>%
select(销售时间,byhours)%>%
unique()%>%
.$byhours%>%
table()%>%
rbind(smbh,snbh,.)%>%
t()%>%
as.data.frame()
names(sbh)<-c("销售金额","商品数按种类","频数")
sbh$时间<-row.names(sbh)
row.names(sbh)=NULL
sbh<-mutate(sbh,时均销售额=销售金额/频数,时均销售商品数=商品数按种类/频数)
spbh<-valdata%>%
group_by(byhours)%>%
summarise(订单数=n_distinct(单据号))
names(spbh)<-c("时间","顾客数")
sbh<-cbind(sbh,spbh[,2])
sbh$时间<-factor(sbh$时间,order=TRUE,levels = c(6:21))
head(sbh,5)
## 销售金额 商品数按种类 频数 时间 时均销售额 时均销售商品数 顾客数
## 1 103.30 11 7 6 14.75714 1.571429 9
## 2 13539.39 1791 127 7 106.60937 14.102362 1168
## 3 16442.87 1975 131 8 125.51809 15.076336 1285
## 4 27189.02 2455 134 9 202.90313 18.320896 1458
## 5 29887.62 2668 133 10 224.71895 20.060150 1600
sbh[,5:7]<-scale(sbh[,5:7])
md<-melt(sbh, id.vars=c("时间"),
measure.vars=c("时均销售额", "时均销售商品数", "顾客数"),
variable.name="项目类型", value.name="值")
x<-md$时间
y<-md$值
z<-md$项目类型
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="标准化后值",x="时间",color="项目类型")+
theme(legend.position=c(0.5,1),legend.justification=c(0.5,1))+
ggtitle("按小时销售情况汇总图")
从该图可以发现: 1.销售时间从早上6点到21点 2.时均销售额、时均商品数、顾客数在不同时段间没有明显差异 3.11-15点时间段,为销售低点;17-20点为销售高点。
2.时间类型数据与商品信息数据
统计商品销售信息 整体产品销售量情况,包括销量前10的产品名称,及不同销售量的产品分布情况
snbp<-valdata%>%
group_by(商品名称)%>%
summarise(销售量=n())%>%
arrange(desc(销售量))
head(snbp,10)
## Source: local data frame [10 x 2]
##
## 商品名称 销售量
## (fctr) (int)
## 1 烤肠 2352
## 2 华润苏果中号购物袋 1806
## 3 怡宝纯净水 694
## 4 五香鸡蛋 594
## 5 可口可乐汽水 530
## 6 甜不辣 472
## 7 苏烟(五星红杉树) 423
## 8 百事可乐 395
## 9 五香茶叶蛋(小吃) 373
## 10 华润苏果大号购物袋 321
str(snbp)
## Classes 'tbl_df', 'tbl' and 'data.frame': 2115 obs. of 2 variables:
## $ 商品名称: Factor w/ 2115 levels "100支家庭装吸管",..: 903 651 1982 1812 914 1649 1600 134 1811 649 ...
## $ 销售量 : int 2352 1806 694 594 530 472 423 395 373 321 ...
可以看到在所有时间内,销量前十的产品为:烤肠,中号购物袋,怡宝纯净水等。通过head(snbp,n)可以继续查看销量前n的产品,发现热销产品主要以小额的食品饮料为主。符合社区小型超市的销售特征。 ##词云展示热销品## 安装方式:devtools::install_github(“lchiffon/wordcloud2”) 数据:snbp(sell num by produt)
由于产品总数有2千多种,不方便分析所有产品的销售与时间的关系。所以方便起见,首先选取热销的50件商品为分析样本,按时间类型分别分析热销50件商品与销售时间/日期/月/周/时的销售。 选取总销售量前50的产品从总销量排名数据snbp中选择销量前50的产品名称为数据hsp50(hot sell product 50),方便后续通过filter匹配产品名称选取特定分析样本。
hsp50<-snbp[1:50,1]
hsp50<-hsp50$商品名称
3.交易类型数据与商品信息数据
snbp<-as.data.frame(snbp)
snd<-snbp%>%
group_by(snbp[,2])%>%
summarise(商品数=n_distinct(商品名称))
names(snd)<-c("销售量","商品数")
ggplot(snd,aes(snd$销售量,snd$商品数))+
geom_line(size=1)+
xlim(0,150)+
labs(y="商品数目",x="销售量")+
ggtitle("不同销售量的商品类型数分布")
## Warning: Removed 40 rows containing missing values (geom_path).
从该图可以发现,不同销售量的商品类型数符合典型的幂率分布或者说长尾效应,大部分产品的销售量都小于50。这里除了受现实中的长尾效应影响,也和产品能分类过于详细和数据量有关,本次数据有2115(按商品名称)种商品,31682条交易数据,会加剧长尾效应。
hsbt<-valdata%>%
select(商品名称,销售数量,销售时间)%>%
dcast(销售时间~商品名称,value.var="销售数量",sum)%>%
as.data.frame()
md<-hsbt%>%
melt(id.vars=c("销售时间"),variable.name="商品名称", value.name="销售数量")%>%
filter(商品名称 %in% hsp50)
x<-md$商品名称
y<-md$销售数量
z<-md$销售时间
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="销售数量",x="热销50产品",color="日期")+
theme(legend.position=c(1,1),legend.justification=c(1,1))+
theme(axis.text.x=element_text(angle=90,hjust=1))+
ggtitle("热销品按时间销售情况图")
str(hsbt)
## 'data.frame': 134 obs. of 2116 variables:
## $ 销售时间 : Date, format: "2014-10-18" "2014-10-19" ...
## $ 100支家庭装吸管 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 2粒南孚5#碱电池15A2B : num 1 1 0 2 1 2 0 0 1 1 ...
## $ 2粒南孚7#碱电池24A2B : num 1 0 1 0 0 0 0 0 1 4 ...
## $ 2粒装红松下电池1# : num 0 0 0 2 0 0 0 3 0 0 ...
## $ 2粒装红松下电池5# : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 2粒装青松下1号电池 : num 0 0 0 0 0 0 1 1 1 0 ...
## $ 42%双沟大曲(小青花) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 42%苏酒 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 45%五粮春酒(新) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 4粒装青松下5号电池 : num 0 0 0 0 0 0 0 0 4 0 ...
## $ 52%五粮液酒(新) : num 0 0 0 0 1 0 0 0 0 0 ...
## $ 555(硬金) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 555(硬金.锐) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 5奔涌西瓜味无糖口香糖 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 5青桔汽水味无糖口香糖 : num 0 0 0 0 0 0 0 1 0 0 ...
## $ 99稻花香宴酒 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ baolilai丁焕气 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ B级碳烧雕刻筷10入(竹) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ CNY果王五福临门礼盒 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ C恒源祥男薄棉休闲才船袜 : num 0 0 0 3 0 0 0 0 0 0 ...
## $ C耐品精梳棉男袜NM5138 : num 2 0 0 0 0 0 0 1 0 0 ...
## $ C耐品精梳棉女袜NF5259 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ C耐品精梳棉女袜NF5265 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ C耐品精梳棉运动袜NM5044 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ C耐品精梳棉运动袜NM5054 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ C耐品女袜NF5246 : num 0 0 0 0 0 0 1 0 0 0 ...
## $ DJY男女彩条毛口吹气棉拖854多色CX : num 0 0 0 0 0 0 0 0 0 1 ...
## $ DZC男毛口水彩印棉拖4371灰 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ DZC男童绣熊猫地板拖4339咖啡 : num 1 0 0 0 0 0 0 0 0 0 ...
## $ DZC女毛口水彩印棉拖4373大红 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ DZC女童绣熊猫地板拖4340大红 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ D福文精纺羊毛男袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ D福文精纺羊毛女袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ D恒源祥混纺女毛袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ EGO芦荟味椰果果冻(3杯装) : num 0 0 0 0 0 0 0 0 1 0 ...
## $ EGO芒果味椰果果冻(3杯装) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ Hello Kitty马克杯 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ HUMA BEAR柳橙味牛奶饮品 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ HUMA BEAR麦芽味牛奶饮品 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ KIRIN火咖法式香草味咖啡饮料 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ KIRIN麒麟午后红茶(奶茶) : num 1 0 1 0 0 0 0 0 0 0 ...
## $ M&Ms花生牛奶巧克力豆 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ M&Ms花生巧克力 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ M&Ms牛奶巧克力 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ M&Ms牛奶巧克力豆 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ MG海洋冰泉补水面膜 : num 0 0 0 0 2 0 0 0 0 0 ...
## $ MG活氧温泉净化保湿面膜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ MG绿茶清盈祛痘面膜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ MG牛奶白滑润颜面膜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ MG左旋VC嫩白面膜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ mintia酷活无糖薄荷糖 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ mintia酷活无糖薄荷糖(超凉薄荷味) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ mintia酷活无糖薄荷糖(清爽柠檬味) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ mintia酷活无糖薄荷糖(清新葡萄味) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ N)素手浣花黑糖棒棒糖 : num 0 0 0 0 0 0 0 0 0 1 ...
## $ N)台农巧克力牛乳 : num 2 0 1 0 0 0 0 0 1 0 ...
## $ N)台农全脂牛奶 : num 1 0 0 0 0 0 0 0 0 0 ...
## $ N)星巴克咖啡星冰乐咖啡饮料 : num 1 0 0 0 0 0 0 0 2 0 ...
## $ N)星巴克摩卡星冰乐咖啡饮料 : num 0 1 0 2 0 1 0 0 2 0 ...
## $ N)义美小泡芙--牛奶(盒装) : num 1 0 2 0 1 0 0 0 0 0 ...
## $ N)义美小泡芙--巧克力(盒装) : num 1 0 0 1 0 2 0 0 0 0 ...
## $ N)张君雅捏碎面 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ N)张君雅巧克力甜甜圈 : num 1 0 0 0 1 0 0 2 2 0 ...
## $ Ob PROCOMFORT卫生棉条量多型 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ Ob PROCOMFORT卫生棉条普通型 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ Ozi缤纷巧克力豆 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ S恒源祥薄型棉女袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ S恒源祥超薄棉男袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ S恒源祥加跟棉男袜 : num 1 0 0 0 0 0 0 0 0 0 ...
## $ S恒源祥菱形花棉氨男袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ S恒源祥棉氨女袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ S恒源祥男棉氨运动袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ S恒源祥男棉袜 : num 2 1 0 0 0 0 0 1 0 0 ...
## $ S恒源祥男袜 : num 1 0 0 1 0 1 0 0 0 0 ...
## $ S恒源祥女袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ S恒源祥绣花棉氨男袜 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 阿尔卑斯单条草莓味奶糖 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 阿尔卑斯单条酸奶味软糖 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 阿尔卑斯牛奶糖 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 阿萨姆奶茶1.5L : num 2 1 1 1 1 1 0 0 0 1 ...
## $ 艾兰得Vc含片(草莓味) : num 0 0 0 0 0 1 0 0 0 0 ...
## $ 艾兰得Vc含片(桔子味) : num 0 0 0 0 0 0 0 1 0 0 ...
## $ 爱利地香草华夫块 : num 1 0 1 0 0 0 0 0 0 0 ...
## $ 爱时乐巧克力威化卷心酥 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 爱时乐威化卷心酥(芝士味) : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 爱特福84洁厕灵 : num 0 0 0 2 0 0 1 0 0 0 ...
## $ 爱特福84消毒液 : num 1 0 2 0 2 0 0 0 0 0 ...
## $ 爱之味蕃茄沙司 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 安儿乐实惠干爽小号纸尿片(3-6kg)S520 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 安儿乐实惠干爽中号纸尿片(5-10kg)ML518 : num 1 0 0 0 1 0 0 0 0 0 ...
## $ 安琪高活性干酵母 : num 0 0 0 0 0 0 0 0 0 0 ...
## $ 奥利奥草莓夹心巧克力饼干 : num 1 0 1 0 0 1 1 2 0 0 ...
## $ 奥利奥金装夹心饼干(草莓味) : num 0 0 0 0 0 0 0 0 0 1 ...
## $ 奥利奥金装夹心饼干(巧克力味) : num 1 0 0 0 0 0 0 0 0 0 ...
## $ 奥利奥金装香草慕斯味饼干 : num 0 0 0 0 0 0 0 0 0 1 ...
## $ 奥利奥巧克力夹心饼干 : num 0 0 1 1 1 0 1 0 0 1 ...
## $ 奥利奥巧轻脆薄片夹心(柠檬芝士蛋糕味) : num 1 0 0 0 1 0 0 0 0 0 ...
## $ 奥利奥巧轻脆薄片夹心(浓情提拉米苏味) : num 0 0 0 0 0 0 0 0 0 0 ...
## [list output truncated]
hsbt[1:5,1:3]
## 销售时间 100支家庭装吸管 2粒南孚5#碱电池15A2B
## 1 2014-10-18 0 1
## 2 2014-10-19 0 1
## 3 2014-10-20 0 0
## 4 2014-10-21 0 2
## 5 2014-10-22 0 1
从该图有以下发现: 1.大部分热销产品不同时间的销售情况没有显著差异 2.运动型饮料、可口可乐、纯净水等饮品在10-12月份销售好于其它月份,猜测和气温降低,人们不愿消费常温饮料有关 3.烟草类产品在1-2月销售明显好于其它月份,猜测一是和节日习俗有关,也可能是企业单位年终考核总结、个体户企业年终收账在这几个月,普遍工作量和压力较大有关。
hsbd<-valdata%>%
select(商品名称,销售数量,byday)%>%
dcast(byday~商品名称,value.var="销售数量",sum)%>%
as.data.frame()
md<-hsbd%>%
melt(id.vars=c("byday"),variable.name="商品名称", value.name="销售数量")%>%
filter(商品名称 %in% hsp50)
x<-md$商品名称
y<-md$销售数量
z<-md$byday
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="销售数量",x="热销50产品",color="日期")+
theme(legend.position=c(0.6,1),legend.justification=c(0.6,1))+
theme(axis.text.x=element_text(angle=90,hjust=1))+
ggtitle("热销品按时间销售情况图")
从该图有以下发现: 1.大部分热销品在不同日期的销售最大最小值差异显著,与按周分布对比可以明显发现。 2.很多热销品在17-21号的销售较多,结合月份销售情况可以猜测2015年的春节在这附近。由于总体数据量较少,节日相关产品购买影响了本日期的销售总数,查询日历得到2015春节为2月19,也与数据相符。 3.在17-21号左右的节日采购时间段内,可以发现,美汁源果粒橙、南京系列烟、五星红杉树、卫岗大红枣利乐枕、玉溪等销售明显较多,是大家常常备的年货。
hsbw<-valdata%>%
select(商品名称,销售数量,byweek)%>%
dcast(byweek~商品名称,value.var="销售数量",sum)%>%
as.data.frame()
md<-hsbw%>%
melt(id.vars=c("byweek"),variable.name="商品名称", value.name="销售数量")%>%
filter(商品名称 %in% hsp50)
x<-md$商品名称
y<-md$销售数量
z<-md$byweek
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="销售数量",x="热销50产品",color="日期")+
theme(legend.position=c(1,1),legend.justification=c(1,1))+
theme(axis.text.x=element_text(angle=90,hjust=1))+
ggtitle("热销品按时间销售情况图")
从该图有以下发现: 1.大部分热销产品在不同周几的销售情况没有显著差别 2.如果他夹心棒棒糖在周三的销售教多,南京(硬精品)在周五销售较多,怡宝纯净水在周六销售显著增加,原因不明。 3.除南京(红)外,其它烟草制品普遍周末的销售好于工作日
hsbm<-valdata%>%
select(商品名称,销售数量,bymonths)%>%
dcast(bymonths~商品名称,value.var="销售数量",sum)%>%
as.data.frame()
md<-hsbm%>%
melt(id.vars=c("bymonths"),variable.name="商品名称", value.name="销售数量")%>%
filter(商品名称 %in% hsp50)
x<-md$商品名称
y<-md$销售数量
z<-md$bymonths
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="销售数量",x="热销50产品",color="日期")+
theme(legend.position=c(1,1),legend.justification=c(1,1))+
theme(axis.text.x=element_text(angle=90,hjust=1))+
ggtitle("热销品按时间销售情况图")
从该图中有以下发现: 1.南京(红),苏烟,红杉树,玉溪等烟类产品在一二月份中销售最多,猜测这两月有传统节日春节,所以烟草销售好。而在其它月份销售很差,表明该类产品销售有明显周期性,受节日影响较大。 2.烤肠在十一月到二月的销售最多,猜测天气变冷,很多人更喜欢吃热的烤肠 3.运动型饮料,茶类饮料,果汁类饮料,每个月的销售情况接近,而且销售总量接近,表明不同饮料类型偏好的消费者的比例可能类似,而且市场比较成熟和稳定了 4.苏果小号购物袋销售比较稳定,中号和大号购物袋在一二月份明显增多,猜测随着天气变冷,人们为了不冻手及携带方便,更愿意购买购物袋 5.如果他夹心棒棒糖在十一月的销售远好于其它月,猜测该月有光棍节,很多单身女同学会购买棒棒糖作为朋友或自己的节日礼物 #按小时
hsbh<-valdata%>%
select(商品名称,销售数量,byhours)%>%
dcast(byhours~商品名称,value.var="销售数量",sum)%>%
as.data.frame()
md<-hsbh%>%
melt(id.vars=c("byhours"),variable.name="商品名称", value.name="销售数量")%>%
filter(商品名称 %in% hsp50)
x<-md$商品名称
y<-md$销售数量
z<-md$byhours
ggplot(md,aes(x=x,y=y,color=z,group=z))+
geom_line(size=1)+
geom_point(size=2,shape=21,fill="white")+
labs(y="销售数量",x="热销50产品",color="日期")+
theme(legend.position=c(1,1),legend.justification=c(1,1))+
theme(axis.text.x=element_text(angle=90,hjust=1))+
ggtitle("热销品按时间销售情况图")
从该图中有以下发现: 1.大部分热销品在不同时段的销售情况没有显著差别 2.购物袋和怡宝纯净水分布类似,猜测为纯净水较重,买了纯净水同时一般会买塑料袋,所以相关性较强。 3.早上八九点左右康师傅茉莉蜜茶,烤肠和五香鸡蛋销售最多,猜测这个时间段为上班族的早餐时间,这三者可能是很多上班族的早餐选择。 4.下午三四点烤肠和甜不辣销售最多,猜测这个超市可能在中小学附近,下午三四点是大部分中小学休息时间,而且这两类商品应该以学生消费为主。 5.如果他夹心棒棒糖下午六点时销售最好,加强了上一条的猜想,猜测放学时间,很多女学生会选择买个棒棒糖回家。
3.2关联规则挖掘
载入关联规则分析包arules及关联规则可视化分析包arulesViz。 将(单据号,商品名称)single数据类型转换成arules包可以识别的transaction数据 格式。 删除无效的购物袋类产品。
library(arules)
## Loading required package: Matrix
##
## Attaching package: 'arules'
## The following objects are masked from 'package:base':
##
## abbreviate, write
library(arulesViz)
## Loading required package: grid
transdata<-valdata%>%
select(单据号,商品名称)%>%
.[!duplicated(.),]
transdata<-filter(transdata,!grepl("购物袋",商品名称))
transdata$商品名称<-as.character(transdata$商品名称)
#只有转成char才可以重新items显示为字符型,因为过滤掉4个商品名,但原来是factor编码未变
transrule<-as(split(transdata[,2], transdata[,1]), "transactions")
查看前10条交易,通过apriori算法和eclat算法计算关联规则,并按照Lift降序排列查看规则。
inspect(transrule[1:10])
## items
## 1 {绿箭口香糖}
## 2 {百乐宝奶昔 巧克力口味雪糕}
## 3 {多力葵花籽油}
## 4 {海天酱油(金标生抽王)}
## 5 {雀巢丝滑拿铁咖啡饮料}
## 6 {卫岗原味低脂酸奶,五香茶叶蛋(小吃)}
## 7 {白猫洗洁精,雕牌加香透明皂,恒顺白醋,洁云福瑞200抽塑包面纸-单包装(大幅)}
## 8 {金龙鱼黄金比例食用调和油}
## 9 {奥妙净蓝全效水清莲香洗衣粉}
## 10 {海飞丝丝质柔滑型去屑洗发露0025,红石洁厕灵}
## transactionID
## 1 20000001
## 2 20000002
## 3 20000003
## 4 20000004
## 5 20000005
## 6 20000006
## 7 20000007
## 8 20000008
## 9 20000009
## 10 20000010
rules<-apriori(transrule,parameter=list(support=0.001,confidence=0.01))
## Apriori
##
## Parameter specification:
## confidence minval smax arem aval originalSupport support minlen maxlen
## 0.01 0.1 1 none FALSE TRUE 0.001 1 10
## target ext
## rules FALSE
##
## Algorithmic control:
## filter tree heap memopt load sort verbose
## 0.1 TRUE TRUE FALSE TRUE 2 TRUE
##
## Absolute minimum support count: 31
##
## set item appearances ...[0 item(s)] done [0.00s].
## set transactions ...[2111 item(s), 31682 transaction(s)] done [0.01s].
## sorting and recoding items ... [465 item(s)] done [0.00s].
## creating transaction tree ... done [0.01s].
## checking subsets of size 1 2 done [0.00s].
## writing ... [14 rule(s)] done [0.00s].
## creating S4 object ... done [0.00s].
inspect(head(sort(rules, by = "lift"), 10))
## lhs rhs support confidence
## 11 {甜不辣} => {烤肠} 0.004702986 0.31567797
## 12 {烤肠} => {甜不辣} 0.004702986 0.06335034
## 9 {五香茶叶蛋(小吃)} => {烤肠} 0.002146329 0.18230563
## 10 {烤肠} => {五香茶叶蛋(小吃)} 0.002146329 0.02891156
## 13 {五香鸡蛋} => {烤肠} 0.003030112 0.16161616
## 14 {烤肠} => {五香鸡蛋} 0.003030112 0.04081633
## 1 {} => {苏烟(五星红杉树)} 0.013351430 0.01335143
## 2 {} => {百事可乐} 0.012467647 0.01246765
## 3 {} => {五香茶叶蛋(小吃)} 0.011773247 0.01177325
## 4 {} => {可口可乐汽水} 0.016602487 0.01660249
## lift
## 11 4.252257
## 12 4.252257
## 9 2.455700
## 10 2.455700
## 13 2.177008
## 14 2.177008
## 1 1.000000
## 2 1.000000
## 3 1.000000
## 4 1.000000
反复实验,发现当supp和conf均取很小时才有合适的规则,数据关联性不强。可能因为数据量和商品命名的关系,需要对商品名称进行一定集中后可能更有利于关联规则挖掘。由于汇总较为复杂,这里不进行过多无效的关联规则挖掘了。仅对support=0.001和confidence=0.01时做些可视化分析。 作出关联规则可视化图:
subrules<-head(sort(rules, by = "lift"), 10)
plot(rules,method = "graph")
1.dcast返回的data.fame格式的数据,由于自动为cast的变量“byweek”增加了新变量名称v1-v7, 2.reshape2-dplyr包中一些函数反复运用可以解决绝大部分数据整理问题 #总结 待优化: 1.多次复用的代码函数化,做成超市数据分析包,结合shiny做成超市数据在线分析app 2.时间序列分析中可以加入区域天气信息等额外信息等,研究大雨等天气对销售影响 3.报告撰写时间较急,没能完善代码解释,及更多功能
**参考资料 [1].数据整型,reshape2使用:http://seananderson.ca/2013/10/19/reshape.html [2].数据集分组,dplyr包使用:http://blog.csdn.net/sinat_26917383/article/details/506884[3].Tidy data.Hadley Wickham