大数据集群(PySpark)+MySQL+PyEcharts+Flask:购物篮数据分析与挖掘

1 大数据集群

1.1 大数据集群介绍

  Spark集群是一种分布式计算框架,它基于内存计算,提供了高效的数据抽象和并行计算能力,能够处理大规模数据集的批处理和实时处理任务。Spark采用内存存储中间计算结果,可减少迭代运算的磁盘I/O,并通过并行计算有向无环图的优化,使其运行速度比MapReduce快100倍;Spark可以使用Hadoop YARN和Apache Mesos作为其资源管理和调度器,可以从多种数据源读取数据,如HDFS、HBase、MySQL等。
  PySpark则是Spark的Python API,允许Python开发者利用Spark的强大功能进行数据处理和分析。本案例采用PySpark大数据集群做数据分析与挖掘,使用HDFS作为文件存储系统。PySpark由多个组件构成,包括Spark SQL、Spark Streaming、MLlib(机器学习库)和GraphX(图计算库)等。
  本案例部署了3个节点的完全分布式集群,具体的开发环境如下:

节点/组件/安装包版本备注
名称节点192.168.126.10master
数据节点192.168.126.11slave1
数据节点192.168.126.12slave2
JDKjdk-8u281Java运行环境,Spark的运行需要JDK的支持
Hadoophadoop-3.1.4提供HDFS、Hive运行环境支持。HDFS系统访问端口为:hdfs://192.168.126.10:9000
pysparkspark-3.4.3-bin-hadoop3.tgzSpark集群的master节点的地址和端口为:spark://192.168.126.10:7077
MySQL5.7.18存储数据分析结果,端口:3306,用户名:root,密码:123456
pythonPython-3.9.0.tgz3.9.0版本的Python
MySQL Connectormysql-connector-java-5.1.32-bin.jarSpark 连接MySQL的驱动
IntelliJ IDEAUltimate 2020.3编程工具IDEA

  大数据集群运行时,Spark的管理页面如下图所示:
在这里插入图片描述

1.2 大数据集群部署、相关组件(软件)的配置(略,后续更新)

2 购物篮数据分析与挖掘

2.1 数据集介绍

  “Assignment-1_Data”数据集,包含与消费者购物行为相关的数据,用于市场购物篮分析(MBA)和关联规则挖掘(Association Rule Mining),目的是通过分析消费者在购买过程中的商品组合,揭示商品之间的关联性和购买行为模式。
  数据集下载地址: 购物篮数据集

2.1.1 数据集包含的内容

  数据集的字段如下图所示:
在这里插入图片描述

字段说明备注
订单号(BillNo)用于唯一标识每一笔交易或订单。订单信息
购买日期(Date)记录订单发生的日期订单信息
客户ID(CustomerID)用于标识购买该订单的客户,可用于进一步分析客户的购买习惯和偏好。订单信息
商品名称(Itemname)列出订单中包含的商品名称。商品信息
数量(Quantity)每种商品在订单中的购买数量。商品信息
单价(Price)每种商品的单价商品信息
国家(Country)交易地点订单信息

数据集有7个字段,522,064条记录,共38.7MB。

2.1.2 购物篮分析(MBA)与关联规则挖掘(Association Rule Mining)的应用

(1)识别频繁项集:
  支持度(Support):衡量某个商品组合在所有订单中出现的频率。高支持度意味着该商品组合在销售中较为常见。例如,如果“可乐+薯片”组合的支持度为10%,则意味着在所有订单中,有10%的订单同时包含了这两种商品。
(2)提取关联规则:
  置信度(Confidence):衡量在购买了商品A的条件下,同时购买商品B的概率。高置信度表示商品之间的关联性强。
  提升度(Lift):衡量商品A的购买对商品B购买的影响程度。提升度大于1表示商品A的购买对商品B的购买有正向影响。例如,如果购买可乐的顾客中有40%也购买了薯片,而薯片在所有订单中的支持度为10%,则“可乐→薯片”的置信度为40%,提升度为4(因为40%/10%=4),表示购买可乐对购买薯片有很强的正向影响。
(3)应用分析结果:
  交叉销售和捆绑销售:根据分析出的关联规则,商家可以设计交叉销售策略,将经常一起购买的商品放在相邻的货架上,或进行捆绑销售以提高销售额。
  库存管理和促销策略:了解哪些商品组合更受欢迎,有助于商家优化库存管理和制定更有效的促销策略。
个性化推荐:在电子商务平台上,可以根据用户的购买历史和行为,利用关联规则为其推荐可能感兴趣的商品。

2.1.3 数据集上传

  先启动Hadoop集群和Spark集群,再将数据集Assignment-1_Data.csv上传到Hadoop分布式存储系统HDFS的目录/data/下,如下图所示:
在这里插入图片描述

2.2 数据导入,数据预处理

  打开IntelliJ IDEA,新建项目flaskpy,再新建python文件:购物篮数据分析挖掘_关联规则.py,以及其他文件夹(Flask部分再详细说明),项目结构如图所示。
在这里插入图片描述

  打开python文件:购物篮数据分析挖掘_关联规则.py。
  数据导入:先导入数据集,再做数据预处理。
  数据预处理:主要是日期(Date)列拆分转换为年(Year)、月(Month)、日(Day)、星期(DayOfWeek)、小时(Hour)、分钟(Minute)列,为后面更深、更细的数据分析和挖掘做准备;删除负值的记录、填充缺失值并删除那些非商品的记录。

from pyspark import SparkConf
from pyspark.ml.feature import StringIndexer
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.ml.recommendation import ALS
from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.fpm import *
import matplotlib.pyplot as plt
import pandas as pd

from pyspark.sql.types import StructField, IntegerType

plt.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体为SimHei显示中文
plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像时负号'-'显示为方块的问题

# 创建一个Spark配置对象,用于设置Spark作业的配置项
conf = SparkConf().setAppName("购物篮分析") \
    .set("spark.sql.legacy.timeParserPolicy", "LEGACY") \
    .setMaster('spark://192.168.126.10:7077')  # Spark集群的master节点的地址和端口
sc = SparkContext.getOrCreate(conf)
spark = SparkSession(sc)

# -------------一、数据导入与数据预处理-----------------------
# ------------(一)数据导入--------------------
filename = "Assignment-1_Data.csv"
data = spark.read.csv('hdfs://192.168.126.10:9000/data/' + filename, header=True, inferSchema=True) #inferSchema=True表示自动推断每列的数据类型
data.show(10, False)  # 显示数据的前10行,并设置False来避免截断过长的列值
data.printSchema()  # 打印数据的模式(Schema),即各列的名称和数据类型

#  ------------(二) 数据预处理--------------
# ----------1 数据转换------------
data = data.withColumn('Timestamp', to_timestamp('Date', 'yyyy/MM/dd HH:mm')) # 将'Date'列的数据类型从字符串转换为时间戳类型,并添加列Timestamp
data.show(5)
print("数据集记录数:", data.count())

# 接下来,基于新创建的'Timestamp'列,提取并添加年、月、日、星期几、小时和分钟等字段
data = data.withColumn('Year', year('Timestamp')) \
    .withColumn('Month', month('Timestamp')) \
    .withColumn("Day", dayofmonth("Timestamp")) \
    .withColumn("DayOfWeek", dayofweek("Timestamp")) \
    .withColumn("Hour", hour("Timestamp")) \
    .withColumn("Minute", minute("Timestamp"))
data.show(5)

# -----2 删除负值的记录、填充缺失值并删除那些非商品的记录-------------
data = data.where((data['Quantity'] > 0) & (data['Price'] > 0))  # 删除商品数量和价格为负数的记录
print(data.count())
data = data.withColumn("Total_Price", col("Quantity") * col("Price"))  # 增加一列商品总价Total_Price

data_null_agg = data.agg(*[sum(when(isnull(c), 1)).alias(c) for c in data.columns])  # 检查并查看每列的空值总数
data_null_agg.show()  # 发现 CustomerID 有十几万个空值
data = data.fillna('99999', 'CustomerID')  # 客户号CustomerID 空值填充99999

data = data.where(
    ~(data['Itemname'].isin(['POSTAGE', 'DOTCOM POSTAGE', 'Adjust bad debt', 'Manual']))
)  # 删除非商品的那些Item
data.show(5)

2.3 数据探索

2.3.1 总体销售情况

  从订单量(No_Of_Trans)、成交量(Quantity)与成交额(Total_Price)三个维度,分别从多个角度进行数据分析与可视化。数据可视化不是Spark的强项,这里仅用于程序测试,程序最终提交集群运行时,可以将数据可视化部分放在Flask部分,使用PyEchart将多张图片做成Web端可视化大屏,方便用户查看。
  编程思路:
