📘 第3课:你与数据的第一次互动——加载、探索和基本准备

"数据不是冰冷的数字。它们是故事、模式、错误和机会。学会倾听它们。"


⏱️ 本课预计时长: 75-90分钟


🧭 为什么这节课如此重要?

因为这里是理论变为实践的地方。

在第2课中你学习了地图。现在,你要走上这条路

你将:

  • 加载一个真实的数据集。
  • 像侦探一样探索它。
  • 像外科医生一样清理它。
  • 像厨师一样准备它。
  • 并让它准备好被模型理解。

⚠️ 友好提醒: 这节课的代码比之前的多。但不要害怕。我们会一步步来,有详细的解释、常见错误和专家提示。你不会孤单的。


🎯 本课目标

完成后,你将能够:

✅ 使用Pandas从URL或本地文件加载数据集。
✅ 探索其结构、内容和可能的问题(空值、重复、奇怪的值)。
✅ 创建简单的可视化来理解模式。
✅ 为模型准备数据:编码标签、分割训练/测试、向量化文本。
✅ 理解每个准备步骤的必要性。
✅ 舒适地操作数据……你的新原材料!


🛠️ 你将使用的工具

  • Pandas → 数据操作的瑞士军刀。
  • Matplotlib / Seaborn → 基本可视化。
  • Scikit-learn → 分割数据和向量化文本。
  • Jupyter Notebook或Google Colab → 你的实验实验室。

💡 如果你还没这样做,现在打开Google Colab:https://colab.research.google.com
创建一个新的notebook,让我们开始吧!


📥 第1部分:加载数据——你的第一次导入

我们将使用SMS垃圾邮件收集数据集。它小而干净,非常适合入门。

🔹 步骤1:导入必要的库

# 总是从导入你需要的东西开始
import pandas as pd

📌 什么是Pandas?
它是用于操作和分析数据的Python库。把它想象成Excel,但更强大且可编程。


🔹 步骤2:从URL加载数据集

# 数据集URL(托管在GitHub上)
url = "https://raw.githubusercontent.com/justmarkham/DAT8/master/data/sms.tsv  "

# 使用pandas加载
# 文件是制表符分隔的(\t),没有标题
data = pd.read_csv(url, sep='\t', names=['label', 'message'])

# 显示前5行
print(data.head())

📌 预期输出:

  label                                            message
0   ham  Go until jurong point, crazy.. Available only ...
1   ham                      Ok lar... Joking wif u oni...
2  spam  Free entry in 2 a wkly comp to win FA Cup fina...
3   ham  U dun say so early hor... U c already then say...
4   ham  Nah I don't think he goes to usf, he lives aro...

✅ 数据已加载!你现在有了一个Pandas DataFrame。


🔹 步骤3:理解数据集结构

# 多少行和列?
print(f"数据集形状: {data.shape}")  # (5572, 2)

# 列名
print(f"列: {data.columns.tolist()}")  # ['label', 'message']

# 数据类型
print(data.dtypes)

📌 输出:

label      object
message    object
dtype: object

→ 两列都是object类型(Pandas中的文本)。


🔹 步骤4:查看基本统计

# 统计摘要(仅适用于数值列,但有助于查看是否有空值)
print(data.describe(include='all'))

📌 关键输出:

       label           message
count   5572              5572
unique     2              5169
top      ham  Sorry, I'll call later
freq    4825                30

→ 标签中unique=2:只有两个值:'ham'和'spam'。
top=ham:最频繁的值是'ham'。
freq=4825:'ham'出现了4825次。


🔍 第2部分:探索数据——做数据侦探

现在,让我们深入挖掘。不要假设任何事情。探索一切。


🔸 问题1:有多少垃圾邮件和正常邮件?

print(data['label'].value_counts())

📌 输出:

ham     4825
spam     747
Name: label, dtype: int64

→ 我们有一个不平衡的数据集!正常邮件是垃圾邮件的6.5倍。
→ 这在垃圾邮件检测中很正常……但这会影响我们如何评估模型。我们将在第6课中看到!


🔸 问题2:有空值吗?

print(data.isnull().sum())

📌 输出:

label      0
message    0
dtype: int64

→ 完美!没有空值。在现实生活中,这很少见。总是要检查这个。


🔸 问题3:消息长度如何分布?

