Pyspark+关联规则 Kaggle购物篮分析案例

数据集地址:Market Basket Analysis | Kaggle

我的NoteBook地址:pyspark Market Basket Analysis | Kaggle

零售商期望能够利用过去的零售数据在自己的行业中进行探索,并为客户提供有关商品集的建议,这样就能提高客户参与度、改善客户体验并识别客户行为。本文将通过pyspark对数据进行导入与预处理,进行可视化分析并使用spark自带的机器学习库做关联规则学习,挖掘不同商品之间是否存在关联关系。

整体的流程思路借鉴了这个Notebook:Market Basket Analysis with Apriori🛒 | Kaggle

一、导入与数据预处理

这一部分其实没有赘述的必要,贴在这里单纯是为了熟悉pyspark的各个算子。

from pyspark import SparkContext
from pyspark.sql import functions as F, SparkSession, Column,types
import pandas as pd
import numpy as npspark = SparkSession.builder.appName("MarketBasketAnalysis").getOrCreate()
data = spark.read.csv("/kaggle/input/market-basket-analysis/Assignment-1_Data.csv",header=True,sep=";")
data = data.withColumn("Price",F.regexp_replace("Price",",",""))
data = data.withColumn("Price",F.col("Price").cast("float"))
data = data.withColumn("Quantity",F.col("Quantity").cast("int"))

预处理时间类变量

#时间变量
data = data.withColumn("Date_Day",F.split("Date"," ",0)[0])
data = data.withColumn("Date_Time",F.split("Date"," ",0)[1])data = data.withColumn("Hour",F.split("Date_Time",":")[0])
data = data.withColumn("Minute",F.split("Date_Time",":")[1])
data = data.withColumn("Year",F.split("Date_Day","\.")[2])
data = data.withColumn("Month",F.split("Date_Day","\.")[1])
data = data.withColumn("Day",F.split("Date_Day","\.")[0])convert_to_Date = F.udf(lambda x:"-".join(x.split(".")[::-1])+" ",types.StringType())data = data.withColumn("Date",F.concat(convert_to_Date(data["Date_Day"]),data["Date_Time"]))
data = data.withColumn("Date",data["Date"].cast(types.TimestampType()))data = data.withColumn("DayOfWeek",F.dayofweek(data["Date"])-1) #dayofweek这个函数中,以周日为第一天
data = data.withColumn("DayOfWeek",F.udf(lambda x:x if x!=0 else 7,types.IntegerType())(data["DayOfWeek"]))

删除负值的记录、填充缺失值并删除那些非商品的记录

# 删除price/QTY<=0的情况
data = data.filter((F.col("Price")>0) & (F.col("Quantity")>0))# 增加Total Price
data = data.withColumn("Total Price",F.col("Price")*F.col("Quantity"))data_null_agg = data.agg(*[F.count(F.when(F.isnull(c), c)).alias(c) for c in data.columns])
data_null_agg.show()# CustomerID 空值填充
data = data.fillna("99999","CustomerID")# 删除非商品的那些Item
data = data.filter((F.col("Itemname")!='POSTAGE') & (F.col("Itemname")!='DOTCOM POSTAGE') & (F.col("Itemname")!='Adjust bad debt') & (F.col("Itemname")!='Manual'))

二、探索性分析

1、总体销售情况

本节开始,将会从订单量(No_Of_Trans)、成交量(Quantity)与成交额(Total Price)三个维度,分别从多个角度进行数据可视化与分析。

import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
from collections import Counter## 总体分析
data_ttl_trans = data.groupby(["BillNo"]).sum("Quantity","Total Price").withColumnRenamed("sum(Quantity)","Quantity").withColumnRenamed("sum(Total Price)","Total Price").toPandas()
data_ttl_item = data.groupby(["Itemname"]).sum("Quantity","Total Price").withColumnRenamed("sum(Quantity)","Quantity").withColumnRenamed("sum(Total Price)","Total Price").toPandas()
data_ttl_time = data.groupby(["Year","Month"]).sum("Quantity","Total Price").withColumnRenamed("sum(Quantity)","Quantity").withColumnRenamed("sum(Total Price)","Total Price").toPandas()
data_ttl_weekday = data.groupby(["DayOfWeek"]).sum("Quantity","Total Price").withColumnRenamed("sum(Quantity)","Quantity").withColumnRenamed("sum(Total Price)","Total Price").toPandas()
data_ttl_country = data.groupby(["Country"]).sum("Quantity","Total Price").withColumnRenamed("sum(Quantity)","Quantity").withColumnRenamed("sum(Total Price)","Total Price").toPandas()
data_ttl_country_time = data.groupby(["Country","Year","Month"]).sum("Quantity","Total Price").withColumnRenamed("sum(Quantity)","Quantity").withColumnRenamed("sum(Total Price)","Total Price").toPandas()
sns.boxplot(data_ttl_trans["Quantity"])