(1)总体交易分析(data_ttl_trans):按订单号(BillNo)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。然后将计算结果存储到MySQL,再用PyEcharts绘图。同时为了测试需要,将计算结果(PySpark DataFrame格式)转换为 Pandas DataFrame格式,用于测试程序的可视化绘图。
(2)总体商品分析(data_ttl_item):按商品名称(Itemname)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。
(3)总体时间分析(按年和月)(data_ttl_time):按年(Year)、月(Month)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。
(4)总体星期几分析(data_ttl_weekday):按星期(DayOfWeek)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。
(5)总体国家分析(data_ttl_country):按国家(Country)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。
(6)总体国家时间分析(按国家、年和月)(data_ttl_country_time):按国家(Country)、年(Year)、月(Month)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。
  最后将6个分组计算后的结果存储到MySQL,同时将其转换为Pandas的DataFrame,但是Pandas的DataFrame的数据运算只能在driver节点进行,会降低整个Spark集群性能,因此只适合处理小型数据集。而PySpark的Data Frame的数据是分布式集群计算,适合处理中大规模数据集。程序代码如下:

# ----------------二、探索性分析--------------------
# ------(一)总体销售情况-----
# 从订单量(No_Of_Trans)、成交量(Quantity)与成交额(Total_Price)三个维度,分别从多个角度进行数据可视化与分析

# 1.总体交易分析
data_ttl_trans = data.groupby("BillNo").agg(
    sum("Quantity").alias("Quantity"),
    sum("Total_Price").alias("Total_Price"))
data_ttl_trans_copy = data_ttl_trans  # 用于存储到MySQL
data_ttl_trans = data_ttl_trans.toPandas()  # 如果需要Pandas DataFrame,则转换为Pandas
print("\n总体交易分析:\n", data_ttl_trans)

# 2.总体商品分析
data_ttl_item = data.groupby("Itemname").agg(
    sum("Quantity").alias("Quantity"),
    sum("Total_Price").alias("Total_Price"))
data_ttl_item_copy = data_ttl_item
data_ttl_item = data_ttl_item.toPandas()  # 如果需要Pandas DataFrame,则转换为Pandas
print("\n总体商品分析:\n", data_ttl_item)

# 3.总体时间分析(按年和月)
data_ttl_time = data.groupby(["Year", "Month"]).agg(
    sum("Quantity").alias("Quantity"),
    sum("Total_Price").alias("Total_Price"))
data_ttl_time_copy = data_ttl_time
data_ttl_time = data_ttl_time.toPandas()  # 如果需要Pandas DataFrame,则转换为Pandas
print("\n总体时间分析(按年和月):\n", data_ttl_time)

# 4.总体星期几分析
data_ttl_weekday = data.groupby("DayOfWeek").agg(
    sum("Quantity").alias("Quantity"),
    sum("Total_Price").alias("Total_Price"))
data_ttl_weekday_copy = data_ttl_weekday
data_ttl_weekday = data_ttl_weekday.toPandas()  # 如果需要Pandas DataFrame,则转换为Pandas
print("\n总体星期几分析:\n", data_ttl_weekday)

# 5.总体国家分析
data_ttl_country = data.groupby("Country").agg(
    sum("Quantity").alias("Quantity"),
    sum("Total_Price").alias("Total_Price"))
data_ttl_country_copy = data_ttl_country
data_ttl_country = data_ttl_country.toPandas()  # 如果需要Pandas DataFrame,则转换为Pandas

print("\n总体国家分析:\n", data_ttl_country)

# 6.总体国家时间分析(按国家、年和月)
data_ttl_country_time = data.groupby(["Country", "Year", "Month"]).agg(
    sum("Quantity").alias("Quantity"),
    sum("Total_Price").alias("Total_Price"))
data_ttl_country_time_copy = data_ttl_country_time
data_ttl_country_time = data_ttl_country_time.toPandas()  # 如果需要Pandas DataFrame,则转换为Pandas
print("\n总体国家时间分析(按国家、年和月):\n", data_ttl_country_time)

# ----------------将数据存储到Linux的MySQL中,为可视化大屏做数据准备-----------------------

# 定义MySQL数据库的连接参数:数据库为test,用户名root,密码123456,驱动com.mysql.jdbc.Driver
url = "jdbc:mysql://192.168.126.10:3306/test"
properties = {
    "user": "root",
    "password": "123456",
    "driver": "com.mysql.jdbc.Driver"}

# 写数据到MySQL的test数据库的movie表,无需预先在MySQL创建表。自动在mysql创建movie表
data_ttl_trans_copy.coalesce(1).write.format('jdbc') \
    .option('url', url) \
    .option('dbtable', 'data_ttl_trans') \
    .option('user', properties['user']) \
    .option('password', properties['password']) \
    .option('driver', properties['driver']) \
    .option('mode', 'overwrite') \
    # .save()  # 第一次建表时使用.save()。append为追加数据模式,overwrite为覆盖数据模式
print('Spark将数据写入mysql完成。')

# ---------封装将pyspark的dataframe写入mysql数据库的函数,方便调用--------------------
from pyspark.sql import DataFrame
import pymysql


def write_to_mysql(df: DataFrame, url: str, properties: dict, table_name: str):
    """
    将Pyspark的DataFrame写入MySQL数据库的指定表。检查表是否存在,存在则先删除表。
      参数:
    - df: 要写入的DataFrame。
    - url: MySQL数据库的JDBC URL。
    - properties: 包含数据库连接信息的字典(user, password, driver)。
    - table_name: 要写入的MySQL表名。
    """
    conn = pymysql.connect(host=url.split('/')[2].split(':')[0],
                           port=int(url.split('/')[2].split(':')[1].split('/')[0]),
                           user=properties['user'],
                           password=properties['password'],
                           database=url.split('/')[-1],
                           charset='utf8mb4',
                           cursorclass=pymysql.cursors.DictCursor)
    try:
        with conn.cursor() as cursor:
            # 检查表是否存在
            cursor.execute(f"SHOW TABLES LIKE '{table_name}'")
            if cursor.fetchone():
                # 如果表存在,则删除它
                cursor.execute(f"DROP TABLE IF EXISTS {table_name}")
                conn.commit()
                print(f"删除表:{table_name}")
    finally:
        conn.close()
        # 写入数据到MySQL
    df.coalesce(1).write.format('jdbc') \
        .option('url', url) \
        .option('dbtable', table_name) \
        .option('user', properties['user']) \
        .option('password', properties['password']) \
        .option('driver', properties['driver']) \
        .option('mode', 'append') \
        .save()
    print(f'Spark将数据写入MySQL的{table_name}表完成。')


# --------封装函数完成。-------
# 使用示例
url = "jdbc:mysql://192.168.126.10:3306/test"  # test为MySQL的数据库
properties = {
    "user": "root",  # MySQL用户名
    "password": "123456",  # MySQL密码
    "driver": "com.mysql.jdbc.Driver" }# MySQL驱动

# 将相关的数据分析结果存储到Linux的MySQL,用于windows端数据可视化。

write_to_mysql(data_ttl_item_copy, url, properties, 'data_ttl_item')  # 2.总体商品分析
write_to_mysql(data_ttl_time_copy, url, properties, 'data_ttl_time')  # 3.总体时间分析(按年和月)
write_to_mysql(data_ttl_weekday_copy, url, properties, 'data_ttl_weekday')  # 4.总体星期几分析
write_to_mysql(data_ttl_country_copy, url, properties, 'data_ttl_country')  # 5.总体国家分析
write_to_mysql(data_ttl_country_time_copy, url, properties, 'data_ttl_country_time')  # 6.总体国家时间分析(按国家、年和月)

# 后面其他PySpark DataFrame数据需要写入MySQL的,直接调用函数即可。Pandas DataFrame数据不能调用该函数。

  在IDEA的调试模式下,可以查看Pandas的DataFrame数据。

2.3.2 异常值检测与处理

异常值检测与处理:用箱线图对分组后的数据做异常值检测,删除异常值。
  使用np.percentile函数计算特定百分位数,箱线图的异常值上界和下界通常定义为Q3 + 1.5IQR(上界)和Q1 - 1.5IQR(下界),其中Q1是第一四分位数(25%分位数),Q3是第三四分位数(75%分位数),IQR = Q3 - Q1是四分位距。
  代码如下:

#  -------------------------------------(二)异常值检测及处理-------------------------------------------------------------------
# data_ttl_trans["Quantity"].plot(kind='boxplot')
plt.boxplot(data_ttl_trans["Quantity"])  # 绘制总体交易成交量的箱线图,查看异常值
plt.show()

plt.boxplot(data_ttl_trans["Total_Price"])  # 绘制总体交易成交额的箱线图,查看异常值
plt.show()

