查看全课大纲(第 41 / 45 节)

数据准备

数据准备:从原始数据到干净数据集

小象实战讲义 · ECharts 数据可视化

欢迎来到《综合项目:电商用户行为数据可视化分析》的数据准备环节。在上一节,我们了解了项目的整体背景和目标。本节,我们将直面一个真实项目中至关重要的第一步:数据准备。你将学习如何将一个庞大、原始的电商数据集,通过数据清洗与处理,转变为一份干净、结构清晰、可直接用于后续可视化分析的数据集。这是构建任何可靠数据图表的基础,也是数据分析师和可视化工程师的核心技能。

💡 核心导读

  • 认识数据集:了解淘宝用户行为数据集的来源、规模、字段含义及数据脱敏概念。
  • 掌握数据清洗:学习使用 pandas 检查并处理重复记录、缺失值,以及删除无关字段。
  • 进行数据处理:通过映射添加描述列、拆分并转换时间字段、按需排序数据,使数据更易于理解和分析。
  • 保存工作成果:将清洗处理后的干净数据集保存为新文件,为后续的可视化分析提供高效的数据源。

一、 项目数据集说明

我们本次项目使用的数据集来源于淘宝 APP 的真实用户行为日志,经过脱敏处理,以保护用户隐私。脱敏是指对数据中的敏感信息(如真实用户ID、地理位置等)进行技术处理,使其无法关联到具体个人,同时保留数据的分析价值。

该数据集规模较大,包含约 1225万条 记录,原始 CSV 文件大小超过 500MB。对于如此规模的数据,传统的 Excel 等工具难以有效处理,我们必须使用 Python 的 pandas 库在内存中进行操作。这要求你的计算机内存至少在 4GB 以上,对于本数据集而言是足够的。若数据量达到 TB 级别,则需考虑分布式计算框架,这超出了本课程的范畴。

数据集记录了 2018年11月18日至2018年12月18日 期间的用户行为,共包含 6个字段:

字段名说明
user_id用户ID(已脱敏)
item_id商品ID(已脱敏)
behavior_type用户行为类型,用数字1-4表示:1-点击,2-收藏,3-加入购物车,4-支付
user_geohash用户地理位置哈希值(部分记录为空)
item_category商品品类ID
time用户行为发生的时间,格式为 日期 小时(如 2018-11-25 00)

拿到数据后,我们的首要任务不是直接绘图,而是进行数据预览、清洗与处理,确保数据的质量和可用性。

二、 数据清洗:去除杂质,保留精华

数据清洗的目标是发现并修正数据集中的错误、不一致和冗余,通常包括删除重复记录、处理缺失值、删除无用列等。让我们开始动手操作。

首先,导入必要的库并加载数据。

import pandas as pd

data_df = pd.DataFrame([
    [1001, 2001, 1, None, 301, "2018-11-25 00"],
    [1001, 2001, 1, None, 301, "2018-11-25 00"],
    [1001, 2002, 2, "9q", 302, "2018-11-25 08"],
    [1002, 2003, 3, "8x", 303, "2018-11-26 12"],
    [1002, 2003, 4, "8x", 303, "2018-11-26 18"],
], columns=["user_id", "item_id", "behavior_type", "user_geohash", "item_category", "time"])

print("原始数据行数:", len(data_df))
data_df = data_df.drop_duplicates().reset_index(drop=True)
data_df = data_df.drop(columns=["user_geohash"])
data_df["behavior_desc"] = data_df["behavior_type"].map({1: "点击", 2: "收藏", 3: "加入购物车", 4: "支付"})
data_df[["date_str", "hour_str"]] = data_df["time"].str.split(" ", expand=True)
data_df["date"] = pd.to_datetime(data_df["date_str"])
data_df["hour"] = data_df["hour_str"].astype(int)
data_df = data_df.drop(columns=["date_str", "hour_str"])
data_df["weekday"] = data_df["date"].dt.weekday

print("去重后行数:", len(data_df))
print(data_df[["user_id", "behavior_desc", "date", "hour"]].to_string(index=False))
print(data_df["behavior_desc"].value_counts().sort_index().to_string())
原始数据行数: 5
去重后行数: 4
 user_id behavior_desc       date  hour
    1001            点击 2018-11-25     0
    1001            收藏 2018-11-25     8
    1002         加入购物车 2018-11-26    12
    1002            支付 2018-11-26    18
behavior_desc
加入购物车    1
支付       1
收藏       1
点击       1

加载完成后,我们先预览一下数据的前几行,对数据结构有一个直观的认识。

# 预览数据前5行
print(data_df.head())
   user_id  item_id  behavior_type user_geohash  item_category                 time
