"数据不是冰冷的数字。它们是故事、模式、错误和机会。学会倾听它们。"
因为这里是理论变为实践的地方。
在第2课中你学习了地图。现在,你要走上这条路。
你将:
⚠️ 友好提醒: 这节课的代码比之前的多。但不要害怕。我们会一步步来,有详细的解释、常见错误和专家提示。你不会孤单的。
完成后,你将能够:
✅ 使用Pandas从URL或本地文件加载数据集。
✅ 探索其结构、内容和可能的问题(空值、重复、奇怪的值)。
✅ 创建简单的可视化来理解模式。
✅ 为模型准备数据:编码标签、分割训练/测试、向量化文本。
✅ 理解每个准备步骤的必要性。
✅ 舒适地操作数据……你的新原材料!
💡 如果你还没这样做,现在打开Google Colab:https://colab.research.google.com
创建一个新的notebook,让我们开始吧!
我们将使用SMS垃圾邮件收集数据集。它小而干净,非常适合入门。
# 总是从导入你需要的东西开始
import pandas as pd
📌 什么是Pandas?
它是用于操作和分析数据的Python库。把它想象成Excel,但更强大且可编程。
# 数据集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。
# 多少行和列?
print(f"数据集形状: {data.shape}") # (5572, 2)
# 列名
print(f"列: {data.columns.tolist()}") # ['label', 'message']
# 数据类型
print(data.dtypes)
📌 输出:
label object
message object
dtype: object
→ 两列都是object类型(Pandas中的文本)。
# 统计摘要(仅适用于数值列,但有助于查看是否有空值)
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次。
现在,让我们深入挖掘。不要假设任何事情。探索一切。
print(data['label'].value_counts())
📌 输出:
ham 4825
spam 747
Name: label, dtype: int64
→ 我们有一个不平衡的数据集!正常邮件是垃圾邮件的6.5倍。
→ 这在垃圾邮件检测中很正常……但这会影响我们如何评估模型。我们将在第6课中看到!
print(data.isnull().sum())
📌 输出:
label 0
message 0
dtype: int64
→ 完美!没有空值。在现实生活中,这很少见。总是要检查这个。
# 创建一个新列:消息长度
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个字符!它们会是垃圾邮件吗?会是正常的吗?
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()
📌 你看到了什么?
让我们做一个非常基本的文本分析。
# 只过滤垃圾邮件
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"这样的词在垃圾邮件中很常见。
→ 这证实了模型将能够从这些线索中学习。
现在,我们将为模型准备数据。记住:模型理解数字,而不是文本。
我们将把'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
→ 完成!现在标签是数字。
永远不要用相同的数据训练和评估!
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,比例得到了保持。
我们将使用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)是什么意思?
→ 每条消息现在是一个7358个数字的向量(大多数是0,因为并非所有词都出现在所有消息中)。
想看看向量化器学到了什么词?
# 获取词汇表中的前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个字母以上的词,不包括数字和符号。但现在,这样就可以了!我们在学习。
现在,轮到你来探索了。
# 找到最长消息的索引
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!!..." (一个非常长的垃圾邮件!)
# 过滤长消息
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%)
→ 几乎所有长消息都是垃圾邮件!这证实了我们的视觉假设。
看一些随机消息。你看到标点符号、大写字母、数字、拼写错误的迹象吗?
# 显示5个随机消息
sample = data.sample(5, random_state=1)
for i, row in sample.iterrows():
print(f"[{row['label']}] {row['message'][:100]}...") # 只显示前100个字符
→ 你会看到这样的东西:
💡 反思: 你认为这会影响模型吗?你如何改进它?(提示:文本清理、词形还原等——我们将在高级课程中看到这些)。
train_test_split中不使用stratify → 使训练/测试不平衡。fit_transform → 数据泄露。只能用transform!y_train和y_test保存为单独变量 → 后来你无法训练或评估。☐ 使用Pandas从URL加载数据集。
☐ 探索其结构、唯一值、空值和统计信息。
☐ 创建可视化来理解模式(长度、频繁词)。
☐ 将文本标签编码为数字。
☐ 分割数据为训练/测试保持比例(stratify)。
☐ 使用CountVectorizer向量化文本。
☐ 理解结果矩阵的形状。
☐ 提出探索性问题并用代码回答。
☐ 舒适地进行基本数据操作。
"在训练模型之前,先训练你的眼睛。学会看到数据在告诉你什么。"
Course: AI-course0
Language: ZH
Lesson: 3 data exploration