# 销售数据极不平衡,有些单子里的销售额或销售量远远超过正常标准,因此在作分布图时,将箱线图中的离群点去除。
"""
np.percentile函数用于计算特定百分位数,而箱线图的异常值上界和下界通常定义为Q3 + 1.5*IQR(上界)和Q1 - 1.5*IQR(下界),
其中Q1是第一四分位数(25%分位数),Q3是第三四分位数(75%分位数),IQR = Q3 - Q1是四分位距。
"""
# 计算四分位数
Q1_quantity = np.percentile(data_ttl_trans["Quantity"], 25)  # 25%分位数
Q3_quantity = np.percentile(data_ttl_trans["Quantity"], 75)  # 75%分位数
IQR_quantity = Q3_quantity - Q1_quantity  # 四分位距

# 计算箱线图异常值上界(注意:这里只计算了上界,下界可以类似计算)
upper_bound_quantity = Q3_quantity + 1.5 * IQR_quantity
print("成交量箱线图上界:", upper_bound_quantity)

# 对Total_Price做同样的计算
Q1_total_price = np.percentile(data_ttl_trans["Total_Price"], 25)
Q3_total_price = np.percentile(data_ttl_trans["Total_Price"], 75)
IQR_total_price = Q3_total_price - Q1_total_price
upper_bound_total_price = Q3_total_price + 1.5 * IQR_total_price
print("成交额箱线图上界:", upper_bound_total_price)

# 使用histplot绘制分布图
# 注意:这里假设了某个阈值来限制绘图的数据范围,但这通常不是必需的,除非您想要专注于数据的某个子集

plt.hist(data=data_ttl_trans[data_ttl_trans["Quantity"] <= upper_bound_quantity], x="Quantity")
plt.title("Distribution of Quantity (limited)")
plt.show()

plt.hist(data=data_ttl_trans[data_ttl_trans["Total_Price"] <= upper_bound_total_price], x="Total_Price")
plt.title("Distribution of Total Price (limited)")
plt.show()
# 从分布图上分布看依旧明显右偏趋势,表明了有极少数的订单拥有极大的销售量与销售额,远超正常订单的水平。

# 总体商品成交量异常值检测 及分布图
plt.boxplot(data_ttl_item['Quantity'])  # 总体商品成交量的箱线图
plt.show()

Q1_quantity = np.percentile(data_ttl_item["Quantity"], 25)  # 25%分位数
Q3_quantity = np.percentile(data_ttl_item["Quantity"], 75)  # 75%分位数
IQR_quantity = Q3_quantity - Q1_quantity  # 四分位距
upper_bound_quantity = Q3_quantity + 1.5 * IQR_quantity  # 计算箱线图异常值上界
print("成交量箱线图上界:", upper_bound_quantity)

plt.hist(data=data_ttl_item[data_ttl_item["Quantity"] <= upper_bound_quantity], x="Quantity")
plt.title("总体商品成交量的分布图")
plt.show()

成交量(Quantity)箱线图如下:
在这里插入图片描述

成交额(Total_Price)箱线图如下:
在这里插入图片描述

  上两图所示,销售数据极不平衡,有些单子里的交易量或交易额远远超过正常标准,因此在作分布图时,将箱线图中的离群点去除。
  剔除异常值后的成交量(Quantity)分布图如下:
在这里插入图片描述

剔除异常值后的成交额(Total_Price)分布图如下:
在这里插入图片描述

  从分布图上看,分布依旧明显右偏趋势,表明了有极少数的订单拥有极大的销售量与销售额,远超正常订单的水平。
  此处并没有删除数据的异常值。实际上,异常值检测与处理,应在数据分组前进行,也就是在“2.2 数据导入,数据预处理”部分,对原始数据做异常值检测与处理。后续将对这一点进行改进。

2.3.3 总体销售情况的数据可视化

  数据可视化部分可以放在Windows端的Flask部分,用PyEcharts绘图,最后渲染到web页面,做成一个可视化大屏。
  总体时间分析(按年和月)(data_ttl_time),可视化绘图如下:
在这里插入图片描述

可以看出,2011年12月,成交量和成交额都达到了最大值。

总体星期几分析(data_ttl_weekday),可视化绘图如下:

在这里插入图片描述
  上图可以看出,周一的成交额较少,周二至周六的成交额起伏较为平缓,周日的成交额的数据缺失。

  总体国家分析(data_ttl_country),绘制成交额排名前5的国家和剩余国家成交额的饼图如下:
在这里插入图片描述
  总体上来看,总销售额(成交额)的绝大部分都是来自于英国销售,占比高达88.2%,紧随其后的是新西兰、德国、法国和澳大利亚,占总销售额的比例很小。

  总体国家分析(data_ttl_country)中,比较每年每月英国成交额和其他国家成交额总和,绘制的堆叠柱形图如下:

在这里插入图片描述
  由上图可以看出,从时间线上来看,每个月都是英国销售额占主体。

  具体的代码如下:

# ----------------------------------(三)数据可视化(这部分可以放在windows端,用PyEcharts绘图)-------------------------
#  ------------------1.data_ttl_time总体时间分析(按年和月)可视化-----------------------
data_ttl_time = data_ttl_time.sort_values(['Year', 'Month'])  # 按年和月排序
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="成交量")
ax1.set_ylabel("Quantity", color="gray")
plt.legend(loc='upper left')

ax2 = ax1.twinx()  # 创建第二个y轴
ax2.plot(data_ttl_time["Total_Price"], color="red", label="成交额")
ax2.set_ylabel("Total_Price", color="red")

plt.legend(loc='upper right')
plt.show()  # 从时间轴上来看,销售额有一个明显的上升趋势,到了11月有一个明显的高峰,而到12月则有所回落。

# --------------------------2.总体星期几分析 可视化-----------------------------
plt.bar(data_ttl_weekday["DayOfWeek"],
        data_ttl_weekday["Total_Price"],
        width=0.5, label="成交额")
plt.legend()
plt.show()

# -----------------------------3.总体国家分析可视化----------------------------

data_ttl_country.set_index("Country", inplace=True)  # 设置索引为 Country 列
top_five = data_ttl_country["Total_Price"].sort_values(ascending=False)[:5]  # 排序 Total_Price 并取前五个
others = data_ttl_country["Total_Price"].sort_values(ascending=False)[5:].sum()  # 计算其他国家的总和

pie_data = top_five.copy()
pie_data = pie_data.append(pd.Series([others], index=["others"]))  # 创建一个新的 Series 来包含前五个国家和“其他”的总和

# 绘制饼图
plt.pie(pie_data, labels=pie_data.index, autopct='%1.1f%%')
plt.axis('equal')  # 确保饼图是圆形
plt.legend(loc="upper right")
plt.show()

# -----------------4.data_ttl_country_time总体国家时间分析(按国家、年和月)可视化-------------

data_ttl_country_time["isUnitedKingdom"] = ["UK" if i == "United Kingdom" else "Not UK" for i in
                                            data_ttl_country_time["Country"]]  # 创建isUnitedKingdom列
data_ttl_country_time_isUK = data_ttl_country_time.groupby(["isUnitedKingdom", "Year", "Month"])[
    "Total_Price"].sum().reset_index()  # 分组并计算成交额

data_ttl_country_time_isUK.index = [str(i) + "\n" + str(j) for i, j in
                                    zip(data_ttl_country_time_isUK["Year"],
                                        data_ttl_country_time_isUK["Month"])]  # 重建索引
UK = data_ttl_country_time_isUK[data_ttl_country_time_isUK["isUnitedKingdom"] == "UK"]  # 英国UK的每年每月成交额
NUK = data_ttl_country_time_isUK[data_ttl_country_time_isUK["isUnitedKingdom"] == "Not UK"]  # 非英国UK的每年每月成交额

plt.figure(figsize=(8, 8))
plt.bar(UK.index, UK["Total_Price"], color="#66c2a5", label="英国")
plt.bar(NUK.index, NUK["Total_Price"], bottom=UK["Total_Price"], color="#8da0cb", label="其他国家")
plt.legend()
plt.show()  # 总体上来看,总销售额的绝大部分都是来自于英国销售,而从时间线上来看,每个月都是英国销售额占主体。

2.3.4 英国是否存在季节性商品(按季度分组)

  将2011年4个季度的交易量、成交量以及成交额分别汇总并排序,看看有没有哪个商品在各个季度的排名有明显的差距。也就是分别在交易量、成交量以及成交额找出季节性波动最大的一个商品(即:有的月份量大,有的月份量小),据此可以判断是不是季节性商品。
“No_Of_Trans”(不同购物小票的数量)、“Quantity”(成交量)和 “Total_Price”(成交额)。
  尽量避免在Pandas和Spark之间频繁转换,因为Pandas DataFrame的操作通常不适合大规模数据集。尽量在Spark环境中完成所有的数据分析与处理。
  编程思路如下