# 创建一个新列:消息长度
data['length'] = data['message'].apply(len)

# 描述性统计
print(data['length'].describe())

📌 输出:

count    5572.000000
mean       80.489052
std        59.942492
min         2.000000
25%        36.000000
50%        61.000000
75%       111.000000
max       910.000000
Name: length, dtype: float64

→ 有消息长达910个字符!它们会是垃圾邮件吗?会是正常的吗?


🔸 问题4:可视化长度分布

import matplotlib.pyplot as plt
import seaborn as sns

# 设置样式
sns.set_style("whitegrid")

# 长度直方图,按标签着色
plt.figure(figsize=(12, 6))
sns.histplot(data=data, x='length', hue='label', bins=50, kde=False)
plt.title("按类型的消息长度分布(垃圾邮件vs正常邮件)", fontsize=16)
plt.xlabel("消息长度(字符)", fontsize=12)
plt.ylabel("频率", fontsize=12)
plt.legend(title='类型', labels=['垃圾邮件', '正常邮件'])
plt.show()

📌 你看到了什么?

  • 垃圾邮件消息往往更长(许多在150-200个字符左右)。
  • 正常邮件消息更短(集中在20到100个字符之间)。
  • 这是一个有价值的线索!长度可能是模型的有用特征。

🔸 问题5:垃圾邮件中出现什么词?

让我们做一个非常基本的文本分析。

# 只过滤垃圾邮件
spam_messages = data[data['label'] == 'spam']['message']

# 转换为小写并分割成单词
words = ' '.join(spam_messages).lower().split()

# 计算词频
from collections import Counter
word_freq = Counter(words)

# 显示垃圾邮件中最常见的20个词
print("垃圾邮件中最频繁的词:")
for word, freq in word_freq.most_common(20):
    print(f"{word}: {freq}")

📌 典型输出:

free: 167
to: 137
you: 117
call: 90
txt: 89
now: 87
...

→ 当然!像"free"、"call"、"now"这样的词在垃圾邮件中很常见。
→ 这证实了模型将能够从这些线索中学习。


🧹 第3部分:准备数据——清理和转换

现在,我们将为模型准备数据。记住:模型理解数字,而不是文本


🔹 步骤1:编码标签(标签编码)

我们将把'ham'和'spam'转换为0和1。

# 创建映射
label_map = {'ham': 0, 'spam': 1}

# 应用映射
data['label_encoded'] = data['label'].map(label_map)

# 验证
print(data[['label', 'label_encoded']].head())

📌 输出:

  label  label_encoded
0   ham              0
1   ham              0
2  spam              1
3   ham              0
4   ham              0

→ 完成!现在标签是数字。


🔹 步骤2:分割为训练和测试

永远不要用相同的数据训练和评估!

from sklearn.model_selection import train_test_split

# 特征(X) = 消息
# 标签(y) = label_encoded
X = data['message']
y = data['label_encoded']

# 分割:80%训练,20%测试
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,
    random_state=42,  # 为了可重现性
    stratify=y        # 保持训练和测试中的垃圾邮件/正常邮件比例!
)

print(f"训练集大小: {len(X_train)} 条消息")
print(f"测试集大小: {len(X_test)} 条消息")
print(f"训练集中垃圾邮件比例: {y_train.mean():.2%}")
print(f"测试集中垃圾邮件比例: {y_test.mean():.2%}")

📌 输出:

训练集大小: 4457 条消息
测试集大小: 1115 条消息
训练集中垃圾邮件比例: 13.42%
测试集中垃圾邮件比例: 13.41%

→ 完美!多亏了stratify=y,比例得到了保持。


🔹 步骤3:向量化文本——将词转换为数字

我们将使用Scikit-learn的CountVectorizer

from sklearn.feature_extraction.text import CountVectorizer

# 创建向量化器
vectorizer = CountVectorizer()

# 学习词汇表并转换X_train
X_train_vec = vectorizer.fit_transform(X_train)

# 只转换X_test(不要从它学习!)
X_test_vec = vectorizer.transform(X_test)

# 查看大小
print(f"词汇表: {len(vectorizer.vocabulary_)} 个唯一词")
print(f"X_train_vec 形状: {X_train_vec.shape}")  # (4457, 7358)
print(f"X_test_vec 形状: {X_test_vec.shape}")    # (1115, 7358)