sns.boxplot(data_ttl_trans["Total Price"])

 如上两图所示,销售数据极不平衡,有些单子里的销售额或销售量远远超过正常标准,因此在作分布图时,我会将箱线图中的离群点去除。

print("Quantity upper",np.percentile(data_ttl_trans["Quantity"],75)*2.5-np.percentile(data_ttl_trans["Quantity"],25)) #箱线图顶点
print("Total Price upper",np.percentile(data_ttl_trans["Total Price"],75)*2.5-np.percentile(data_ttl_trans["Total Price"],25)) #箱线图顶点
sns.distplot(data_ttl_trans[data_ttl_trans["Quantity"]<=661]["Quantity"])
sns.distplot(data_ttl_trans[data_ttl_trans["Total Price"]<=97779]["Total Price"])

 

可以看出,哪怕去除了离群点,从分布图上分布看依旧明显右偏趋势,表明了有极少数的订单拥有极大的销售量与销售额,远超正常订单的水平。

sns.boxplot(data_ttl_item["Quantity"])
print("Quantity upper",np.percentile(data_ttl_item["Quantity"],75)*2.5-np.percentile(data_ttl_item["Quantity"],25)) #箱线图顶点
#Quantity upper 3279.75
sns.distplot(data_ttl_item[data_ttl_item["Quantity"]<=2379]["Quantity"])

以商品为单位的销量分布也同样右偏,表明了同样有极少数的商品拥有极大的销售量,远超一般商品的水平。

接下来我们看看时间维度上的销量变化

data_ttl_time.index = [str(i)+"\n"+str(j) for i,j in zip(data_ttl_time["Year"],data_ttl_time["Month"])]
fig,ax1 = plt.subplots()
ax1.plot(data_ttl_time["Quantity"],color='gray',label="Quantity")
ax2 = ax1.twinx()
ax2.plot(data_ttl_time["Total Price"],color="red",label="Total Price")
ax1.set_ylabel("Quantity",color="gray")
ax2.set_ylabel("Total Price",color="red")
plt.show()

 

plt.bar(data_ttl_weekday["DayOfWeek"],data_ttl_weekday["Total Price"],color='red',width=0.5,label="Quantity")
plt.show() #周六不卖

 从时间轴上来看,销售额有一个明显的上升趋势,到了11月有一个明显的高峰,而到12月则有所回滚。

data_ttl_country.index = data_ttl_country["Country"]
ttl_price_country = data_ttl_country["Total Price"].sort_values()[::-1]
tmp = ttl_price_country[5:]
ttl_price_country = ttl_price_country[0:5]
ttl_price_country["others"] = sum(tmp)
plt.pie(ttl_price_country,labels=ttl_price_country.index)
plt.legend(loc="upper right")
plt.show()

data_ttl_country_time["isUnitedKingdom"]=["UK" if i=="United Kingdom" else "Not UK" for i in data_ttl_country_time["Country"]]
data_ttl_country_time_isUK = data_ttl_country_time.groupby(["isUnitedKingdom","Year","Month"])["Total Price"].sum().reset_index()
data_ttl_country_time_isUK.index = data_ttl_country_time_isUK["Year"]+"\n"+data_ttl_country_time_isUK["Month"]
UK = data_ttl_country_time_isUK[data_ttl_country_time_isUK["isUnitedKingdom"]=="UK"]
NUK = data_ttl_country_time_isUK[data_ttl_country_time_isUK["isUnitedKingdom"]=="Not UK"]
plt.figure(figsize=(8,8))
plt.bar(UK.index,UK["Total Price"],color="#66c2a5",label="United Kingdom")
plt.bar(NUK.index,NUK["Total Price"],bottom=UK["Total Price"],color="#8da0cb",label="other Country")
plt.legend()
plt.show()

 总体上来看,总销售额的绝大部分都是来自于英国销售,而从时间线商来看,每个月都是英国销售额占主体。因此,后文的分析将看2011年英国的全年销售。

## 只选取UK2011年的全年数据
df_UK = data.filter((F.col("Country")=="United Kingdom") & (F.col("Year")==2011))

2、是否存在季节性商品

首先,我们将2011年的4个季度的销售额、销售量以及成交量分别汇总并排序,看看有没有哪个商品在各个季度的排名有明显的差距。

