# %%--- [python] cell-f74e05782eef
# properties:
#   run_on_load: true
#   top_hidden: true
# ---%%
print("开始初始化数据分析和可视化的运行环境,请稍候……")
import micropip
await micropip.install("/pypi/openpyxl-3.1.5-py2.py3-none-any.whl")
await micropip.install("/pypi/pyfonts-0.0.2-py3-none-any.whl")
import pandas as pd
import pyodide
from pyfonts import load_font

# 文件的URL
file_url = '/assets/fonts/NotoSansSC-Regular.ttf'  # 请将此URL替换为实际文件的URL
# 本地保存路径
local_file_path = '/NotoSansSC-Regular.ttf'
# 下载文件并保存到本地
try:
	response = await pyodide.http.pyfetch(file_url)
	# Return the response body as a bytes object
	image_data = await response.bytes()
	with open(local_file_path, 'wb') as f:
		f.write(image_data)
	print(f"中文字体已成功下载并保存为: {local_file_path}")
except Exception as e:
	print(f"下载文件时出错: {e}")

font = load_font(font_path="/NotoSansSC-Regular.ttf")
print("数据分析和可视化的运行环境已经就绪。可以进行第1步了。")
# %% [markdown] cell-5e351e7966a3
## 🕐请从本地上传三个用电商记插件采集的新版淘宝、拼多多、京东搜索结果(含综合排序和销量排序)的Excel文件。[下载示例](https://www.dianshangji.cn/u/user5344/cfiles/browse/index?fid=3)
# %%--- [html] cell-a5b79fdf3c10
# properties:
#   run_on_load: true
# ---%%
请上传三个Excel文件:<input class="btn btn-primary" type="file" id="fileInput" multiple />
# %%--- [javascript] cell-d28877a258f5
# properties:
#   run_on_load: true
# ---%%
const fileInput = document.getElementById('fileInput');

  const files = fileInput.files;
  if (files.length !== 3) {
      alert("Please select exactly three files: one for 淘宝, one for 拼多多, and one for 京东.");
      return;
  }

  // 先创建一个文件名到对应保存文件名的映射
  const fileMapping = {
      '淘宝': '/taobao.xlsx',
      '拼多多': '/pdd.xlsx',
      '京东': '/jd.xlsx'
  };

  // 遍历用户上传的所有文件
  for (let file of files) {
    const fileName = file.name.toLowerCase();

    // 判断文件名包含的关键词,并根据关键词确定保存文件的名称
    let pyodideFileName = null;
    for (let key in fileMapping) {
      if (fileName.includes(key.toLowerCase())) {
        pyodideFileName = fileMapping[key];
        break;
      }
    }

    // 如果文件名不符合要求
    if (!pyodideFileName) {
      alert("Each file must be named with one of these keywords: '淘宝', '拼多多', or '京东'.");
      return;
    }

    // 读取文件为 ArrayBuffer
    const arrayBuffer = await file.arrayBuffer();
    
    // 将 ArrayBuffer 转换为 Uint8Array
    const uint8Array = new Uint8Array(arrayBuffer);
    
    // 写入文件到 Pyodide 文件系统
    console.log(pyodideFileName, uint8Array.length + '字节');
    pyodide.FS.writeFile(pyodideFileName, uint8Array);
  }
# %% [markdown] cell-d4de1bcd39b2
## 🕑数据清洗与预处理
在进行数据分析之前,首先需要对采集到的数据进行清洗和预处理。
# %% [python] cell-50bbd749f8a1
import pandas as pd
import re

print("开始读取内存中的Excel文件……")

# 读取淘宝、拼多多和京东的数据
df_taobao = pd.read_excel('/taobao.xlsx', skiprows=2, usecols=['商品ID', '价格', '月销量'], sheet_name='综合')
df_pdd = pd.read_excel('/pdd.xlsx', skiprows=2, usecols=['商品ID', '价格', '月销量'], sheet_name='综合')
df_jd = pd.read_excel('/jd.xlsx', skiprows=2, usecols=['商品ID', '价格', '评价'], sheet_name='综合')

# 打印前几行数据以确认读取正确
print("淘宝表前几行:")
print(df_taobao.head())

print("拼多多表前几行:")
print(df_pdd.head())

print("京东表前几行:")
print(df_jd.head())

# 处理月销量字段的函数
def clean_month_sales(sales_str):
    if pd.isna(sales_str):
        return None
    
    sales_str = str(sales_str).strip()
    
    # 根据不同的格式估算月销量
    if "本月行业热销" in sales_str:
        return 1000
    elif "万+" in sales_str:
        match = re.search(r'(\d+)', sales_str)
        if match:
            return int(match.group(1)) * 10000  # 假设每万+表示的销量为数字乘以10000
    elif "+人" in sales_str:
        match = re.search(r'(\d+)', sales_str)
        if match:
            return int(match.group(1))  # 假设+人后跟的是具体的销量数字
    
    try:
        return int(sales_str)  # 尝试将字符串转为整数
    except ValueError:
        return None