1.选取英国2011年的数据,将1-12月转换为1-4季度。
2.确定每季度每件商品的交易订单量、成交量、成交额排名,给每件商品标注排名并添加新列。
3.选取四季度交易量、成交量、成交额3个维度排名的商品,四季度的前10商品名去重后按照维度放在一起。此时,存放的每个维度的商品数量(集合)有可能大于10件。
4.是否是季节性商品,也就是分析四季度排名前10的商品集合,分别在1、2、3、4季度的排名,然后计算四季度排名差,保存最大排名差。
  如某商品PAPER CHAIN KIT VINTAGE CHRISTMAS (复古圣诞纸链套装)的交易量No_Of_Trans在第1、2、3、4季度的排名分别为1669、1734、110、4,值(交易量)分别为:11、9、143、509,因此该商品的排名差为:1734-4=1730。排名差越大,说明是季节性商品的可能性越大。
5.对交易订单量、销售量、销售额3个维度的商品排名差进行降序排序,选取排名差最大的3个商品。
6.分别绘制交易订单量、销售量、销售额3个维度排名差最大的3个商品四个季度的对应值的饼图。
  代码如下:

# -------------------(四)英国是否存在季节性商品(按季度)-------------------------------
"""
将2011年的4个季度的成交量、销售量以及销售额分别汇总并排序,看看有没有哪个商品在各个季度的排名有明显的差距。
也就是分别在成交量、销售量以及销售额找出季节性波动最大的一个商品(有的月份量大,有的月份量小),据此可以判断它们是季节性商品。
"No_Of_Trans"(不同账单号的数量)、"Quantity"(每件商品的销售量)和 "Total_Price"(每件商品的销售额)。
尽量避免在Pandas和Spark之间频繁转换,因为Pandas DataFrame的操作通常不适合大规模数据集。如果可能,尽量在Spark环境中完成所有处理。
编程思路如下:
1.选取英国2011年的数据,将1-12月转换为1-4季度。
2.确定每季度每件商品的交易订单量、销售量、销售额排名,并添加列给每件商品标注排名。
3.选取四季度交易订单量、销售量、销售额3个维度排名的商品,四季度前10商品名去重后按照维度一起存放。此时,存放的每个维度的商品数量(集合)有可能大于10件。
4.是否存在季节性商品,也就是分析四季度排名前10的商品集合,分别在1、2、3、4季度的排名,然后计算四季度排名差,保存最大排名差。
如某商品PAPER CHAIN KIT VINTAGE CHRISTMAS (复古圣诞纸链套装)的交易量No_Of_Trans在第1、2、3、4季度的排名分别为1669、1734、110、4,
值(交易量)分别为:11、9、143、509,因此该商品的排名差为:1734-4=1730。排名差越大,说明是季节性商品的可能性越大。
5.对交易订单量、销售量、销售额3个维度的商品排名差进行降序排序,选取排名差最大的3个商品。
6.分别绘制交易订单量、销售量、销售额3个维度排名差最大的3个商品四个季度的对应值的饼图。
"""
# 1.选取英国2011年的数据,将1-12月转换为1-4季度。
df_UK = data.filter((col("Country") == "United Kingdom") & (col("Year") == 2011))  # 只选取UK英国2011年的全年数据
df_UK = df_UK.withColumn("Season", floor((col("Month") - 1) / 3) + 1)  # 将1-12月转换为相应的1-4季度,新增一列:季度

# 分组与聚合:按季度、商品名称分组,统计季度的成交量、销售量、销售额
df_UK_seasonal = df_UK.groupby(["Season", "Itemname"]).agg(
    countDistinct("BillNo").alias("No_Of_Trans"),  # 成交量
    sum("Quantity").alias("Quantity"),  # 销售量
    sum("Total_Price").alias("Total_Price")  # 销售额
)

print("\n按季度、商品名称分组,统计季度的成交量、销售量、销售额:\n")
df_UK_seasonal.orderBy('Itemname').show(20, False)
print('df_UK_seasonal的行数:', df_UK_seasonal.count())

df_UK_seasonal = df_UK_seasonal.toPandas()


# 排序
# 2.确定每季度每件商品的交易订单量、销售量、销售额排名,并添加列给每件商品标注排名。

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"])  # 按商品名分组,再按季节,计算No_Of_Trans,Quantity,Total_Price的总和
    for col in ["No_Of_Trans", "Quantity", "Total_Price"]:
        df[col + "_Rank"] = df[col].rank(ascending=False)  # 确定每件商品的交易数,销售量,销售额的排名,并添加相应的排名列
    return df


df_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")

print(df_UK_all)
print(df_UK_fourth)
"""
在Apache Spark的PySpark模块中,Window函数是一个非常强大的工具,它允许你在保持数据集为分布式的同时,执行复杂的分组聚合操作,
这些操作类似于SQL中的窗口函数(Window Functions)或分析函数(Analytic Functions)。Window函数不是直接对数据集进行分区,
而是定义了一个“窗口”或“框架”,在这个窗口内可以对数据进行聚合或排序等操作,而不需要将数据实际地分组到一起。
partitionBy:分组,
"""
# 全年交易量、销售量、销售额排名

# 3.选取四季度交易订单量、销售量、销售额3个维度排名的商品,四季度前10商品名去重后按照维度一起存放。此时,存放的每个维度的商品数量(集合)有可能大于10件。
dfs = [df_UK_all, df_UK_first, df_UK_second, df_UK_third, df_UK_fourth]
item_set_No_Of_Trans = set()  # 如果 DataFrame 中的有关列 包含重复值,并且这些值在排名前十的列表中,则它们在集合中只会出现一次。这是集合(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)

print("全年及四个季度排名前10的商品的集合(商品不重复):\n")
pd.set_option('display.max_columns', None)  # 显示所有列
print(item_set_No_Of_Trans)  # 全年及四季度交易订单量排行前十的商品的集合
print(item_set_Quantity)  # 全年及四季度销售量排行前十的商品的集合
print(item_set_Total_Price)  # 全年及四季度销售额排行前十的商品的集合

## 每个季度的是否有所不同?
"""
4.是否存在季节性商品,也就是分析四季度排名前10的商品集合,分别在1、2、3、4季度的排名,然后计算四季度排名差,保存最大排名差。
如某商品PAPER CHAIN KIT VINTAGE CHRISTMAS (复古圣诞纸链套装)的交易量No_Of_Trans在第1、2、3、4季度的排名分别为1669、1734、110、4,
值(交易量)分别为:11、9、143、509,因此该商品的排名差为:1734-4=1730。排名差越大,说明是季节性商品的可能性越大。
"""
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)]:  # 3个维度dim:No_Of_Trans,Quantity,Total_Price
    for i in list(itemset):  # 遍历 全年及四季度排名前十的商品名,逐一取出商品名给i
        min_rank = 9999
        max_rank = -1
        for j, df in enumerate(dfs):  # 遍历全年及四季度交易订单量,销售量,销售额的排名数据,j是dfs的索引。
            df = df.reset_index()  # 重置索引,索引号为原来的自然编号
            if j == 0:  # j=0,表示取出的是全年订单量,销售量,销售额的排名数据
                RANK_DF["Season"].append("all")
            else:
                RANK_DF["Season"].append(df["Season"].values[0])
            sub_df = df[df["Itemname"] == i]  # 比对排名前十商品名,抽取该商品数据信息(包含3项排名信息)
            if sub_df.shape[0] == 0:
                curr_rank = 9999
                curr_value = 0
            else:
                curr_rank = df[df["Itemname"] == i][dim + "_Rank"].values[0]  # 该商品在该季度对应维度的排名
                curr_value = df[df["Itemname"] == i][dim].values[0]  # 提取该商品对应维度的数据,如该商品交易量是多少。
            min_rank = __builtins__.min(curr_rank,
                                        min_rank)  # 商品的当前排名curr_rank和最小排名min_rank比较,更新min_rank。使用python内置的min函数,而不是pyspark。
            max_rank = __builtins__.max(curr_rank, max_rank) if curr_rank < 9999 else max_rank
            RANK_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  # 将该排名差放在该商品的全年及四季度的Range列里面。每件商品出现5次,对应全年及四季度的数据
RANK_DF = pd.DataFrame(RANK_DF)  # 将字典转成pandas的dataframe
print(RANK_DF)

