数据加载与初步探索
好,咱们正式开始。做数据合并前的质控预处理,第一步是什么?不是写代码,不是调参数。第一步是把数据请进来,然后看明白它长什么样。你想想看,连数据都没看清楚,就开始各种合并清洗,那后面肯定全是坑。
我个人习惯,拿到一个新数据集,前五分钟只做两件事:加载它,然后扫一眼基本信息。别小看这五分钟,它能帮你省下后面至少半天排查问题的时间。
数据源加载:从哪里来?
实际工作中,数据不会乖乖躺在同一个地方。CSV 文件、Excel 表格、数据库,甚至 API 接口,都有可能。咱们先看最常见的。
从 CSV 加载
CSV 是最朴素的数据交换格式。我一般直接用 pandas 的 read_csv,但你得留意它的默认行为。举个例子:
import pandas as pd
# 最基础的加载
df = pd.read_csv('data/raw_sales.csv')
# 记住,这行代码假设:
# 1. 文件用逗号分隔
# 2. 第一行是列名
# 3. 编码是系统默认的 UTF-8
万一你的 CSV 用分号分隔,或者编码是 GBK,记得加参数。我有一个项目就因为这个翻过车,接手的 CSV 是用 Excel 导出的,默认 GBK 编码,加载进来全是乱码。后来养成了习惯:永远先检查文件的前几行和编码格式。
从 Excel 加载
Excel 文档在业务部门很常见。麻烦的是,一个 Excel 文件里可能有好多个 Sheet。你读的时候得指定读哪个 Sheet。
# 加载 Excel 中的指定 Sheet
df_inventory = pd.read_excel('data/inventory.xlsx', sheet_name='Sheet1')
# 我先看看这个文件到底包含哪些 Sheet
xl_file = pd.ExcelFile('data/inventory.xlsx')
print(xl_file.sheet_names) # 输出所有的 Sheet 名
这里有件事要注意:Excel 里有些单元格可能是合并的,或者带格式。pandas 读进来会把合并单元格的空白部分填充为 NaN。嗯,这里要特别小心,那些 NaN 不是真正的缺失值,只是合并单元格导致的“视觉空缺”。
从数据库加载
从数据库加载数据,通常要直面 SQL 查询。我之前在一个 ETL 项目里,需要从 MySQL 把用户订单数据拉出来做分析。连接数据库你需要一个连接字符串,然后执行查询。
import pandas as pd
from sqlalchemy import create_engine
# 假设是 MySQL 数据库
# 连接格式: dialect+driver://username:password@host:port/database
engine = create_engine('mysql+pymysql://user:password@localhost:3306/sales_db')
# 执行 SQL 查询,结果直接变成 DataFrame
df_orders = pd.read_sql_query("SELECT * FROM orders WHERE order_date >= '2024-01-01'", con=engine)
# 查完记得关连接,不关可能会出现连接泄漏
engine.dispose()
初步探索:数据长什么样?
数据加载进来以后,别急着做任何处理。先花几分钟把数据的“长相”看透。我一般会用下面几个方法,就像医生给病人做体检一样——量体温、测血压、验血常规。
快速浏览前几行:head()
用 head() 默认看前 5 行,既能感受数据的格式,又能发现明显的格式错误。比如:日期字段看起来像字符串,金额字段里混进了“待确认”这样的中文描述。
print(df.head())
# 看随机几行,比只查看前几行更能发现异常
# 但我个人更喜欢先看 head(),因为它的结果稳定、可复现
查看数据结构:info()
info() 会显示每一列的名称、非空数量、数据类型。为什么说这个重要?我见过太多次把“订单金额”读成了对象(object)类型而不是浮点数的例子了。你想想看,如果金额是字符串,你怎么求和、求平均值?
print(df.info())
# 输出示例:
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 10000 entries, 0 to 9999
# Data columns (total 8 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 order_id 10000 non-null int64
# 1 user_id 10000 non-null int64
# 2 amount 9500 non-null object <-- 注意!金额应该是 float,但这里显示为 object
# 3 order_date 10000 non-null object <-- 日期应该转成 datetime
# 4 product_id 10000 non-null int64
# dtypes: float64(2), int64(3), object(3)
获取基本统计信息:describe()
describe() 默认只对数值型列做统计,包括计数、均值、标准差、最小值、四分位数和最大值。这是我用来快速判断数据范围是否合理的神器。
# 对数值型列做统计摘要
print(df.describe())
# 对分类文本列也做统计,加参数 include='object'
print(df.describe(include=['object']))
举个例子,如果你发现某个数值列的均值是 1000,但最大值是 100 万,而 75% 分位数是 1500——说明这个列的分布严重偏斜,可能有极端值(离群点)。嗯,这个发现会直接影响你后续是否要做均值填充还是中位数填充的决定。
值得额外关注的一些指标
| 函数/属性 | 作用 | 我在项目中遇到的场景 |
|---|---|---|
shape |
查看数据表的行数和列数 | 有一次加载后发现少了 2000 行,原来是过滤条件写错导致数据不全 |
columns |
列出所有列名,检查列名是否规范 | 某 CSV 文件中,列名带空格和特殊符号,复制粘贴时忘了加反引号 |
isnull().sum() |
统计每列缺失值的数量 | 有个“用户性别”列缺失了 60%,加载时以为是编码问题,其实是数据采集时没做校验 |
duplicated().sum() |
统计完全重复的行数 | 数据库导出时因为 JOIN 使用不当,导致产生了重复记录,还好及时发现 |
一次完整的加载和探索流程
下面展示一段完整的代码,它把上面讲的内容串在一起。你可以把它当作你每次处理新数据集时的检查清单脚本。
import pandas as pd
# 1. 加载数据
file_path = 'data/sample_sales.csv'
df = pd.read_csv(file_path)
# 2. 快速浏览
print("===== 数据概览 =====")
print(f"行数:{df.shape[0]},列数:{df.shape[1]}")
print("\n===== 前 5 行 =====")
print(df.head())
# 3. 检查缺失情况和数据类型
print("\n===== 数据类型与缺失值 =====")
print(df.info())
print("\n===== 数值列统计 =====")
print(df.describe())
print("\n===== 分类列统计 =====")
print(df.describe(include=['object']))
print("\n===== 重复行数 =====")
print(df.duplicated().sum())
我曾经接手过一个来自电商平台的订单数据,大小接近 500 万行。当时我直接把上面这段脚本跑了一遍,发现 “amount” 列缺失值比例高达 15%,而且格式参差不齐。如果不是初步探索,我不会知道后面做填充前,必须先完成格式清洗。你看,探测问题永远比解决问题更需要时间,但更值得花时间。
你看,整个知识体系非常直白:从不同的数据源出发,用对应的方法加载进来,然后用几个函数快速做“健康检查”。这一步虽然简单,但为你后面做合并、做清洗打下了扎实的基础。如果你跳过了这个阶段,你就是在蒙着眼睛做手术——万一数据本身有硬伤,后面的所有工作都会白费。
所以,记住我这句话:加载数据不是终点,看清它才是起点。