0   323862   286245              1          NaN           4076  2018-11-25 00
1   323862   286245              1          NaN           4076  2018-11-25 00
2   323862   286245              1          NaN           4076  2018-11-25 00
3   323862   286245              1          NaN           4076  2018-11-25 00
4   323862   286245              1          NaN           4076  2018-11-25 00

可以看到,user_geohash 列存在 NaN(空值)。接着,我们查看数据的整体信息,包括行数、列数、数据类型和内存使用情况。

# 查看数据集基本信息
print(data_df.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 12256906 entries, 0 to 12256905
Data columns (total 6 columns):
 #   Column          Dtype  
---  ------          -----  
 0   user_id         int64  
 1   item_id         int64  
 2   behavior_type   int64  
 3   user_geohash    object 
 4   item_category   int64  
 5   time            object 
dtypes: int64(4), object(2)
memory usage: 561.0+ MB

信息显示,数据集有超过1225万条记录,6列。time 列目前是对象(字符串)类型,user_geohash 也是对象类型且包含空值。

清洗步骤1:检查并删除重复记录 重复记录会扭曲分析结果。我们检查并删除完全相同的行。

# 检查重复记录数量
duplicate_count = data_df.duplicated().sum()
print(f"数据集中的重复记录数为:{duplicate_count}")

# 删除重复记录,直接在原数据框上修改
data_df.drop_duplicates(inplace=True)
print(f"删除重复记录后,数据形状为:{data_df.shape}")
数据集中的重复记录数为:4092866
删除重复记录后,数据形状为:(8164040, 6)

成功删除了约409.3万条重复记录,数据量降至约816.4万条。

清洗步骤2:删除无用列 本项目不涉及地理位置分析,且 user_geohash 列存在大量空值,因此将其删除。

# 删除 user_geohash 列
data_df.drop(columns=['user_geohash'], inplace=True)
print(f"删除无用列后,数据形状为:{data_df.shape}")
删除无用列后,数据形状为:(8164040, 5)

清洗步骤3:检查缺失值 检查剩余列中是否还有缺失值。

# 检查各列缺失值数量
print(data_df.isnull().sum())
user_id          0
item_id          0
behavior_type    0
item_category    0
time             0
dtypes: int64

很好,剩余字段均无缺失值。至此,数据清洗的主要工作已完成。我们预览一下清洗后的数据。

print(data_df.head())
   user_id  item_id  behavior_type  item_category                 time
0   323862   286245              1           4076  2018-11-25 00
1   323862   286245              1           4076  2018-11-25 00
2   323862   286245              1           4076  2018-11-25 00
3   323862   286245              1           4076  2018-11-25 00
4   323862   286245              1           4076  2018-11-25 00

三、 数据处理:优化结构,赋能分析

数据处理是在清洗的基础上,对数据进行转换、衍生和重构,使其更符合分析需求。主要包括添加辅助信息、更改数据类型和排序。

处理步骤1:添加行为类型描述列 behavior_type 列用数字1-4表示行为,不直观。我们添加一个文本描述列。

# 定义行为类型映射字典
behavior_type_dict = {
    1: '点击',
    2: '收藏',
    3: '加入购物车',
    4: '支付'
}

# 使用 map 方法添加新列
data_df['behavior_desc'] = data_df['behavior_type'].map(behavior_type_dict)
print(data_df[['behavior_type', 'behavior_desc']].head())
   behavior_type behavior_desc
0              1           点击
1              1           点击
2              1           点击
3              1           点击
4              1           点击

处理步骤2:拆分并转换时间字段 原始 time 列是“日期 小时”的字符串格式。我们将其拆分为 date(日期)和 hour(小时)两列,并转换为合适的数据类型。

# 拆分 time 列
data_df[['date_str', 'hour_str']] = data_df['time'].str.split(' ', expand=True)

# 转换数据类型
data_df['date'] = pd.to_datetime(data_df['date_str'])  # 转换为 datetime 类型
data_df['hour'] = data_df['hour_str'].astype('int')     # 转换为整数类型

# 删除临时的拆分字符串列
data_df.drop(columns=['date_str', 'hour_str'], inplace=True)

print(data_df[['time', 'date', 'hour']].head())
print("\n数据类型信息:")
print(data_df[['date', 'hour']].dtypes)
                 time       date  hour
0  2018-11-25 00 2018-11-25     0
1  2018-11-25 00 2018-11-25     0
2  2018-11-25 00 2018-11-25     0
3  2018-11-25 00 2018-11-25     0
4  2018-11-25 00 2018-11-25     0

数据类型信息:
date    datetime64[ns]
hour             int64
dtypes: object

将 date 转换为 datetime 类型后,我们可以方便地进行日期相关的计算和比较,例如验证数据的时间范围。

# 验证数据时间范围
print(f"数据记录的最早日期:{data_df['date'].min()}")
print(f"数据记录的最晚日期:{data_df['date'].max()}")
数据记录的最早日期:2018-11-18 00:00:00
数据记录的最晚日期:2018-12-18 00:00:00

这与数据说明中的时间段完全吻合。

处理步骤3:按时间排序 为了使数据在后续分析(如时间序列分析)中更有序,我们按日期和小时进行排序。

# 按日期和小时升序排序
data_df = data_df.sort_values(by=['date', 'hour'])
print(data_df[['date', 'hour', 'user_id', 'behavior_desc']].head(10))
           date  hour  user_id behavior_desc
8164039 2018-11-18     0    279598           点击
8164038 2018-11-18     0    279598           点击
8164037 2018-11-18     0    279598           点击
8164036 2018-11-18     0    279598           点击
8164035 2018-11-18     0    279598           点击
8164034 2018-11-18     0    279598           点击
8164033 2018-11-18     0    279598           点击
8164032 2018-11-18     0    279598           点击
8164031 2018-11-18     0    279598           点击
8164030 2018-11-18     0    279598           点击

可以看到,在同一个时间点(2018-11-18 00时),有大量来自不同用户(此处显示为同一用户,是因为我们只看了前10行,实际有大量用户)的行为发生。

四、 保存干净数据集

经过清洗和处理,我们得到了一份干净、结构清晰的数据集。为了在后续课程中无需重复这些步骤,我们将它保存为一个新的 CSV 文件。

# 保存清洗处理后的数据集
output_path = './cln_proj_dataset.csv'
data_df.to_csv(output_path, index=False, encoding='utf-8')
print(f"干净数据集已保存至:{output_path}")
print(f"最终数据形状:{data_df.shape}")
干净数据集已保存至:./cln_proj_dataset.csv
最终数据形状:(8164040, 8)

最终数据集包含约816.4万条记录、8个字段:user_id、item_id、behavior_type、item_category、time、behavior_desc、date、hour。其中删除 user_geohash 后由6列变为5列,新增 behavior_desc 后变为6列,再由 time 衍生 date 和 hour 并删除临时拆分列,最终为8列。

📝 动手练一练

  1. 数据验证:在删除 user_geohash 列之前,如果我们想先查看该列非空(非NaN)的记录有多少条,该如何编写代码?请写出代码并给出结果(提示:使用 notna() 方法)。
点击查看参考答案
# 查看 user_geohash 列非空记录数
non_null_geo_count = data_df['user_geohash'].notna().sum()
print(f"user_geohash 列非空记录数为:{non_null_geo_count}")

(注意:此代码需在删除 user_geohash 列之前执行。根据原始数据情况,非空记录数是一个固定值,例如可能为 2000000。)

  1. 数据衍生:除了按日期和小时排序,有时我们还需要按“周几”进行分析。请尝试从 date 列中衍生出一个新的列 weekday,表示星期几(0为周一,6为周日),并查看前10条数据中,星期一的记录有多少条。
点击查看参考答案
# 衍生 weekday 列
data_df['weekday'] = data_df['date'].dt.weekday  # 注意:在较新pandas版本中,weekday属性返回0-6(周一到周日)

# 查看前10条数据中星期一的记录数 (weekday == 0)
monday_count_in_first_10 = (data_df.head(10)['weekday'] == 0).sum()
print(f"前10条数据中,星期一的记录有 {monday_count_in_first_10} 条。")

# 也可以查看整个数据集中各星期的分布(可选)
print("\n整个数据集中各星期记录数:")
print(data_df['weekday'].value_counts().sort_index())

本章小结

本节我们完成了综合项目中至关重要的一步——数据准备。我们不仅学习了如何处理一个真实、大规模的数据集,更掌握了数据工作的标准流程:认识数据 → 清洗数据 → 处理数据 → 保存成果。这份干净的 cln_proj_dataset.csv 文件,将成为我们后续所有可视化图表的坚实数据基础。

📋 行动清单

  • 理解数据清洗三要素:能清晰说出检查重复值、缺失值和无用列的具体方法及其在 pandas 中的实现函数(duplicated(), isnull(), drop())。
  • 掌握数据处理关键操作:能够使用 map() 方法为分类数据添加描述,使用 str.split() 和 pd.to_datetime() 正确处理时间字符串,并理解按多列排序的意义。
  • 养成保存中间成果的习惯:在完成重要数据处理阶段后,及时将干净数据保存为独立文件,提升后续分析效率。

—— 小象教研组