# 京东“评价”字段估算为“月销量”
def estimate_jd_sales(reviews):
    if pd.isna(reviews):
        return None
    
    reviews = str(reviews).strip()
    
    # 处理 "100+" 形式
    if "万+" in reviews:
        # 提取“万+”格式的数字(如“5万+”)
        match = re.search(r'(\d+)', reviews)
        if match:
            return int(match.group(1)) * 1000  # 假设每万条评价对应1000月销量
    
    elif "+" in reviews:
        # 提取“+”形式的数字(如“100+”)
        match = re.search(r'(\d+)', reviews)
        if match:
            # 如果是 "100+",假设销量为100到200之间
            return int(match.group(1)) * 1.5  # 假设是100+表示的实际销量在100到200之间(系数1.5)

    else:
        # 处理没有"+"的数字(如"96")
        try:
            return int(reviews) * 1  # 假设每1个评价对应1件月销量
        except ValueError:
            return None
    
    return None

# 拼多多销量估算的缩放因子
def scale_pdd_sales(df, scale_factor=0.1): 
    """根据淘宝销量对拼多多销量进行缩放"""
    df['月销量'] = df['月销量'] * scale_factor
    return df

# 处理数据的通用函数
def processdata(df, is_jd=False, is_pdd=False):  
    # 如果是拼多多数据,使用缩放因子调整销量
    if is_pdd:
        df = scale_pdd_sales(df)
    #print(df.head(5))
    # 如果是京东数据,使用估算月销量的函数
    if is_jd:
        df['月销量'] = df['评价'].apply(estimate_jd_sales)
    elif is_pdd:
        pass        
    else:
        # 处理“月销量”字段
        df['月销量'] = df['月销量'].apply(clean_month_sales)
    
    # 筛选出“价格”小于等于1000元的记录
    df = df[df['价格'] <= 1000]
    print("筛选出“价格”小于等于1000元的记录")
    #print(df.head(5))
  
    # 筛选出“月销量”大于等于0,小于等于10000的记录
    df = df[df['月销量'] >= 0]
    df = df[df['月销量'] <= 10000]  
    print("筛选出“月销量”小于等于10000的记录")
    #print(df.head(5))  

    # 检查商品ID是否有重复记录
    if df['商品ID'].duplicated().any():
        print("存在重复的商品ID记录,正在去除重复条目...")
        df = df.drop_duplicates(subset=['商品ID'], keep='first')
    else:
        print("没有重复的商品ID记录。")
    
    # 打印前5条记录以检查结果
    print(df.head(5))
    
    print("有效记录总数:", len(df))
    return df

# 处理淘宝、拼多多和京东的数据
df_taobao = processdata(df_taobao, is_jd=False, is_pdd=False)
df_pdd = processdata(df_pdd, is_jd=False, is_pdd=True)
df_jd = processdata(df_jd, is_jd=True, is_pdd=False)

print("数据已处理。")
# %% [markdown] cell-ff91033e7acc
## 🕒价格区间直方图
# %% [python] cell-70f67d1cde93
import matplotlib.pyplot as plt
await micropip.install("seaborn")
import seaborn as sns

def drawbarchart_price(name, df):
  # 假设 df 已经是一个包含商品ID、价格和月销量字段的 DataFrame
  # 选择“价格”字段来绘制价格分布图
  
  plt.figure(figsize=(10, 6))
  
  # 使用 seaborn 绘制价格的分布图
  sns.histplot(df['价格'], kde=True, bins=30, color='blue')
  
  # 添加标题和标签
  plt.title(name + '商品价格分布图', fontsize=16, font=font)
  plt.xlabel('价格', fontsize=12, font=font)
  plt.ylabel('频次', fontsize=12, font=font)
  
  # 显示图形
  plt.show()

# 绘制综合排序搜索数据的价格区间直方图
drawbarchart_price('淘宝综合价格分布', df_taobao)
drawbarchart_price('拼多多综合价格分布', df_pdd)
drawbarchart_price('京东综合价格分布', df_jd)

# %%--- [markdown] cell-983a4edf8ee8
# properties:
#   top_hidden: true
# ---%%
### 1. 导入必要的库

```
import matplotlib.pyplot as plt
await micropip.install("seaborn")
import seaborn as sns
```

* `matplotlib.pyplot`: 用于绘制基本的图形,如线图、散点图、直方图等。它提供了很多自定义选项来控制图形的外观。
* `seaborn`: 基于`matplotlib`的一个高级可视化库,提供了更漂亮和更方便的绘图功能。`seaborn`与`matplotlib`兼容,并且能够创建复杂的统计图表。下面附录中详细讲解如何在交互式文档中安装seaborn库。

### 2. 设置图表的大小

```
plt.figure(figsize=(10, 6))
```

