# %%--- [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
## 🕐请从本地上传一个用电商记插件采集的淘宝搜索2025新版结果(含综合排序和销量排序)的Excel文件。[下载示例](https://www.dianshangji.cn/u/user5344/cfiles/browse/index?fid=3)
# %%--- [html] cell-a5b79fdf3c10
# properties:
#   run_on_load: true
#   top_hidden: true
# ---%%
<input class="btn btn-primary" type="file" id="fileInput"/> 
# %%--- [javascript] cell-d28877a258f5
# properties:
#   run_on_load: true
#   top_hidden: true
# ---%%

const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async function(ev){
  const file = fileInput.files[0];
  if (!file) {
      alert("Please select a file first.");
      return;
  }
  
  // 读取文件为 ArrayBuffer
  const arrayBuffer = await file.arrayBuffer();
  
  // 将 ArrayBuffer 转换为 Uint8Array
  const uint8Array = new Uint8Array(arrayBuffer);
  
  pyodide.FS.writeFile('/file1.xlsx', uint8Array);
});
# %% [markdown] cell-d4de1bcd39b2
## 🕑数据清洗与预处理
在进行数据分析之前,首先需要对采集到的数据进行清洗和预处理。数据清洗的过程包括去除缺失值、异常值处理、格式标准化等步骤。由于淘宝平台的数据可能存在一定的噪声(如商品标题的重复、价格的极端值等),我们需要对数据进行过滤,确保后续分析的准确性。由于在电商平台上,部分商品的销量和付款人数较少,可能无法代表整体市场趋势,因此我们在数据清洗阶段对数据进行了筛选,去除了销量数值超过**10000**的商品。
# %% [python] cell-50bbd749f8a1
import re

print("开始读取内存中的Excel文件……")
# 读取Excel文件,跳过前两行(即从第三行开始读取),只保留“商品ID”、“价格”和“月销量”两个字段
excel_file_path = '/file1.xlsx'
# 分别读取名为“综合”和“销量”的工作表
df_zonghe = pd.read_excel(excel_file_path, skiprows=2, usecols=['商品ID','价格', '月销量'], sheet_name='综合')
df_xiaoliang = pd.read_excel(excel_file_path, skiprows=2, usecols=['商品ID','价格', '月销量'], sheet_name='销量')

# 打印前几行数据以确认读取正确
print("综合表前几行:")
print(df_zonghe.head())

print("销量表前几行:")
print(df_xiaoliang.head())

def processdata(df):  
  # 代码片段1
  # 定义一个函数来处理“月销量”字段
  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
      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
  
  # 应用函数到“月销量”列
  df['月销量'] = df['月销量'].apply(clean_month_sales)
  
  # 代码片段2
  # 筛选出“价格”小于等于1000元的记录
  df = df[df['价格'] <= 1000]
  print("筛选出“价格”小于等于1000元的记录")
  
  # 筛选出“月销量”大于等于0,小于等于100000的记录
  df = df[df['月销量'] >= 0]
  df = df[df['月销量'] <= 10000]  
  print("筛选出“月销量”小于等于10000的记录")

  # 代码片段3
  # 假设 df 是已经加载的数据
  # 检查商品ID是否有重复记录
  if df['商品ID'].duplicated().any():
      print("存在重复的商品ID记录,正在去除重复条目...")
      # 去除重复的商品ID条目,只保留第一次出现的记录
      df = df.drop_duplicates(subset=['商品ID'], keep='first')
  else:
      print("没有重复的商品ID记录。")
  # 打印前5条记录以检查结果
  print(df.head(5))
  
  print("有效记录总数:", len(df))
  return df

df_zonghe = processdata(df_zonghe)
df_xiaoliang = processdata(df_xiaoliang)

# %% [markdown] cell-3e50bf8d15a1
### 读取表格数据
这两行代码是使用Pandas库来读取Excel文件,并从文件中提取特定的列和数据。下面是详细的解释:

#### 1. `df_zonghe = pd.read_excel(excel_file_path, skiprows=2, usecols=['商品ID','价格', '月销量'], sheet_name='综合')`

- **`pd.read_excel()`**:这是Pandas的一个函数,用来读取Excel文件中的数据。`excel_file_path`是Excel文件的路径。
  
- **`skiprows=2`**:这个参数指定在读取数据时跳过前两行。这通常用于跳过Excel文件中的表头信息,或者有时数据从第三行开始的情况。

- **`usecols=['商品ID','价格', '月销量']`**:这个参数指定了要读取的列名。只有这三列的数据会被读取,其他列会被忽略。这里我们读取的是:
  - **商品ID**:商品的唯一标识符。
  - **价格**:商品的价格。
  - **月销量**:商品在一个月内的销量。

- **`sheet_name='综合'`**:这个参数指定了要读取的Excel表单的名称。在这个例子中,读取的是名为“综合”的表单。

- **返回的结果**:这一行代码会读取Excel文件中的“综合”工作表,并将包含**商品ID**、**价格**、**月销量**这三列数据的内容存储在`df_zonghe`这个DataFrame中。

#### 2. `df_xiaoliang = pd.read_excel(excel_file_path, skiprows=2, usecols=['商品ID','价格', '月销量'], sheet_name='销量')`

这一行代码的解释与上面的代码类似,只是读取的是名为“销量”的表单。

- **`sheet_name='销量'`**:这次指定了要读取的工作表名称是“销量”,所以数据将来自Excel文件中名为“销量”的表单。

- **返回的结果**:这一行代码会读取“销量”工作表的数据,同样是**商品ID**、**价格**、**月销量**这三列数据,并将其存储在`df_xiaoliang`这个DataFrame中。