📌 (4457, 7358)是什么意思?

  • 4457 → 训练中的消息数。
  • 7358 → 词汇表中的唯一词数(从训练中学习)。

→ 每条消息现在是一个7358个数字的向量(大多数是0,因为并非所有词都出现在所有消息中)。


🔹 步骤4(可选):保存词汇表

想看看向量化器学到了什么词?

# 获取词汇表中的前20个词
vocab = vectorizer.get_feature_names_out()
print("词汇表中的前20个词:")
print(vocab[:20])

📌 输出:

['00', '000', '0000', '00000', '000000', '00001', '0001', '00011', '00012', '00015', '0002', '0003', '0004', '0005', '0006', '0007', '00080', '0009', '001', '0010']

→ 哎呀!有很多数字。为什么?因为CountVectorizer默认把一切都当作词,包括数字和标点符号。

💡 专业提示: 以后,你可以这样改进:

  • stop_words='english' → 删除常见词("the"、"and"、"is")。
  • lowercase=True → 转换为小写(默认已经这样做了)。
  • token_pattern=r'\b[a-zA-Z]{2,}\b' → 只取2个字母以上的词,不包括数字和符号。

但现在,这样就可以了!我们在学习。


🧪 第4部分:迷你探索项目——让它成为你的!

现在,轮到你来探索了。

🔸 练习1:最长的消息是什么?是垃圾邮件还是正常邮件?

# 找到最长消息的索引
idx_max = data['length'].idxmax()
longest_message = data.loc[idx_max]

print(f"长度: {longest_message['length']} 个字符")
print(f"类型: {longest_message['label']}")
print(f"消息: {longest_message['message']}")

📌 典型输出:

长度: 910 个字符
类型: spam
消息: "I HAVE A DATE ON SUNDAY WITH WILL!!..." (一个非常长的垃圾邮件!)

🔸 练习2:有多少消息超过200个字符?其中百分之多少是垃圾邮件?

# 过滤长消息
long_messages = data[data['length'] > 200]
total_long = len(long_messages)
spam_long = long_messages[long_messages['label'] == 'spam'].shape[0]

print(f"超过200个字符的消息: {total_long}")
print(f"其中,垃圾邮件: {spam_long} ({spam_long/total_long:.1%})")

📌 典型输出:

超过200个字符的消息: 45
其中,垃圾邮件: 43 (95.6%)

→ 几乎所有长消息都是垃圾邮件!这证实了我们的视觉假设。


🔸 练习3:文本有多干净?

看一些随机消息。你看到标点符号、大写字母、数字、拼写错误的迹象吗?

# 显示5个随机消息
sample = data.sample(5, random_state=1)
for i, row in sample.iterrows():
    print(f"[{row['label']}] {row['message'][:100]}...")  # 只显示前100个字符

→ 你会看到这样的东西:

  • "U dun say so early hor..." → 非正式语言,缩写。
  • "FreeMsg Hey there darling..." → 大写字母、符号、数字的混合。

💡 反思: 你认为这会影响模型吗?你如何改进它?(提示:文本清理、词形还原等——我们将在高级课程中看到这些)。


❌ 本课常见错误(避免它们!)

  1. train_test_split中不使用stratify → 使训练/测试不平衡。
  2. 在测试上应用fit_transform → 数据泄露。只能用transform
  3. 在向量化前不探索数据 → 你会错过模式和错误。
  4. 害怕高维度(7358列) → 在文本中这很正常!这叫做"高维空间"。
  5. 不将y_trainy_test保存为单独变量 → 后来你无法训练或评估。

✅ 本课检查清单——你现在应该知道如何做:

☐ 使用Pandas从URL加载数据集。
☐ 探索其结构、唯一值、空值和统计信息。
☐ 创建可视化来理解模式(长度、频繁词)。
☐ 将文本标签编码为数字。
☐ 分割数据为训练/测试保持比例(stratify)。
☐ 使用CountVectorizer向量化文本。
☐ 理解结果矩阵的形状。
☐ 提出探索性问题并用代码回答。
☐ 舒适地进行基本数据操作。


🎯 要记住的话:

"在训练模型之前,先训练你的眼睛。学会看到数据在告诉你什么。"


Course Info

Course: AI-course0

Language: ZH

Lesson: 3 data exploration