def transform_to_Season(x):if len(x)>1 and x[0]=="0":x = x[1:]return int((int(x)-1)//3)+1df_UK = df_UK.withColumn("Season",F.udf(transform_to_Season,types.IntegerType())(F.col("Month")))df_UK_seasonal = df_UK.groupby(["Season","Itemname"]).agg(F.countDistinct(F.col("BillNo")).alias("No_Of_Trans"),F.sum(F.col("Quantity")).alias("Quantity"),F.sum(F.col("Total Price")).alias("Total Price")).toPandas()# 排序
def rank_seasonal(df,season="all"):groupby_cols = ["Itemname"]if season!="all":groupby_cols.append("Season")df = df[df["Season"]==int(season)]df = df.groupby(groupby_cols).sum(["No_Of_Trans","Quantity","Total Price"])for col in ["No_Of_Trans","Quantity","Total Price"]:df[col+"_Rank"]=df[col].rank(ascending=False)return dfdf_UK_all = rank_seasonal(df_UK_seasonal)
df_UK_first = rank_seasonal(df_UK_seasonal,"1")
df_UK_second = rank_seasonal(df_UK_seasonal,"2")
df_UK_third = rank_seasonal(df_UK_seasonal,"3")
df_UK_fourth = rank_seasonal(df_UK_seasonal,"4")## Top 10 Rank:
dfs = [df_UK_all,df_UK_first,df_UK_second,df_UK_third,df_UK_fourth]
item_set_No_Of_Trans=set()
item_set_Quantity=set()
item_set_Total_Price = set()
for df in dfs:df = df.copy().reset_index()itemname_top_Trans = df[df["No_Of_Trans_Rank"]<=10]["Itemname"]itemname_top_Qty = df[df["Quantity_Rank"]<=10]["Itemname"]itemname_top_TTL_PRICE = df[df["Total Price_Rank"]<=10]["Itemname"]for i in itemname_top_Trans:item_set_No_Of_Trans.add(i)for i in itemname_top_Qty:item_set_Quantity.add(i)for i in itemname_top_TTL_PRICE:item_set_Total_Price.add(i)## 每个季度的是否有所不同?
RANK_DF = {"Dimension":[],"item":[],"Rank":[],"Value":[],"Season":[],"Range":[]}#pd.DataFrame(columns=["Dimension","item","Rank","Value"])for dim,itemset in [("No_Of_Trans",item_set_No_Of_Trans),("Quantity",item_set_Quantity),("Total Price",item_set_Total_Price)]:for i in list(itemset):min_rank=9999max_rank=-1for j,df in enumerate(dfs):df = df.reset_index()if j == 0:RANK_DF["Season"].append("all")else:RANK_DF["Season"].append(df["Season"].values[0])sub_df = df[df["Itemname"]==i]if sub_df.shape[0]==0:curr_rank=9999curr_value=0else:curr_rank = df[df["Itemname"]==i][dim+"_Rank"].values[0]curr_value = df[df["Itemname"]==i][dim].values[0]min_rank = min(curr_rank,min_rank)max_rank = max(curr_rank,max_rank) if curr_rank<9999 else max_rankRANK_DF["Dimension"].append(dim)RANK_DF["item"].append(i)RANK_DF["Rank"].append(curr_rank)RANK_DF["Value"].append(curr_value)RANK_DF["Range"] += [max_rank-min_rank]*5
RANK_DF = pd.DataFrame(RANK_DF)plt.figure(figsize=(20,20))
# “每个季度的量相差很多”的,但是本身很多的
dims = ["No_Of_Trans","Quantity","Total Price"]
for i,dim in enumerate(dims):tmp_df = RANK_DF[(RANK_DF["Dimension"]==dim) & (RANK_DF["Season"]!='all')]tmp_df["Range_Rank"] = tmp_df["Range"].rank(ascending=False)tmp_df = tmp_df.sort_values("Range_Rank").reset_index(drop=True)for j in range(3):tmp_df_rank = tmp_df.loc[4*j:4*(j+1)-1,:]this_item = list(set(tmp_df_rank["item"]))[0]plt.subplot(3,3,i*3+j+1)plt.pie(tmp_df_rank["Value"],labels=tmp_df_rank["Season"])plt.title(f"{dim}:{this_item}")
plt.show()

上图中的商品,是在全年的销售中名销售额/销售了/成交量列前茅的几个商品,但是从饼图上来看,这下商品明显是会集中在某一个季度中畅销。我们把颗粒度缩小到按月份来看,计算每月销售额占总销售额的比重(季节指数);并以此排序看看哪些商品的季节指数极差最大。

df_UK_monthly = df_UK.groupby(["Month","Itemname"]).agg(F.countDistinct(F.col("BillNo")).alias("No_Of_Trans"),F.sum(F.col("Quantity")).alias("Quantity"),F.sum(F.col("Total Price")).alias("Total Price")).toPandas().reset_index(drop=True)df_UK_fullyear = df_UK_monthly.groupby(["Itemname"]).mean(["No_Of_Trans","Quantity","Total Price"]).reset_index()
df_UK_fullyear.rename(columns={"No_Of_Trans":"FY_No_Of_Trans","Quantity":"FY_Quantity","Total Price":"FY_Total Price"},inplace=True)df_UK_Monthly_season = pd.merge(df_UK_monthly,df_UK_fullyear,on=["Itemname"],how='left')
df_UK_Monthly_season["seasonal_index_No_Of_Trans"]=df_UK_Monthly_season["No_Of_Trans"]/df_UK_Monthly_season["FY_No_Of_Trans"]
df_UK_Monthly_season["seasonal_index_Quantity"]=df_UK_Monthly_season["Quantity"]/df_UK_Monthly_season["FY_Quantity"]
df_UK_Monthly_season["seasonal_index_Total Price"]=df_UK_Monthly_season["Total Price"]/df_UK_Monthly_season["FY_Total Price"]#算极差df_UK_season_max = df_UK_Monthly_season.groupby("Itemname").max(["seasonal_index_No_Of_Trans","seasonal_index_Quantity","seasonal_index_Total Price"]).reset_index().rename(columns={"seasonal_index_No_Of_Trans":"seasonal_index_No_Of_Trans_max","seasonal_index_Quantity":"seasonal_index_Quantity_max","seasonal_index_Total Price":"seasonal_index_Total Price_max"})
df_UK_season_min = df_UK_Monthly_season.groupby("Itemname").min(["seasonal_index_No_Of_Trans","seasonal_index_Quantity","seasonal_index_Total Price"]).reset_index().rename(columns={"seasonal_index_No_Of_Trans":"seasonal_index_No_Of_Trans_min","seasonal_index_Quantity":"seasonal_index_Quantity_min","seasonal_index_Total Price":"seasonal_index_Total Price_min"})
df_UK_season_ranges = pd.merge(df_UK_season_min,df_UK_season_max,on=["Itemname"])
seasonal_index_cols = ["seasonal_index_No_Of_Trans","seasonal_index_Quantity","seasonal_index_Total Price"]
for col in seasonal_index_cols:df_UK_season_ranges[col] = df_UK_season_ranges[col+"_max"]-df_UK_season_ranges[col+"_min"]df_UK_season_ranges[col+"_Rank"]=df_UK_season_ranges[col].rank(ascending=False)
df_UK_season_ranges = df_UK_season_ranges[["Itemname"]+[i for i in df_UK_season_ranges if "seasonal_index" in i]]plt.figure(figsize=(15,15))
for i,col in enumerate(["seasonal_index_No_Of_Trans","seasonal_index_Quantity","seasonal_index_Total Price"]):seasonal_items = df_UK_season_ranges.sort_values(col+"_Rank").reset_index().loc[:2,"Itemname"].tolist()for j,item in enumerate(seasonal_items):plt.subplot(3,3,i*3+j+1)        idxs = [k for k in df_UK_monthly.index if df_UK_monthly.loc[k,"Itemname"]==item]tmp_df = df_UK_monthly.loc[idxs,["Itemname","Month",col.replace("seasonal_index_","")]]tmp_df = tmp_df.pivot(index="Month",columns="Itemname",values=col.replace("seasonal_index_",""))plt.plot(tmp_df, marker='o')plt.ylabel(col.replace("seasonal_index_",""))plt.title(item)plt.show()

 此处分别从销售额/销售量/成交量3个维度构造了季节指数,并将极差排名前3的商品作了折线统计图。从途中来看,这些商品全都是“集中在某一个月份十分畅销,但是在其他月份相对无人问津”。

ranges = RANK_DF.groupby(["Dimension","item"]).first(["Range"]).reset_index()[["Dimension","item","Range"]]## No_Of_Trans
plt.figure(figsize=(20,20))
range_df = ranges[ranges["Dimension"]=="No_Of_Trans"]
titles = ["All","Season One","Season Two","Season Three","Season Four"]
for n in range(5):plt.subplot(5,1,n+1)df = dfs[n].copy().reset_index()df = df.loc[[i for i in df.index if df.loc[i,"Itemname"] in item_set_No_Of_Trans],["Itemname","No_Of_Trans"]]df=df.sort_values("No_Of_Trans",ascending=False)df = df.iloc[:10,:]df = pd.merge(df,range_df,left_on="Itemname",right_on="item",how="left")plt.barh(df["Itemname"]+" "+df["Range"].astype("str"),df["No_Of_Trans"])plt.title("No_Of_Trans "+titles[n])
plt.show()plt.figure(figsize=(20,20))
range_df = ranges[ranges["Dimension"]=="Quantity"]
titles = ["All","Season One","Season Two","Season Three","Season Four"]
for n in range(5):plt.subplot(5,1,n+1)df = dfs[n].copy().reset_index()df = df.loc[[i for i in df.index if df.loc[i,"Itemname"] in item_set_Quantity],["Itemname","Quantity"]]df=df.sort_values("Quantity",ascending=False)df = df.iloc[:10,:]df = pd.merge(df,range_df,left_on="Itemname",right_on="item",how="left")plt.barh(df["Itemname"]+" "+df["Range"].astype("str"),df["Quantity"])plt.title("Quantity "+titles[n])
plt.show()plt.figure(figsize=(20,20))
range_df = ranges[ranges["Dimension"]=="Total Price"]
titles = ["All","Season One","Season Two","Season Three","Season Four"]
for n in range(5):plt.subplot(5,1,n+1)df = dfs[n].copy().reset_index()df = df.loc[[i for i in df.index if df.loc[i,"Itemname"] in item_set_Total_Price],["Itemname","Total Price"]]df=df.sort_values("Total Price",ascending=False)df = df.iloc[:10,:]df = pd.merge(df,range_df,left_on="Itemname",right_on="item",how="left")plt.barh(df["Itemname"]+" "+df["Range"].astype("str"),df["Total Price"])plt.title("Total Price "+titles[n])
plt.show()

 

 

最终将全年的top10以及每个季度的top10作图,图中商品旁边标出了它对应销售额/销售量/成交量的排名的极差,可以看到,虽然大多数在全年销售中获得的top10排名极差都较小(总共有3000+个商品),但是在总销售额的全年top10中,依然有一个明显存在季节性效应的商品(PAPER CHAIN KIT 50's CHRISMAS)。

3、同一个商品在不同维度上是否排名差距较大

df_UK_all = df_UK_monthly.groupby(["Itemname"]).sum(["No_Of_Trans","Quantity","Total Price"]).reset_index()
df_UK_all["No_Of_Trans_Rank"] = df_UK_all["No_Of_Trans"].rank(ascending=False)
df_UK_all["Quantity_Rank"] = df_UK_all["Quantity"].rank(ascending=False)
df_UK_all["Total Price_Rank"] = df_UK_all["Total Price"].rank(ascending=False)Rank_Gaps = [max(df_UK_all.loc[i,"No_Of_Trans_Rank"],df_UK_all.loc[i,"Quantity_Rank"],df_UK_all.loc[i,"Total Price_Rank"])\- min(df_UK_all.loc[i,"No_Of_Trans_Rank"],df_UK_all.loc[i,"Quantity_Rank"],df_UK_all.loc[i,"Total Price_Rank"])\for i in range(df_UK_all.shape[0])]
df_UK_all["Rank_Gaps"] = Rank_Gapsdf_ranked_all = df_UK_all.sort_values("Rank_Gaps",ascending=False).reset_index(drop=True)plt.figure(figsize=(20,20))
for i in range(5):plt.subplot(5,1,i+1)sub = df_ranked_all.iloc[i,:]item_name = sub["Itemname"]sub = dict(sub)sub_df = pd.DataFrame({"index":["No_Of_Trans_Rank","Quantity_Rank","Total Price_Rank"],\"Value":[sub["No_Of_Trans_Rank"],sub["Quantity_Rank"],sub["Total Price_Rank"]]})plt.barh(sub_df["index"],sub_df["Value"])plt.title(item_name)
plt.show()

 这里计算了每件商品在销售额、销售量、成交量上的排名并将这三个排名相差最大的5个商品列出了。可以发现上图中的商品,有些虽然销售额与销售量名列前茅,但是成交量却排名靠后。特别是第一名(PAPER CRAFT, LITTLE BRIDE),实际看表格数据时会发现它只有1个单子,但是那1个单子却订了8万多个这个商品。

4、探索客单价、客单量与平均售价

现在构造以下3个KPI:

客单量(UPT)= 销售量(Quantity)/成交量(No_Of_Trans)

平均售价(ASP)= 销售额(Total Price)/ 销售量(Quantity)

客单价(ATV)= 销售额(Total Price)/ 成交量(No_Of_Trans)

df_UK_monthly_KPI = df_UK_monthly.groupby(["Month"]).sum(["No_Of_Trans","Quantity","Total Price"]).reset_index()
df_UK_monthly_KPI["UPT"] = df_UK_monthly_KPI["Quantity"]/df_UK_monthly_KPI["No_Of_Trans"]
df_UK_monthly_KPI["ATV"] = df_UK_monthly_KPI["Total Price"]/df_UK_monthly_KPI["No_Of_Trans"]
df_UK_monthly_KPI["ASP"] = df_UK_monthly_KPI["Total Price"]/df_UK_monthly_KPI["Quantity"]plt.plot(df_UK_monthly_KPI["Month"],df_UK_monthly_KPI["UPT"])
plt.title("Unit Per Transaction")
plt.show()
plt.plot(df_UK_monthly_KPI["Month"],df_UK_monthly_KPI["ATV"])
plt.title("Average Transaction Value")
plt.show()
plt.plot(df_UK_monthly_KPI["Month"],df_UK_monthly_KPI["ASP"])
plt.title("Average Selling Price")
plt.show()plt.plot(df_UK_monthly_KPI["Month"],df_UK_monthly_KPI["No_Of_Trans"])
plt.title("No_Of_Trans")
plt.show()

这里可以得出一个非常有趣的结论。11月的高销售额实际上是由于11月的高交易量。尽管12月份的销售额再次下降,但12月份的订单大多都是大单子。

5、(待定)探索客户价值

此处把Customer ID不为空的数据当做是会员数据

df_monthly_CustomerID = df_UK.groupBy(["CustomerID","Month"]).agg(F.countDistinct(F.col("BillNo")).alias("No_Of_Trans"),F.sum(F.col("Quantity")).alias("Quantity"),F.sum(F.col("Total Price")).alias("Total Price")).toPandas()
df_by_CustomerID_by_Item = df_UK.groupBy(["CustomerID","Itemname"]).agg(F.countDistinct(F.col("BillNo")).alias("No_Of_Trans"),F.sum(F.col("Quantity")).alias("Quantity"),F.sum(F.col("Total Price")).alias("Total Price")).toPandas()df_monthly_CustomerID["CustomerID"] = df_monthly_CustomerID["CustomerID"].astype(str)
df_monthly_CustomerID["isMember"] = [str(i)!='99999' for i in df_monthly_CustomerID["CustomerID"]]df_monthly_Member = df_monthly_CustomerID[df_monthly_CustomerID["isMember"]==True].groupby("Month").sum(["No_Of_Trans","Total Price"]).sort_index()
df_monthly_Non_Member = df_monthly_CustomerID[df_monthly_CustomerID["isMember"]==False].reset_index()
df_monthly_Non_Member.index=df_monthly_Non_Member["Month"]
df_monthly_Non_Member = df_monthly_Non_Member.sort_index()plt.figure(figsize=(8,8))
plt.bar(df_monthly_Member.index,df_monthly_Member["Total Price"],color="#66c2a5",label="Member")
plt.bar(df_monthly_Non_Member.index,df_monthly_Non_Member["Total Price"],bottom=df_monthly_Member["Total Price"],color="#8da0cb",label="Non-Member")
plt.legend()
plt.show()df_monthly_Member_No_of_Trans = df_monthly_CustomerID[df_monthly_CustomerID["isMember"]==True].groupby("Month").sum("No_Of_Trans").sort_index()
plt.figure(figsize=(8,8))
plt.bar(df_monthly_Member_No_of_Trans.index,df_monthly_Member_No_of_Trans["No_Of_Trans"],color="#66c2a5",label="Member")
plt.bar(df_monthly_Non_Member.index,df_monthly_Non_Member["No_Of_Trans"],bottom=df_monthly_Member_No_of_Trans["No_Of_Trans"],color="#8da0cb",label="Non-Member")
plt.legend()
plt.show()

 

 从图中来看,无论是交易量还是销售额,都是会员销售占大多数的。这样的结论对我而言过于反直觉,因此我没有做后续的分析。

三、关联规则分析

关联规则分析为的就是发现商品与商品之间的关联。通过计算商品之间的支持度、置信度与提升度,分析哪些商品有正向关系,顾客愿意同时购买它们。

此处使用了pyspark自带的FPGrowth算法。它和APRIORI算法一样都是计算两两商品之间支持度置信度与提升度的算法,虽然算法流程不同,但是计算结果是一样的。

from pyspark.ml.fpm import FPGrowth,FPGrowthModel
df_UK_concatenated=df_UK.groupby(["BillNo","Itemname"]).agg(F.sum("Quantity").alias("Quantity")).groupby("BillNo").agg(F.collect_list(F.col("Itemname")).alias("items"))model = FPGrowth(minSupport=0.03,minConfidence=0.3)
model = model.fit(df_UK_concatenated.select("items"))res = model.associationRules.toPandas()
import itertoolscombs = [('confidence', 'lift', 'support'),('lift', 'support', 'confidence'),('support', 'confidence', 'lift')]for i,(x,y,c) in enumerate(combs):plt.subplot(3,1,i+1)sc = plt.scatter(res[x],res[y],c=res[c],cmap='viridis')plt.xlabel(x)plt.ylabel(y)plt.colorbar(sc,label=c)
plt.show()

 

res.sort_values("support",ascending=False).head(6)

上图是支持度(support)前三的商品组合(每2条实际上是同一组商品)。

支持度指的是2件商品同时出现的概率。P(A\cap B)

res.sort_values("confidence",ascending=False).head(5)

上图是置信度(confidence)前5的商品组合,置信度指的是当商品A被购买时,商品B也被购买的概率。

P(B|A)=\frac{P(A\cap B)}{P(A)}

res.sort_values("lift",ascending=False).head(6)

上图是提升度(lift)前三的商品组合(每2条实际上是同一组商品)。 提升度可以看做是2件商品之间是否存在正向/反向的关系。

最后将提升度降序排列打印出来:

res.sort_values("lift",ascending=False)

我们发现顾客最常见的组合是不同颜色的杯子和碟子。
其他产品,如午餐袋、Jumbo Bag或闹钟也处于同样的情况。
总的来说,最常见的组合是同一种产品不同的颜色。 

四、结论

1、英国2011年时11月份销售量急剧上升,12月份又有所回落
2、11月的销售量高是因为11月的成交量大,而在12月,虽然销售量相对较小,但实际上12月的每笔交易都是大交易,数量和价格都很大。
3、许多产品都有季节性影响,即使是当年销量排名前10的产品(PAPER CHAIN KIT 50’s CHRISMAS)
4、有些产品虽然交易量很少,但销量却遥遥领先(如PAPER CRAFT, LITTLE BIRDIE, 2011年只有1笔交易,但销量第一)。
5、最常见的组合是同一产品不同颜色的组合。最典型的例子就是杯子和碟子。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/317650.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

50. 【Android教程】xml 数据解析

xml 是一种标记扩展语言&#xff08;Extension Mark-up Language&#xff09;&#xff0c;学到这里大家对 xml 语言一定不陌生&#xff0c;但是它在 Android 中的运用其实只是冰山一角。抛开 Android&#xff0c;XML 也被广泛运用于各种数据结构中。在运用 xml 编写 Android 布…

【氮化镓】GaN器件在航天器高可靠正向转换器中应用

文章是发表在《IEEE Journal of Emerging and Selected Topics in Power Electronics》2022年10月第10卷第5期上的一篇关于GaN(氮化镓)器件在航天器高可靠性正向转换器中应用的研究。文章的作者是匹兹堡大学电气与计算机工程系的Aidan Phillips, Thomas Cook和Brandon M. Gra…

ubuntu安装python

点个关注吧&#xff0c;谢谢&#xff01; python现在还未安装 解决方法&#xff1a; 一、更新软件包列表 sudo apt update二、根据需要安装 sudo apt install python3 sudo apt install python2 sudo apt install python3.8完成 三、添加链接 解决只有python3&#xff0c;没…

spring boot 自定义starter示例

springboot 约定规范 Starter项目的命名规范 建议自定义的starter 以 xxx-spring-boot-starter 命名&#xff0c;官方的Starter一般都是以spring-boot-starter-为前缀。这样做的目的是为了避免与官方或其他第三方提供的Starter产生冲突或混淆。 Starter项目的结构规范(重要) …

【C++容器map】map的相关用法

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; C进阶 &#x1f389;其它专栏&#xff1a; C初阶 | 初阶数据结构 | Linux 本篇文章主要讲解 C容器之map相关用法 的相关内容 文章目录 1. map的介绍2. map的使用<font size5 color #…

在做题中学习(48):朴素的二分查找

. - 力扣&#xff08;LeetCode&#xff09; 解法一&#xff1a; 暴力求解 for循环中&#xff0c;从nums[0]枚举到nums[n-1]&#xff0c;依次判断&#xff0c;返回 target的值。 时间复杂度 : O(N) :因为要遍历一遍数组 解法二&#xff1a;二分查找 因为此数组为有序的…

Springboot+Vue+小程序+基于微信小程序护农远程看护系统

开发平台为idea&#xff0c;maven管理工具&#xff0c;Mybatis操作数据库&#xff0c;根据市场数字化需要为农户打造小程序可远程查看农场的种植情况。项目是调试&#xff0c;讲解服务均可有偿获取&#xff0c;需要可在最下方QQ二维码处联系我。 SpringbootVue小程序&#xff…

QT——简易计算机(从0开始)

目录 一、题目描述&#xff1a; 二、创建工程&#xff1a; 三、UI界面设计&#xff1a; 四、程序编写&#xff1a; 五、总程序&#xff1a; 六、windows可执行文件 七、实现效果 一、题目描述&#xff1a; 创建一个简单的图形用户界面(GUI),包括一个文本框用于显示计算结…

Cisco IOS XE Web UI 权限提升漏洞复现(CVE-2023-20198)

0x01 产品简介 Web UI 是一种基于GUI的嵌入式系统管理工具,能够提供系统配置、简化系统部署和可管理性以及增强用户体验。它带有默认映像,因此无需在系统上启用任何内容或安装任何许可证。Web UI 可用于构建配置以及监控系统和排除系统故障,而无需CLI专业知识。 0x02 漏洞…

Linux命令大全 以及搭建hadoop

Liunx系统目录 ├── bin -> usr/bin # 用于存放二进制命令 ├── boot # 内核及引导系统程序所在的目录 ├── dev # 所有设备文件的目录&#xff08;如磁盘、光驱等&#xff09; ├── etc # 配置文件默认路径、服务启动命令存放目录 ├── home # 用户家目录&#…

快速了解Django:核心概念解析与实践指南

title: 快速了解Django&#xff1a;核心概念解析与实践指南 date: 2024/5/1 20:31:41 updated: 2024/5/1 20:31:41 categories: 后端开发 tags: Django核心路由系统视图系统ORM管理中间件Web框架登录装饰器 第一章&#xff1a;Django简介 背景和发展历程&#xff1a; Djan…

用Jenkins实现cherry-pick多个未入库的gerrit编译Android固件

背景: 在做Android固件开发的时候,通常我们可以利用gerrit-trigger插件,开发者提交一笔的时候自动触发jenkins编译,如果提交的这一笔的编译依赖其他gerrit才能编译过,我们可以在commit message中加入特殊字段,让jenkins在编译此笔patch的时候同时抓取依赖的gerrit代码下…

JSON教程(非常详细)

参考文章来源&#xff1a;JSON教程&#xff08;非常详细&#xff09; 目录 JSON JSON 发展史 为什么要使用 JSON&#xff1f; JSON 的不足 存储格式 使用场景 1) 定义接口 2) 序列化 3) 生成 Token 4) 配置文件 JSON语法规则 JSON 与 JavaScript 对象的区别 JSON数…

第7篇:创建Nios II工程之控制LED<二>

Q&#xff1a;上一期我们完成了Quartus硬件工程部分&#xff0c;本期我们创建Nios II软件工程这部分。 A&#xff1a;创建完BSP和Nios II Application之后&#xff0c;在source文件main.c中添加LED控制代码&#xff1a;system.h头文件包含了Platform Designer系统中IP的硬件信…

Kubernetes - CentOS7搭建k8s_v1.18集群高可用(kubeadm/二进制包部署方式)实测配置验证手册

Kubernetes - CentOS7搭建k8s集群高可用&#xff08;kubeadm/二进制包部署方式&#xff09;实测配置验证手册 前言概述&#xff1a; 一、Kubernetes—k8s是什么 Kubernetes 这个名字源于希腊语&#xff0c;意为“舵手“或”飞行员"。 Kubernetes&#xff0c;简称K8s&#…

Qwen-Audio:推动通用音频理解的统一大规模音频-语言模型(开源)

随着人工智能技术的不断进步&#xff0c;音频语言模型&#xff08;Audio-Language Models&#xff09;在人机交互领域变得越来越重要。然而&#xff0c;由于缺乏能够处理多样化音频类型和任务的预训练模型&#xff0c;该领域的进展受到了限制。为了克服这一挑战&#xff0c;研究…

51单片机入门(一)

1. 51单片机的基础介绍 2. RAM和ROM的区别 总体而言&#xff0c;RAM和ROM在计算机系统中起着不同的角色&#xff0c;RAM用于临时存储运行时数据&#xff0c;而ROM用于存储永久性的固件和系统程序。 3. 为什么叫51单片机 因为51系列单片机都是使用Intel 8031指令系统的单片机…

太速科技-多路PCIe的阵列计算全国产化服务器

多路PCIe的阵列计算全国产化服务器 多路PCIe的阵列计算全国产化服务器以国产化处理器&#xff08;海光、飞腾ARM、算能RSIC V&#xff09;为主板&#xff0c;扩展6-8路PCIe3.0X4计算卡&#xff1b; 计算卡为全国产化的AI处理卡&#xff08;瑞星微ARM&#xff0c;算能AI&#x…

《QT实用小工具·五十》动态增删数据与平滑缩放移动的折线图

1、概述 源码放在文章末尾 该项目实现了带动画、带交互的折线图&#xff0c;包含如下特点&#xff1a; 动态增删数值 自适应显示坐标轴数值 鼠标悬浮显示十字对准线 鼠标靠近点自动贴附 支持直线与平滑曲线效果 自定义点的显示类型与大小 自适应点的数值显示位置 根据指定锚点…

如何讲好ppt演讲技巧(4篇)

如何讲好ppt演讲技巧&#xff08;4篇&#xff09; 如何讲好PPT演讲技巧&#xff08;四篇&#xff09; **篇&#xff1a;精心准备&#xff0c;奠定演讲基础 一个成功的PPT演讲&#xff0c;离不开精心的准备。首先&#xff0c;要确定演讲的主题和目标&#xff0c;确保演讲内容清…