#### 总结
这两行代码的作用是:
- 第一行从Excel文件中读取“综合”表单中的**商品ID**、**价格**、**月销量**三列数据,并将其存储在`df_zonghe`中。
- 第二行从Excel文件中读取“销量”表单中的**商品ID**、**价格**、**月销量**三列数据,并将其存储在`df_xiaoliang`中。

`skiprows=2`确保跳过前两行,`usecols`只选择了我们关心的三列数据,这样可以更有效地处理和分析数据。

### 代码片段1

这段代码定义了一个函数 `clean_month_sales`,用于处理 `pandas` DataFrame 中的“月销量”字段。然后,使用 `apply` 方法将这个函数应用到 DataFrame 的 `月销量` 列,以对每个元素进行处理。下面是代码的详细解析:

### 1. **函数** `clean_month_sales` 定义:

```
def clean_month_sales(sales_str):
    if pd.isna(sales_str):
        return None
```

* `sales_str` 是函数的输入参数,表示“月销量”字段的单个值。
* `pd.isna(sales_str)` 用于检查该值是否为缺失值(`NaN`)。如果是 `NaN`,函数返回 `None`,表示该值不能被处理。

### 2. **去除字符串前后空白:**

```
sales_str = str(sales_str).strip()
```

* `str(sales_str)`:将 `sales_str` 转换为字符串类型,以防原始值不是字符串类型(如数字、日期等)。
* `strip()`:去除字符串的前后空白字符,以便后续匹配和处理。

### 3. **处理包含“本月行业热销”字符串的情况:**

```
if "本月行业热销" in sales_str:
    return 1000
```

* 如果 `sales_str` 中包含 “本月行业热销”,函数直接返回 1000,表示销量为 1000。

### 4. **处理包含“万+”字符串的情况:**

```
elif "万+" in sales_str:
    match = re.search(r'(\d+)', sales_str)
    if match:
        return int(match.group(1)) * 10000
```

* 如果 `sales_str` 中包含 “万+”,表示销量以“万”为单位。
* 使用 `re.search(r'(\d+)', sales_str)` 通过正则表达式匹配字符串中的数字部分。`(\d+)` 匹配一个或多个数字。
* `match.group(1)` 返回匹配到的数字部分(作为字符串),并将其转换为整数。然后,将这个整数乘以 10000,表示实际销量的值(例如,如果字符串是 “2万+”,则返回 `20000`)。

### 5. **处理包含“+人”字符串的情况:**

```
elif "+人" in sales_str:
    match = re.search(r'(\d+)', sales_str)
    if match:
        return int(match.group(1))
```

* 如果 `sales_str` 中包含 “+人”,表示销量为一些人购买了该商品。
* 同样,使用正则表达式 `re.search(r'(\d+)', sales_str)` 来匹配数字部分,并将其转换为整数。
* 返回匹配到的数字,表示实际销量。

### 6. **尝试将字符串转换为整数:**

```
try:
    return int(sales_str)
except ValueError:
    return None
```

* 如果以上三种情况都没有匹配到,最后尝试将 `sales_str` 直接转换为整数。
* 如果转换成功,则返回该整数值,表示销量的数值。
* 如果 `sales_str` 无法转换为整数(例如包含非数字字符),则捕获 `ValueError` 异常并返回 `None`,表示该值无法处理。

### 7. **应用函数到 DataFrame 的“月销量”列:**

```
df['月销量'] = df['月销量'].apply(clean_month_sales)
```

* `df['月销量']` 是 `pandas` DataFrame 中的“月销量”列。
* 使用 `apply(clean_month_sales)` 将 `clean_month_sales` 函数应用到 `月销量` 列中的每个元素。
  * 这会逐个处理 `月销量` 列的所有值,基于前面定义的规则来清洗和转换这些值。

### 总结:

这段代码的作用是根据不同的规则来处理“月销量”字段的数据,规范化这些数据,使它们统一为整数形式。具体处理方式包括:

* 对于“本月行业热销”直接设定为 1000;
* 对于含有“万+”的,转换为以万为单位的实际数值;
* 对于含有“+人收货”的,提取数字并作为销量;
* 对于其他情况,直接将字符串转换为整数;
* 如果无法转换为整数,则返回 `None`。

最终,这个函数被应用到 `df['月销量']` 列中,清洗并处理了该列的所有数据。

### 代码片段2

1. `df['价格'] <= 1000` 会生成一个布尔值 Series,表示 `价格` 列中每个元素是否小于等于 1000。
2. `df = df[df['月销量'] >= 0]`会筛选出销量大于等于0的所有元素。
3. 使用 `df[...]` 语法,将这个布尔 Series 作为索引过滤出符合条件的记录。

### 代码片段3

1. `df['商品ID'].duplicated().any()`:检查 `商品ID` 列是否有重复记录,`duplicated()` 返回一个布尔 Series,表示每一行是否为重复项,`any()` 检查是否有 `True`(即存在重复记录)。
2. `df.drop_duplicates(subset=['商品ID'], keep='first')`:去除 `商品ID` 列中的重复项,`keep='first'` 表示保留第一次出现的记录,删除后续重复的记录。

这样,代码会检查是否有重复的 `商品ID`,如果有,就去除重复记录并保留每个 `商品ID` 的第一条记录。
# %% [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_zonghe)
# 绘制销量排序搜索数据的价格区间直方图
drawbarchart_price('销量排序', df_xiaoliang)
# %%--- [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_zonghe)
# 绘制销量排序搜索数据的价格区间直方图
drawbarchart_sales('销量排序', df_xiaoliang)