* `plt.figure(figsize=(10, 6))`:指定图表的尺寸。`figsize`的参数是一个元组,表示图形的宽度和高度(单位是英寸)。例如,`figsize=(10, 6)`意味着图形的宽度是10英寸,高度是6英寸。

### 3. 绘制价格分布图

```
sns.histplot(df['价格'], kde=True, bins=30, color='blue')
```

这个是`seaborn`库中的核心绘图函数之一——`histplot()`。它专门用于绘制直方图,提供了比`matplotlib`更高级的功能和更简洁的语法。

#### 解释每个参数:

* `df['价格']`:
  * 这是我们数据框`df`中的`价格`字段。`seaborn.histplot()`需要一个数值型数据(例如,价格数据)来绘制直方图。
* `kde=True`:
  * `kde`表示“Kernel Density Estimate”(核密度估计),它是通过平滑曲线显示数据的分布。
  * 核密度估计(KDE)是一种通过平滑数据点来估算连续变量分布的技术。它生成的是一个平滑的曲线,用来展示数据的整体分布趋势,而不是像直方图那样显示离散的条形。
  * `kde=True`启用核密度估计,默认情况下它绘制的是与直方图重叠的平滑曲线。
  * 如果你只想显示直方图,而不想显示KDE平滑曲线,可以设置`kde=False`。
* `bins=30`:
  * `bins`参数控制直方图中分箱(或区间)的数量。每个箱子对应一个区间,数据根据价格的值被分配到这些区间中。
  * `bins=30`表示将价格范围分成30个区间。你可以调整这个参数来使直方图的分辨率更高或更低。
  * 通常,`bins`的值会根据数据的大小、分布等进行调整。较小的`bins`可能会导致数据过于粗略,而较大的`bins`可能会过于细致。
* `color='blue'`:
  * `color`指定直方图的颜色。这里选择的是`blue`(蓝色)。你可以根据需要选择其他颜色,例如 `'red'`, `'green'` 或者使用颜色代码(如`#FF5733`)。

### 4. 设置图表的标题和标签

```
plt.title('商品价格分布图', fontsize=16)
plt.xlabel('价格', fontsize=12)
plt.ylabel('频次', fontsize=12)
```

* `plt.title('商品价格分布图', fontsize=16)`: 设置图表的标题,`fontsize=16`设置字体的大小为16。
* `plt.xlabel('价格', fontsize=12)`: 设置X轴的标签为“价格”,字体大小为12。
* `plt.ylabel('频次', fontsize=12)`: 设置Y轴的标签为“频次”,字体大小为12。

这些标签和标题使得图表更易于理解和呈现,尤其是当你展示给其他人时,它们有助于说明图表的含义。

### 5. 显示图形

```
plt.show()
```

* `plt.show()`:这个命令用于显示当前图形。它是`matplotlib`的标准方法,启动后会弹出图形窗口或将图形嵌入到Jupyter Notebook等环境中。

### 总结:

这段代码的作用是生成一个显示商品价格分布的直方图,同时叠加一个平滑的KDE曲线(用于展示价格的分布趋势),并且设置了图表的标题、轴标签和图形大小。通过调整`bins`、`kde`等参数,你可以控制图表的细节和外观。

### `seaborn.histplot()`常用参数:

* `data`: 输入的数据,可以是一个DataFrame或Series。
* `kde`: 布尔值,是否启用KDE平滑曲线。默认`False`。
* `bins`: 控制直方图箱数目,数据的“分箱”数。也可以传递一个数组来自定义每个箱的边界。
* `color`: 设置条形的颜色。
* `hue`: 用于按类别进行颜色分组的字段(如果数据包含多个类别的话)。
* `stat`: 直方图的统计类型,默认是`'count'`,可以改为`'density'`表示频率密度。

如果你对`seaborn`或`matplotlib`有更多的疑问,欢迎继续提问!
# %% [markdown] cell-10c8e96a2446
## 🕓销量分布直方图
# %% [python] cell-f4ff7a08a942
def drawbarchart_sales(name, df):
  # 假设 df 已经是一个包含商品ID、价格和月销量字段的 DataFrame
  # 选择“价格”字段来绘制价格分布图
  
  plt.figure(figsize=(10, 6))
  
  # 使用 seaborn 绘制月销量的分布图
  sns.histplot(df['月销量'], kde=True, bins=30, color='blue')
  
  # 添加标题和标签
  plt.title(name + '商品销量分布图', fontsize=16, font=font)
  plt.xlabel('月销量', fontsize=12, font=font)
  plt.ylabel('频次', fontsize=12, font=font)
  
  # 显示图形
  plt.show()

# 绘制综合排序搜索数据的价格区间直方图
drawbarchart_sales('淘宝综合销量分布', df_taobao)
drawbarchart_sales('拼多多综合销量分布', df_pdd)
drawbarchart_sales('京东综合销量分布', df_jd)