# “每个季度的量相差很多”的,但是本身很多的
"""
5.对交易订单量、销售量、销售额3个维度的商品排名差进行降序排序,选取排名差最大的3个商品。
6.分别绘制交易订单量、销售量、销售额3个维度排名差最大的3个商品四个季度的对应值的饼图。
"""
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')]  # 取出对应维度的四季度数据,剔除全年(all)数据。
    tmp_df["Range_Rank"] = tmp_df["Range"].rank(ascending=False)  # 对排名差 进行降序排序,并添加一列数据Range_Rank
    tmp_df = tmp_df.sort_values("Range_Rank").reset_index(drop=True)  # 重置索引,且不在新的DataFrame中保留旧的索引作为一列
    for j in range(3):  # 提取每个维度的前j=3个商品,绘制每个季度对应维度(如No_Of_Trans交易量)数据的饼图。
        tmp_df_rank = tmp_df.loc[4 * j:4 * (j + 1) - 1, :]  # 分别提取前3个商品的4行数据(四季度)
        this_item = list(set(tmp_df_rank["item"]))[0]  # 从tmp_df_rank的"item"列中选取一个唯一的(不重复的)元素,集合set自动过滤重复值
        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()
"""
#上图中的商品,是在全年的销售中名销售额/销售了/成交量列前茅的前3个商品,但是从饼图上来看,这些商品明显是会集中在某一个季度中畅销。
把颗粒度缩小到按月份来看,计算每月销售额占总销售额的比重(季节指数);并以此排序看看哪些商品的季节指数极差最大。
"""

  选取排名差最大的前3个商品,计算交易量、成交量和成交额在四季度中所占的比例。绘制的饼图如下:
在这里插入图片描述
上图中,
  第一行3个饼图,表示四个季度中成交量排名相差最大的前3个商品,在四个季度中的交易量占比。如商品“PAPER CHAIN KIT VINTAGE CHRISTMAS(复古圣诞纸链套装)”是季节性商品,在第4季度的交易量占比最大。
  第二行的3个饼图,表示四个季度中成交量排名相差最大的前3个商品,在四个季度中的成交量占比。
  第三行的3个饼图,表示四个季度中成交额排名相差最大的前3个商品,在四个季度中的成交额占比。

2.3.5 哪些商品的季节指数极差最大(按月判断)

  上图中的商品,是按季度划分,交易量、成交量、成交额名列前茅的前3个商品,从饼图上来看,这些商品明显集中在某一个季度中畅销。如果按月分组,哪些商品季节指数极差最大?把数据分析颗粒度缩小到按月份分组,计算每月交易量、成交量、成交额/全年月平均交易量、成交量、成交额,即:季节指数,并以此排序看看哪些商品的季节指数极差最大。

  代码如下:

# ------------(五)哪些商品的季节指数极差最大(按月判断)---------------------
# 把颗粒度缩小到按月份来看,计算每月交易量/全年月平均交易量的(季节指数);并以此排序看看哪些商品的季节指数极差最大。以此类推,再计算成交量、成交额的季节指数
from pyspark.sql import functions as F

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]]  # 选取Itemname列和含seasonal_index的列。

# -------------月度极差可视化(每个维度排名前3的商品)-------------
# 提取月度极差排名前3的商品,绘制其1-12月相应维度数据的折线图
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()  # 选择相应维度的极差指数的前3名 商品
    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]  # df_UK_monthly查找相应物品的索引
        tmp_df = df_UK_monthly.loc[idxs, ["Itemname", "Month", col.replace("seasonal_index_","")]]  
        # 根据这些索引,从df_UK_monthly中选取对应的商品名称、月份和相应的交易量/销售量/销售额
        tmp_df = tmp_df.pivot(index="Month", columns="Itemname",
                              values=col.replace("seasonal_index_", ""))  
        # 将tmp_df数据透视,以月份为行索引,商品名称为列索引,交易量/销售量/销售额为数据
        plt.plot(tmp_df, marker='o')
        plt.ylabel(col.replace("seasonal_index_", ""))
        plt.title(item)
plt.subplots_adjust(wspace=0.2, hspace=0.2) # 调整子图间距。wspace=0.2,水平间距为 整图的20%,hspace=0.2,垂直方向
plt.show()

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

# -----------将全年及每个季度排名top10的商品的交易量、成交量、成交额作水平柱形图,图中商品旁边标出了它对应交易量/成交量/成交额的极差排名--------
# 对RANK_DF按Dimension分组,并取每个小组的第一个非空行,然后再选择"Dimension", "item", "Range"三列数据。
ranges = RANK_DF.groupby(["Dimension", "item"]).first().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()  # 选取全年或1、2、3、4季度的数据
    # [i for i in df.index if df.loc[i, "Itemname"] in item_set_No_Of_Trans] 选取在排名前10商品表item_set_No_Of_Trans
    # 中的df的行索引,再选取df的["Itemname", "No_Of_Trans"]两列。
    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, :]  # 选取 交易量前10 商品。
    df = pd.merge(df, range_df, left_on="Itemname", right_on="item", how="left")  # 两表左合并,使整表的商品名Itemname和Range列。
    # df["Itemname"] + " " + df["Range"].astype("str")条形图的标签,df["No_Of_Trans"]条形图长度
    plt.barh(df["Itemname"] + " " + df["Range"].astype("str"), df["No_Of_Trans"])
    plt.title("No_Of_Trans " + titles[n])
plt.subplots_adjust(hspace=0.4) # 调整子图间距。wspace=0.2,水平间距为 整图的20%,
plt.show()

# Quantity成交量排名前十商品,及商品相应的极差
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.subplots_adjust(hspace=0.4) # 调整子图间距。wspace=0.2,水平间距为 整图的20%,
plt.show()

# Total_Price成交额排名前十商品,及商品相应的极差
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.subplots_adjust(hspace=0.4) # 调整子图间距。wspace=0.2,水平间距为 整图的20%,
plt.show()

  月度极差排名前3的商品,绘制其1-12月相应维度数据的折线图,如下图所示。
在这里插入图片描述
  全年及每个季度交易量、成交量、成交额排名top10的商品的,图中商品旁边标出了它对应交易量、成交量、成交额的极差排名。交易量的水平柱形图如下所示。
在这里插入图片描述
  成交量的柱形图如下所示:
在这里插入图片描述

  成交额的柱形图如下所示:
在这里插入图片描述

  可以看到,虽然大多数在全年销售中获得的top10排名极差都较小(总共有3000+个商品), 但是在总成交额的全年top10中,依然有一个明显存在季节性效应的商品(PAPER CHAIN KIT 50’s CHRISMAS)。

2.3.6 同一个商品在不同维度上全年排名之间的排名差

  计算每件商品在交易量、成交量、成交额的排名,并将这3个维度排名之差最大的5个商品列出。
  代码如下:

# --------(六)同一个商品在不同维度上的全年排名是否排名差距较大---------
"""
计算了每件商品在交易量、成交量、成交额的全年排名,并将这3个维度排名之差最大的5个商品列出了。

"""

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)

# df_UK_all.shape[0])表示行数。遍历df_UK_all所有行,取出交易量、成交量、成交额的排名,计算这3个数的最大最小值。
# 再计算最大最小值之差Rank_Gaps,即同一商品的排名差。
Rank_Gaps = [
    __builtins__.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"]) \
    - __builtins__.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_Gaps  #

df_ranked_all = df_UK_all.sort_values("Rank_Gaps", ascending=False).reset_index(drop=True)  # 最大最小值之差 降序排序

# --------------------------可视化:在交易量、成交量、成交额全年排名相差最大的5个商品----------------------
plt.figure(figsize=(20, 20))
for i in range(5):  # 取5个商品
    plt.subplot(5, 1, i + 1)
    sub = df_ranked_all.iloc[i, :]  # 逐一取出5行数据(5个商品的数据)
    item_name = sub["Itemname"]
    sub = dict(sub)  # 将pandas的dataframe做成字典。方便sub["No_Of_Trans_Rank"]等取值。。
    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.subplots_adjust(hspace=0.4) # 调整子图间距。wspace=0.2,水平间距为 整图的20%,
plt.show()

本商品全年交易量、成交量、成交额排名之差最大的前5个商品,如下图所示。
在这里插入图片描述

  可以发现上图中的商品,有些虽然成交额与成交量名列前茅,但是成交量却排名靠后。特别是第一名(PAPER CRAFT, LITTLE BRIDE),实际看表格数据时会发现它只有1个单子,但是那1个单子却订了8万多个这个商品。

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

  构造以下3个KPI(每月):
  客单量(UPT, Units Per Transaction)= 成交量(Quantity)/交易量(No_Of_Trans), 客单量是指平均每笔交易所包含的商品数量。
  平均售价(ASP, Average Selling Price)= 成交额(Total_Price)/ 成交量(Quantity),平均售价是指售出商品的平均价格。
  客单价(ATV, Average Transaction Value)= 成交额(Total_Price)/ 成交量(No_Of_Trans),客单价是指平均每笔交易的总金额。

  客单量,平均每笔交易所包含的商品数量,如下图所示。
在这里插入图片描述

平均售价,售出商品的平均价格,如下图所示。
在这里插入图片描述

客单价,平均每笔交易的总金额,如下图所示。
在这里插入图片描述

每月交易量,如下图所示。
在这里插入图片描述
  11月的高成交额实际上是由于11月的高交易量。尽管12月份的成交额再次下降,但12月份的订单大多都是大单子。

3 数据挖掘—关联规则分析

  关联规则分析为的就是发现商品与商品之间的关联。通过计算商品之间的支持度、置信度与提升度,分析哪些商品有正向关系,顾客愿意同时购买它们。
  此处使用了pyspark自带的FPGrowth算法。它和APRIORI算法一样都是计算两两商品之间支持度置信度与提升度的算法,虽然算法流程不同,但是计算结果是一样的。
  关联规则算法只需要每个订单商品名称,不需要其他数据。因此,每个订单的商品名称放在一个列表里,整理后的 数据集如下图所示。
在这里插入图片描述
  利用Pyspark机器学习库的关联规则模型,对上述数据集进行关联分析,代码如下:

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"))
"""
# 将同一个订单号的商品集合到一个列表。
df_UK_concatenated = df_UK \
    .groupby(["BillNo", "Itemname"]) \
    .agg(F.sum("Quantity").alias("Quantity"))  # 按"BillNo", "Itemname"分组,计算Quantity的总数(分组后必须要有一个运算式子)
print("关联规则,按BillNo, Itemname分组:")
df_UK_concatenated.show(10, False)

df_UK_concatenated = df_UK_concatenated \
    .groupby("BillNo") \
    .agg(F.collect_list(F.col("Itemname")).alias("items"))
print("关联规则,按BillNo分组,将分组内的Itemname列的值,收集到列表items:")
df_UK_concatenated.show(10)

model = FPGrowth(minSupport=0.03, minConfidence=0.3)
model = model.fit(df_UK_concatenated.select("items"))

res = model.associationRules.toPandas()

# -----------------------关联规则可视化-----------------------
combs = [('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()

# 支持度(support)前三的商品组合(每2条实际上是同一组商品)。
print(res.sort_values("support", ascending=False).head(6))

# 置信度(confidence)前5的商品组合,置信度指的是当商品A被购买时,商品B也被购买的概率。
print(res.sort_values("confidence", ascending=False).head(5))

# 提升度(lift)前三的商品组合(每2条实际上是同一组商品)。 提升度可以看做是2件商品之间是否存在正向/反向的关系。
print(res.sort_values("lift", ascending=False).head(6))

(1) Antecedent(前件)
  定义:在关联规则中,if部分称为前提(Antecedent),表示某些物品的组合。它是规则中用于推断或预测的条件部分。
  示例:在规则“购买牛奶 → 购买面包”中,“购买牛奶”是前件。
(2) Consequent(后件)
  定义:在关联规则中,then部分称为结果(Consequent),表示其他物品的组合。它是在前件满足时可能发生的结果。
  示例:在规则“购买牛奶 → 购买面包”中,“购买面包”是后件。
(3) Confidence(置信度)
  定义:置信度是关联规则的可信程度,表示在包含前件的事务中,同时也包含后件的事务所占的比例。
  示例:如果购买牛奶的顾客中有70%也购买了面包,则规则“购买牛奶 → 购买面包”的置信度为70%。
(4) Lift(提升度)
  定义:提升度表示在包含前件的事务中同时包含后件的比例,与仅包含后件的事务的比例之间的比值。它反映了前件对后件出现的提升作用。
  示例:如果购买牛奶的顾客中购买面包的比例是70%,而所有顾客中购买面包的比例是50%,则规则“购买牛奶 → 购买面包”的提升度为1.4(70% / 50%)。
(5) Support(支持度)
  定义:支持度是项集在数据集中出现的频率或概率,表示同时包含前件和后件的事务占所有事务的比例。
  示例:如果100个购物交易中,有30个交易同时包含了牛奶和面包,则规则“购买牛奶 → 购买面包”的支持度为30%。

  按支持度(support)降序排序的商品组合(每2条实际上是同一组商品)。
在这里插入图片描述
  第一条关联规则:[‘JUMBO BAG PINK POLKADOT’]----->[‘JUMBO BAG RED RETROSPOT’],
  置信度confidence为0.67541,表示购买了JUMBO BAG PINK POLKADOT商品的顾客,有67.541%的概率会购买商品JUMBO BAG RED RETROSPOT。
  提升度lift为6.10348,如果该值大于1,则说明该关联规则有效,值越大,有效性越强。
  支持度support为0.04534,表示有4.5%的交易包含了商品JUMBO BAG PINK POLKADOT和商品JUMBO BAG RED RETROSPOT,即这两样商品在所有交易中出现的频率。

  置信度(confidence)降序排名的商品组合,置信度指的是当商品A被购买时,商品B也被购买的概率。
在这里插入图片描述
  关联规则:[‘PINK REGENCY TEACUP AND SAUCER’]---->[‘GREEN REGENCY TEACUP AND SAUCER’]的置信度confidence为0.81974,提升度lift为15.50452,支持度support为0.03478。

  提升度(lift)降序排名的商品组合(每2条实际上是同一组商品)。 提升度可以看做是2件商品之间是否存在正向/反向的关系。
在这里插入图片描述
  [‘PINK REGENCY TEACUP AND SAUCER’]—>[‘GREEN REGENCY TEACUP AND SAUCER’]的置信度confidence为0.81974,提升度lift为15.50452,支持度support为0.03478。
  可将关联规则模型推荐的结果存储在MySQL数据库,再通过Flask在可视化大屏展示。

4 数据可视化大屏的设计

  这部分内容可参考作者写的另外一篇文章,Flask+Pyecharts+大数据集群:数据可视化大屏的实现:链接: https://blog.csdn.net/chendengyi2/article/details/1399371066,“六、Flask+Pyecharts+大数据集群(Linux):绘制数据可视化大屏。
  前期准备:安装PyEchats,Flask等组件。

  Flask:是一个轻量级、灵活、可扩展的Web应用框架,使用Python编写,适合用于构建中小型Web应用程序。它提供了基本的路由、模板引擎、URL构建、错误处理等功能,并支持插件和扩展来增强其功能。
Flask提供了一个路由系统,可以将不同的URL路径映射到相应的处理函数上。当用户访问特定的URL时,Flask会调用相应的处理函数,并返回响应。
Flask内置了一个基于Jinja2的模板引擎,开发者可以使用模板来渲染HTML页面。通过将动态数据传递给模板,并使用模板语言来定义页面的结构和样式,开发者可以轻松地创建和管理Web页面的外观和布局。

  Pyecharts:是一款基于Python的开源数据可视化库,它整合了Echarts与Python的优势,使得在Python环境中能够轻松创建出美观且交互性强的图表。Pyecharts支持多达数十种图表类型,包括折线图、柱状图、散点图、饼图等常见图表,以及地图、热力图、关系图等特色图表。这些丰富的图表类型能够满足不同场景下的数据可视化需求。
  Pyecharts的基本教程可以参考本文作者的另外一篇文章:Pyecharts快速入门及高清图片保存:链接: https://blog.csdn.net/chendengyi2/article/details/139878882

  在IDEA项目上创建3个目录:datastatictemplates,和1个python文件:main.py。data用来存储本地数据,static用来存放Echarts的图表和组件模板文件,templates用来存放html文件,项目的目录结构如下图所示。
在这里插入图片描述
  在templates文件上单击鼠标右键,将该目录标记为模板文件夹,Flask默认渲染该文件下的HTML文件。
在这里插入图片描述
  到Echarts官网下载模板和组件json文件:链接: https://echarts.apache.org/zh/download.html
  将下载的echats.js文件复制到static目录。
  在templates目录下新建html文件,用于展示可视化图形,选择HTML 5文件格式,命名为show_pyecharts_05。
在这里插入图片描述
  打开main.py文件,清空里面的内容,输入以下代码:


import json
import pyecharts.charts
from flask import Flask, render_template
from pyecharts.options import *
from markupsafe import Markup  # 导入 Markup,用于在 Flask 模板中安全地渲染 HTML
from pyecharts import charts
import pandas as pd
from sqlalchemy import create_engine
from pyecharts.charts import Pie, Bar, Line
from pyecharts.options import LabelOpts, TitleOpts, LegendOpts
import pymysql
from sqlalchemy import create_engine

app = Flask(__name__)  # 创建一个 Flask 应用实例


# ---------------4.读取集群的MySQL数据,绘制可视化大屏------------------------
# 自定义函数,读master节点的MySQL数据

def connFun_linux(sql_query):
    engine = create_engine("mysql+pymysql://root:123456@192.168.126.10:3306/test")
    try:
        data = pd.read_sql(sql_query, engine)
        return data
    except Exception as e:
        print(f"An error occurred: {e}")
        raise  # 可选:重新抛出异常以便在外部捕获     '''


# 读取master节点(Linux系统)中的MySQL数据,自定义绘图函数
def get_pie_linux():
    sql = '''select * from data_ttl_country'''
    data = connFun_linux(sql)  # 确保这个函数返回的是DataFrame

    # data.set_index("Country", inplace=True)
    top_five = data.sort_values("Quantity", ascending=False)[:5]
    others_quantity = data.sort_values("Quantity", ascending=False)[5:]['Quantity'].sum() # 其余国家的销售量和销售额总和
    others_total_price = data.sort_values("Quantity", ascending=False)[5:]['Total_Price'].sum() # 其余国家的销售量和销售额总和
    top_five2 = top_five.copy()
    others_df = pd.DataFrame(
        {"Country": ["others"], "Quantity": [others_quantity], "Total_Price": [others_total_price]})
    pie_data = pd.concat([top_five2, others_df], ignore_index=True)

    country = list(pie_data["Country"])
    quantity = list(pie_data['Quantity'])
    data_list = [list(z) for z in zip(country, quantity)]
    c = (
        Pie()
            .add("", data_list, radius=["40%", "75%"])
            .set_global_opts(title_opts=TitleOpts(title="成交量"),
                             legend_opts=LegendOpts(orient="vertical", pos_top="15%", pos_left="2%"))
            .set_series_opts(label_opts=LabelOpts(formatter="{b}: {c}"))
    )
    return c


def get_bar_linux():
    sql = '''select * from data_ttl_weekday  '''
    data = connFun_linux(sql)
    data = data.sort_values('DayOfWeek', ascending=True)
    # x = list(data["DayOfWeek"])
    x = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']
    y1 = list(data['Quantity'])
    c = (
        pyecharts.charts.Bar()
            .add_xaxis(x)
            .add_yaxis("成交量(Quantity)", y1)
            .set_global_opts(title_opts=TitleOpts(title="周一到周日成交量", subtitle="bar"),
                             xaxis_opts=AxisOpts(axislabel_opts=LabelOpts(rotate=45)), )
    )
    return c


def get_line_linux():
    sql = '''select * from data_ttl_time  '''
    data = connFun_linux(sql)
    data = data.sort_values(['Year', 'Month'])

    x = [str(i) + "\n" + str(j) for i, j in zip(data["Year"], data["Month"])]  # x轴刻度标签
    y2 = list(data["Quantity"])
    y1 = list(data['Total_Price'].round(0))  # 保留2位小数
    c = (
        Line()
            .add_xaxis(x)
            .add_yaxis("成交额", y1,
                       markline_opts=MarkLineOpts(data=[MarkLineItem(type_="average")]),
                       label_opts=LabelOpts(is_show=False), )  # 不显示数据点标签

            .add_yaxis("成交量", y2,
                       markline_opts=MarkLineOpts(data=[MarkLineItem(type_="average")]),
                       label_opts=LabelOpts(is_show=False),
                       markpoint_opts=MarkPointOpts(data=[MarkPointItem(coord=[11, y2[11]], value=y2[11])]), )
            .set_global_opts(title_opts=TitleOpts(title="成交量和成交额折线图"))
    )
    return c


def get_barstack_linux():
    sql = '''select * from data_ttl_country_time  '''
    data_ttl_country_time = connFun_linux(sql)
    data_ttl_country_time["isUnitedKingdom"] = ["UK" if i == "United Kingdom" else "Not UK" for i in
                                                data_ttl_country_time["Country"]]  # 创建isUnitedKingdom列
    data_ttl_country_time_isUK = data_ttl_country_time.groupby(["isUnitedKingdom", "Year", "Month"])[
        "Total_Price"].sum().reset_index()  # 分组并计算成交额

    data_ttl_country_time_isUK.index = [str(i) + "\n" + str(j) for i, j in
                                        zip(data_ttl_country_time_isUK["Year"],
                                            data_ttl_country_time_isUK["Month"])]  # 重建索引
    UK = data_ttl_country_time_isUK[data_ttl_country_time_isUK["isUnitedKingdom"] == "UK"]  # 英国UK的每年每月成交额
    NUK = data_ttl_country_time_isUK[data_ttl_country_time_isUK["isUnitedKingdom"] == "Not UK"]  # 非英国UK的每年每月成交额
    x = list(UK.index)
    y1 = list(UK['Total_Price'])
    y2 = list(NUK['Total_Price'])

    c = (
        Bar()
            .add_xaxis(x)
            .add_yaxis("英国", y1, stack="stack1")
            .add_yaxis("其他国家", y2, stack="stack1")
            .set_series_opts(label_opts=LabelOpts(is_show=False))
            .set_global_opts(title_opts=TitleOpts(title="成交额"), toolbox_opts=ToolboxOpts(is_show=True), )
    )
    return c


@app.route('/show_pyecharts_05')
def show_pyecharts_05():
    pie = get_pie_linux()
    bar = get_bar_linux()
    line = get_line_linux()
    barstack = get_barstack_linux()
    return render_template("show_pyecharts_05.html",
                           pie_options=pie.dump_options(),
                           bar_options=bar.dump_options(),
                           line_options=line.dump_options(),
                           barstack_options=barstack.dump_options(), )


if __name__ == "__main__":
    app.run(host='127.0.0.1', port=5000, debug=True)


  上面的程序,将通过Pandas访问MySQL,读取PySpark存放的数据,读取MySQL数据库的代码如下图所示:
在这里插入图片描述
  图中,192.168.126.10为大数据集群主节点Master的IP地址,3306为MySQL的访问端口,test为MySQL数据库的名字。root为MySQL用户名,123456为密码。

  这部分的编程流程是:先用Pandas读取MySQL数据,PyEcharts绘制图表,Flask再将PyEchart绘制的图表渲染到show_pyecharts_05.html页面。
  打开show_pyecharts_05.html,清空里面的内容,粘贴以下代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>ECharts</title>
    <style >
        /* CSS 样式将放在这里 */
        .chart-container{
            display:flex;
            flex-direction:column;    /* 默认为column,表示垂直布局 */
            justify-content:center;   /* 水平居中 */
            align-items:center;       /* 垂直居中(如果需要的话) */
            height:150vh;             /* 根据需要设置容器高度 */
            width:100%;               /* 占据全屏宽度 */
        }
        .chart-row{
            display: flex;                    /* 每一行都是一个flex容器 */
            justify-content:space-between;    /* 列之间均匀分布 */
            margin-bottom:20px;               /* 行与行之间的间距 */
        }
        .chart-item{
            flex: 1;                         /* 每个flex item占据相等的空间 */
            margin: 0 10px;                  /* 图表之间的间距 */
        }

    </style>
    <!-- 引入刚刚下载的 ECharts 文件 -->
    <script src="/static/echarts.js"></script>
</head>
<body>
<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
<div class="chart-container">
    <div class="chart-row">
        <div class="chart-item"  id="pie" style="width: 800px;height:400px;"></div>
        <div class="chart-item" id="bar" style="width: 800px;height:400px;"></div>
    </div>

    <div class="chart-row">
        <div class="chart-item" id="line" style="width: 800px;height:400px;"></div>
        <div class="chart-item" id="barstack" style="width: 800px;height:400px;"></div>
    </div>
</div>

<script type="text/javascript">
    // 基于准备好的dom,初始化echarts实例
    var pieChart = echarts.init(document.getElementById('pie'));
    var barChart = echarts.init(document.getElementById('bar'));
    var lineChart = echarts.init(document.getElementById('line'));
    var barStackChart = echarts.init(document.getElementById('barstack'))

    // 使用刚指定的配置项和数据显示图表。
    pieChart.setOption({{ pie_options | safe }});
    barChart.setOption({{ bar_options | safe }});
    lineChart.setOption({{ line_options | safe }});
    barStackChart.setOption({{ barstack_options | safe }});

</script>
</body>
</html>

  以下面的数据为例,PySpark已经将这些数据存储在Linux系统的MySQL的test数据库,先读取这些数据,然后Pyecharts绘制图表,Flaskk将图表渲染到页面,制作成可视化大屏,可视化大屏的布局为2行2列。
  总体国家分析(data_ttl_country):按国家(Country)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。
  总体星期几分析(data_ttl_weekday):按星期(DayOfWeek)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。
  总体时间分析(按年和月)(data_ttl_time):按年(Year)、月(Month)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。
  总体国家时间分析(按国家、年和月)(data_ttl_country_time):按国家(Country)、年(Year)、月(Month)分组,计算各组的成交量(Quantity)和成交额(Total_Price)。

在这里插入图片描述
   Flask渲染的可视化大屏如下图所示。
在这里插入图片描述
  PyEcharts绘制的图表更精致美观,图形也更丰富。本案例有很多图形,可以将它们渲染到可视化大屏,如果一个页面放不下,可以渲染到几个页面。

5 总结

  (1)季节性销售波动分析:数据明确显示,英国市场在2011年11月出现了显著的销售量激增,这一趋势很可能与年度购物季(如黑色星期五、圣诞节前购物潮)相吻合,随后在12月虽略有回落,但仍维持较高水平,反映出节日促销活动的持续影响。

  (2)交易量与单笔交易价值分析:11月的高销售量主要得益于交易次数的增加,表明消费者在该月更加活跃;而12月虽然销售量稍减,但可能由于节日期间高端礼品或大宗商品的购买增加,导致单笔交易金额显著上升,体现了市场消费结构的变化。

  (3)季节性产品策略:进一步确认了产品销量受季节性因素影响显著,即便是年度畅销产品如“PAPER CHAIN KIT 50’s CHRISTMAS”也表现出强烈的季节特征。这要求商家需提前规划库存,优化供应链管理,以应对季节性需求波动。

  (4)小众产品高销量现象:注意到像“PAPER CRAFT, LITTLE BIRDIE”这样的产品,尽管交易次数极少,但单笔交易的高销量使其位居前列。这提示商家应重视小众市场,可能通过限量发售、定制服务等方式提升产品附加值,吸引特定消费群体。

  (5)商品组合与购买习惯:通过关联规则分析,发现消费者倾向于购买同一产品的不同颜色版本,如杯子和碟子的组合购买,这反映了消费者对产品多样性和协调性的需求。商家可据此优化产品组合策略,推出更多配套产品或套餐优惠,提升购买转化率。

  下一步工作:
  (1)增强市场细分与个性化营销:基于用户购买历史、偏好及行为数据,进行更细致的市场细分,实施个性化营销策略。例如,针对高频购买者提供会员特权或积分奖励,对偏好特定类型产品的消费者推送定制化促销信息。

  (2)优化库存管理与预测模型:利用历史销售数据结合季节性因素、市场趋势预测模型,提高库存管理的精确度和灵活性,减少库存积压和缺货风险,同时优化资金占用。

  (3)跨品类关联营销:除了同一产品的不同颜色或款式外,探索跨品类的商品组合推荐,如基于用户购买习惯分析,推荐与已购商品相辅相成的其他产品,增加购物篮平均价值。

参考资料:
1.从0开始学习pyspark–Spark DataFrame数据的选取与访问[第5节]:https://blog.csdn.net/weixin_43817712/article/details/140127703
2.大数据–关联规则挖掘案例:https://blog.csdn.net/qq_51641196/article/details/128478588
3.Pyspark+关联规则 Kaggle购物篮分析案例:https://blog.csdn.net/thorn_r/article/details/138351087
4.Spark 关联规则挖掘:https://blog.csdn.net/weixin_39709476/article/details/109223271
5.Pandas vs Spark:获取指定列的N种方式:https://blog.csdn.net/weixin_43841688/article/details/115222979
6.Spark官网:https://spark.apache.org/docs/latest/api/python/reference/index.html

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
这个项目的大致流程如下: 1. 使用 Python 的 requests 和 Beautiful Soup 库爬取猫眼电影网站的电影信息,包括电影名称、评分、评论人数、上映时间等等。可以使用多线程或异步加速爬取过程。 2. 将爬取到的电影信息存储到 MySQL 数据库中,可以使用 Python 的 pymysql 库进行数据库的连接和操作。 3. 使用 Flask 框架编写 Web 应用程序,提供数据查询和展示功能。可以使用 Flask 的模板引擎 Jinja2 来渲染页面。 4. 使用 PyChart 库对电影数据进行可视化分析,生成各种图表,如柱状图、折线图、饼图等等,以展示电影数据的特征和规律。 具体实现步骤如下: 1. 爬取猫眼电影网站的电影信息 使用 requests 和 Beautiful Soup 库爬取猫眼电影网站的电影信息。可以先通过浏览器查看网页的源代码,找到电影信息的 HTML 标签和属性,再通过 Beautiful Soup 解析并提取出需要的信息。例如,可以使用以下代码爬取电影名称和评分: ```python import requests from bs4 import BeautifulSoup url = 'https://maoyan.com/films' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' } response = requests.get(url, headers=headers) soup = BeautifulSoup(response.text, 'html.parser') movies = soup.find_all('div', class_='movie-item-hover') for movie in movies: name = movie.find('span', class_='name').text score = movie.find('span', class_='score').text print(name, score) ``` 2. 将电影信息存储到 MySQL 数据库中 使用 pymysql 库连接 MySQL 数据库,并将爬取到的电影信息存储到数据库中。可以先创建一个 movies 表来存储电影信息,包括电影名称、评分、评论人数、上映时间等字段。例如,可以使用以下代码将电影信息存储到数据库中: ```python import pymysql # 连接数据库 conn = pymysql.connect( host='localhost', port=3306, user='root', password='123456', database='test', charset='utf8mb4' ) # 创建 movies 表 cursor = conn.cursor() create_table_sql = ''' CREATE TABLE IF NOT EXISTS movies ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, score FLOAT NOT NULL, comments INT NOT NULL, release_date DATE NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ''' cursor.execute(create_table_sql) # 将电影信息插入数据库 for movie in movies: name = movie.find('span', class_='name').text score = float(movie.find('span', class_='score').text) comments = int(movie.find('div', class_='movie-item-number').find_all('span')[2].text[:-3]) release_date = movie.find_all('div', class_='movie-hover-title')[1].text.split(':')[1] insert_sql = f''' INSERT INTO movies (name, score, comments, release_date) VALUES ('{name}', {score}, {comments}, '{release_date}') ''' cursor.execute(insert_sql) conn.commit() # 关闭连接 cursor.close() conn.close() ``` 3. 使用 Flask 编写 Web 应用程序 使用 Flask 框架编写 Web 应用程序,提供数据查询和展示功能。可以使用 Flask 的模板引擎 Jinja2 来渲染页面。例如,可以先创建一个 index.html 模板文件来展示电影数据: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>猫眼电影数据分析</title> </head> <body> <h1>猫眼电影数据分析</h1> <table> <tr> <th>电影名称</th> <th>评分</th> <th>评论人数</th> <th>上映时间</th> </tr> {% for movie in movies %} <tr> <td>{{ movie.name }}</td> <td>{{ movie.score }}</td> <td>{{ movie.comments }}</td> <td>{{ movie.release_date }}</td> </tr> {% endfor %} </table> </body> </html> ``` 然后,在 Flask 应用程序中定义一个路由,从数据库中获取电影数据,并渲染模板文件: ```python from flask import Flask, render_template import pymysql # 连接数据库 conn = pymysql.connect( host='localhost', port=3306, user='root', password='123456', database='test', charset='utf8mb4' ) # 创建 Flask 应用程序 app = Flask(__name__) # 定义路由,获取电影数据并渲染模板文件 @app.route('/') def index(): cursor = conn.cursor(pymysql.cursors.DictCursor) select_sql = ''' SELECT * FROM movies ''' cursor.execute(select_sql) movies = cursor.fetchall() cursor.close() return render_template('index.html', movies=movies) # 启动应用程序 if __name__ == '__main__': app.run(debug=True) ``` 4. 使用 PyChart 库进行数据可视化分析 使用 PyChart 库对电影数据进行可视化分析,生成各种图表,如柱状图、折线图、饼图等等,以展示电影数据的特征和规律。例如,可以使用以下代码生成一个柱状图,展示电影评分的分布情况: ```python from pychart import * import pymysql # 连接数据库 conn = pymysql.connect( host='localhost', port=3306, user='root', password='123456', database='test', charset='utf8mb4' ) # 查询电影数据 cursor = conn.cursor() select_sql = ''' SELECT score, COUNT(*) AS count FROM movies GROUP BY score ''' cursor.execute(select_sql) data = cursor.fetchall() cursor.close() # 生成柱状图 chart = VerticalBarChart() chart.addDataset([row[1] for row in data]) chart.setLegend([f'{row[0]:.1f} 分' for row in data]) chart.setXAxis({'title': '评分'}) chart.setYAxis({'title': '电影数量'}) chart.setTitle('电影评分分布图') chart.setColors(['blue', 'red', 'green', 'yellow', 'purple']) chart.download('score.png') ``` 以上是一个简单的 Python 爬取猫眼电影+MySQL+Flask+PyChart 数据分析的项目流程,具体实现还需要根据实际需求进行调整和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侧耳倾听童话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值