# Python数据分析实战:用Pandas和sklearn玩转Iris数据集(附完整代码)
如果你刚开始接触数据分析,面对一堆陌生的术语和代码感到无从下手,那么Iris数据集就是你最好的“练手场”。这个经典的小数据集,就像学游泳时的浅水区,让你在安全的环境里熟悉Pandas的数据操作,感受sklearn建模的魅力,而不用担心被海量数据和复杂业务逻辑淹没。我刚开始学数据分析时,也是从Iris入门的,当时最大的感受是:原来那些听起来高大上的“特征工程”、“模型评估”,用这个数据集可以如此直观地理解和实践。
今天,我们就抛开那些枯燥的理论,直接上手代码。我会带你走完一个完整的数据分析小项目:从数据的获取与探索,到清洗与可视化,再到构建一个简单的分类模型,最后用模型去预测新数据。整个过程你会看到Pandas如何灵活地处理表格,sklearn如何简洁地搭建机器学习流程。更重要的是,我会分享一些我早期摸索时踩过的坑和总结的技巧,希望能帮你少走弯路。
## 1. 环境准备与数据初探
在动手写任何代码之前,确保你的Python环境里已经安装了必要的库。打开你的终端或命令提示符,用pip安装以下核心包:
```bash
pip install pandas numpy scikit-learn matplotlib seaborn
```
* `pandas`:数据分析的基石,用于数据读取、清洗、转换和分析。
* `numpy`:提供高效的数组运算,是许多科学计算库的基础。
* `scikit-learn`(简称sklearn):机器学习工具箱,包含了从数据预处理到模型训练的全套工具。
* `matplotlib` 和 `seaborn`:数据可视化库,让数据规律一目了然。
安装完成后,我们就可以在Jupyter Notebook或你喜欢的Python IDE中开始了。首先,让我们把Iris数据集加载进来,看看它的“庐山真面目”。
```python
# 导入必要的库
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import seaborn as sns
# 设置绘图风格,让图表更好看
sns.set(style="whitegrid")
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 从sklearn加载数据集
iris = load_iris()
```
`load_iris()`返回的是一个Bunch对象,这是一种类似字典的数据结构。我们来看看里面都有什么:
```python
print("数据类型:", type(iris))
print("\n可用的键:", iris.keys())
print("\n特征名称:", iris.feature_names)
print("\n目标类别名称:", iris.target_names)
```
你会看到类似这样的输出:
```
数据类型: <class 'sklearn.utils.Bunch'>
可用的键: dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename', 'data_module'])
特征名称: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
目标类别名称: ['setosa' 'versicolor' 'virginica']
```
> **提示**:`iris.DESCR`包含了数据集的详细描述,包括背景、特征说明等。打印出来看看,是了解一个新数据集的好习惯。
数据(`iris.data`)和目标标签(`iris.target`)目前是独立的numpy数组。为了用Pandas进行更方便的分析,我们将其合并成一个DataFrame。
```python
# 将数据和特征名称转换为DataFrame
df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
# 添加目标列(花的种类)
df['species'] = iris.target
# 将数字标签映射为可读的字符串名称
df['species'] = df['species'].map({0: 'setosa', 1: 'versicolor', 2: 'virginica'})
# 查看前5行数据
print("数据概览(前5行):")
print(df.head())
```
现在,数据已经以整洁的表格形式呈现了。我们先用Pandas的几个核心方法快速了解数据全貌:
```python
# 查看数据的基本信息:行数、列数、数据类型、非空值数量
print("数据基本信息:")
df.info()
# 查看数值型特征的统计摘要:计数、均值、标准差、最小值、四分位数、最大值
print("\n数值特征统计摘要:")
print(df.describe())
# 查看类别分布是否均衡
print("\n各类别样本数量:")
print(df['species'].value_counts())
```
从`df.info()`可以看到,所有150行数据都是完整的,没有缺失值,这为我们省去了处理缺失值的步骤。`df.describe()`给出的统计量则能让我们对每个特征的分布有一个初步印象,比如花瓣长度(petal length)的均值是3.76厘米,但标准差有1.76厘米,说明不同样本间差异较大。而类别分布显示三种鸢尾花各50个样本,非常均衡。
## 2. 数据清洗与特征工程
虽然Iris数据集非常干净,但真实世界的数据很少如此完美。这一节,我们模拟一些常见的数据问题并处理它们,同时进行一些基本的特征工程,为后续建模做准备。
**首先,我们故意“制造”一点缺失值,然后学习如何处理:**
```python
# 为了演示,随机在‘sepal width (cm)’列插入5个缺失值
np.random.seed(42) # 确保结果可复现
missing_indices = np.random.choice(df.index, size=5, replace=False)
df.loc[missing_indices, 'sepal width (cm)'] = np.nan
print("插入缺失值后,各列缺失情况:")
print(df.isnull().sum())
```
处理缺失值有多种策略,选择哪种取决于数据和业务背景。常见方法有:
- **删除缺失行**:如果缺失数据很少,且随机分布,可以考虑直接删除。
- **填充固定值**:用0、均值、中位数或众数填充。
- **前后值填充**:对于时间序列数据,常用前一个或后一个值填充。
- **模型预测填充**:用其他特征建立模型来预测缺失值。
对于这里的5个缺失值,我们尝试用该列的均值来填充:
```python
# 计算‘sepal width (cm)’的均值(自动忽略NaN)
mean_sepal_width = df['sepal width (cm)'].mean()
print(f"花萼宽度均值为: {mean_sepal_width:.2f} cm")
# 用均值填充缺失值
df['sepal width (cm)'].fillna(mean_sepal_width, inplace=True)
print("\n填充后缺失值检查:")
print(df.isnull().sum())
```
**接下来,检查并处理重复数据:**
```python
# 检查是否有完全重复的行
duplicate_rows = df.duplicated().sum()
print(f"完全重复的行数: {duplicate_rows}")
# 如果有重复,可以删除
if duplicate_rows > 0:
df.drop_duplicates(inplace=True)
print(f"已删除重复行,剩余行数: {len(df)}")
```
**然后,我们进行一些简单的特征工程。** 特征工程的目的是创造对模型预测更有帮助的新特征。对于Iris数据,我们可以基于已有的四个测量值构造一些比率或面积特征。
```python
# 创建新特征:花萼面积(近似)和花瓣面积(近似)
df['sepal_area'] = df['sepal length (cm)'] * df['sepal width (cm)']
df['petal_area'] = df['petal length (cm)'] * df['petal width (cm)']
# 创建新特征:花瓣长宽比和花萼长宽比
df['petal_ratio'] = df['petal length (cm)'] / df['petal width (cm)']
df['sepal_ratio'] = df['sepal length (cm)'] / df['sepal width (cm)']
# 查看添加新特征后的数据前几行
print("添加新特征后的数据:")
print(df[['sepal length (cm)', 'sepal width (cm)', 'sepal_area', 'sepal_ratio', 'species']].head())
```
**最后,数据标准化。** 许多机器学习算法(如K近邻、支持向量机、神经网络)对特征的尺度很敏感。如果特征A的取值范围是0-10,特征B是1000-10000,那么特征B可能会主导模型的学习过程。标准化可以将所有特征转换到相近的尺度。
```python
from sklearn.preprocessing import StandardScaler
# 选择需要标准化的数值特征(排除目标列‘species’)
features_to_scale = [col for col in df.columns if col not in ['species']]
# 初始化标准化器
scaler = StandardScaler()
# 拟合(计算均值和标准差)并转换数据
df_scaled = pd.DataFrame(scaler.fit_transform(df[features_to_scale]), columns=features_to_scale)
# 将标准化后的特征与目标列重新合并
df_scaled['species'] = df['species'].reset_index(drop=True)
print("标准化后的数据统计摘要(均值为0,标准差为1):")
print(df_scaled[features_to_scale[:4]].describe().round(2))
```
现在,我们的数据已经清洗完毕,并准备好了新的特征。接下来,让我们用可视化来更深入地探索数据。
## 3. 多维数据可视化探索
图表比数字更能揭示数据的模式和关系。我们将使用多种可视化技术,从不同角度观察Iris数据集。
**散点图矩阵(Pair Plot)**:这是探索多个特征两两之间关系以及按类别着色的绝佳工具。Seaborn库的`pairplot`函数可以一键生成。
```python
# 使用原始数据(非标准化)绘制散点图矩阵,按物种着色
sns.pairplot(df, hue='species', palette='husl', markers=["o", "s", "D"])
plt.suptitle('鸢尾花数据集特征散点图矩阵', y=1.02)
plt.show()
```
观察这张图,你能立刻发现一些关键信息:例如,花瓣长度和花瓣宽度(左下角或右上角的散点图)呈现明显的线性关系,并且不同种类的点形成了清晰的簇。山鸢尾(setosa)在花瓣尺寸上与其他两种花完全分离。而花萼的尺寸区分度则没那么高。
**箱线图(Box Plot)**:用于查看每个特征在不同类别下的分布情况,包括中位数、四分位距和潜在的异常值。
```python
# 将数据从“宽格式”转换为“长格式”,便于用seaborn绘制分面箱线图
df_melted = df.melt(id_vars='species', value_vars=iris.feature_names,
var_name='feature', value_name='measurement (cm)')
plt.figure(figsize=(12, 6))
sns.boxplot(x='feature', y='measurement (cm)', hue='species', data=df_melted, palette='Set2')
plt.title('不同鸢尾花物种在各特征上的分布(箱线图)')
plt.xticks(rotation=45)
plt.legend(title='Species')
plt.tight_layout()
plt.show()
```
箱线图清晰地展示了:setosa的花瓣尺寸(petal length/width)远小于其他两种;versicolor和virginica在花萼尺寸上重叠较多,但在花瓣尺寸上virginica整体更大。
**热力图(Heatmap)**:展示所有数值特征之间的相关系数。相关系数衡量的是两个变量线性相关的强度和方向,范围从-1到1。
```python
# 计算数值特征之间的相关系数矩阵
correlation_matrix = df[iris.feature_names].corr()
plt.figure(figsize=(8, 6))
# annot=True显示数值,cmap选择颜色映射,fmt控制数值格式
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0, fmt='.2f', square=True)
plt.title('特征间相关系数热力图')
plt.tight_layout()
plt.show()
```
从热力图中可以看到,花瓣长度和花瓣宽度高度正相关(0.96),这意味着一个变大时,另一个也倾向于变大。花萼长度和花瓣长度也有较强的正相关(0.87)。而花萼宽度与其他特征的相关性较弱,甚至与花瓣宽度呈微弱的负相关。
**小提琴图(Violin Plot)**:结合了箱线图和核密度估计图,能更好地展示数据的分布形状。
```python
plt.figure(figsize=(10, 6))
sns.violinplot(x='species', y='petal length (cm)', data=df, palette='muted', inner='quartile')
plt.title('不同物种的花瓣长度分布(小提琴图)')
plt.ylabel('花瓣长度 (cm)')
plt.xlabel('物种')
plt.show()
```
小提琴图显示了versicolor和virginica的花瓣长度分布都是多峰的,这可能暗示了数据内部还有更细的子结构,或者单纯是样本量较小导致的波动。
通过这些可视化,我们不仅验证了数据质量,还对不同鸢尾花种类的区分特征有了直观认识。这为我们选择合适的特征来构建分类模型提供了重要依据。
## 4. 构建与评估分类模型
有了对数据的深入理解,现在我们可以尝试构建一个机器学习模型,让它自动根据花的四个测量值来预测其种类。我们将使用sklearn这个强大的工具箱,它让机器学习流程变得非常模块化和简单。
**第一步,准备数据。** 我们需要将数据分为特征(X)和目标变量(y),然后进一步划分为训练集和测试集。
```python
from sklearn.model_selection import train_test_split
# 定义特征和目标
# 这里我们使用原始特征加上我们创建的两个面积特征
selected_features = ['sepal length (cm)', 'sepal width (cm)',
'petal length (cm)', 'petal width (cm)',
'sepal_area', 'petal_area']
X = df[selected_features].values # 转换为numpy数组
y = df['species'].values
# 将数据分割为训练集(80%)和测试集(20%)
# random_state确保每次分割结果一致,stratify=y确保训练集和测试集中各类别比例与原数据一致
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
print(f"训练集样本数: {X_train.shape[0]}")
print(f"测试集样本数: {X_test.shape[0]}")
```
**第二步,选择并训练模型。** 我们从一个简单直观的模型开始——K近邻(K-Nearest Neighbors, KNN)。它的思想很简单:对于一个新样本,看看它在特征空间里最近的K个邻居大多数属于哪一类,就把它归为哪一类。
```python
from sklearn.neighbors import KNeighborsClassifier
# 初始化KNN分类器,设置n_neighbors=5
knn_model = KNeighborsClassifier(n_neighbors=5)
# 在训练集上拟合(训练)模型
knn_model.fit(X_train, y_train)
print("KNN模型训练完成。")
```
**第三步,在测试集上评估模型性能。** 我们用训练好的模型对从未见过的测试集数据进行预测,然后将预测结果与真实标签对比。
```python
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
# 对测试集进行预测
y_pred = knn_model.predict(X_test)
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"模型在测试集上的准确率: {accuracy:.4f}")
# 打印更详细的分类报告
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
# 绘制混淆矩阵
conf_matrix = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6,5))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues',
xticklabels=iris.target_names,
yticklabels=iris.target_names)
plt.ylabel('真实标签')
plt.xlabel('预测标签')
plt.title('混淆矩阵')
plt.show()
```
如果一切顺利,你应该能看到一个很高的准确率(通常在0.95以上)。分类报告提供了精确率(precision)、召回率(recall)和F1分数等更细致的指标。混淆矩阵则直观地显示了哪些类别容易被混淆。
**但是,只用一个模型和一组参数就下结论为时过早。** 我们需要思考:K值选5一定是最优的吗?有没有其他模型可能表现更好?为了更可靠地评估,我们可以进行两件事:1) 对KNN模型进行超参数调优;2) 尝试其他类型的模型。
```python
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
# 1. 使用网格搜索为KNN寻找最佳K值
param_grid = {'n_neighbors': np.arange(1, 15)}
knn = KNeighborsClassifier()
knn_cv = GridSearchCV(knn, param_grid, cv=5) # 使用5折交叉验证
knn_cv.fit(X_train, y_train)
print(f"KNN最佳参数: {knn_cv.best_params_}")
print(f"KNN最佳交叉验证分数: {knn_cv.best_score_:.4f}")
# 2. 尝试决策树和随机森林
models = {
'决策树': DecisionTreeClassifier(random_state=42),
'随机森林': RandomForestClassifier(n_estimators=100, random_state=42),
'优化KNN': KNeighborsClassifier(n_neighbors=knn_cv.best_params_['n_neighbors'])
}
# 训练并评估每个模型
results = {}
for name, model in models.items():
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
results[name] = acc
print(f"{name} 测试集准确率: {acc:.4f}")
# 用表格对比结果
results_df = pd.DataFrame(list(results.items()), columns=['模型', '测试准确率'])
print("\n模型性能对比:")
print(results_df)
```
通过这个对比,你可能会发现随机森林或优化后的KNN表现最好。这引出了一个重要的数据分析思维:**没有“最好”的模型,只有“更适合”当前数据和任务的模型**。对于像Iris这样线性可分性很好的小数据集,简单模型往往就足够了。
## 5. 模型应用与结果解读
模型训练和评估的最终目的,是为了用它来解决实际问题。假设我们现在有一个植物园,新采集了几朵鸢尾花的测量数据,但还没来得及由植物学家鉴定种类。这时,我们的模型就可以派上用场了。
让我们模拟一批新数据,并用我们训练好的最佳模型(假设是随机森林)进行预测。
```python
# 假设我们新测量了10朵花的尺寸
new_flowers_data = {
'sepal length (cm)': [5.1, 6.3, 7.0, 4.9, 5.0, 6.5, 5.5, 5.2, 5.4, 6.7],
'sepal width (cm)': [3.5, 2.9, 3.2, 2.4, 3.3, 3.8, 2.3, 3.4, 3.9, 3.1],
'petal length (cm)': [1.4, 4.9, 5.5, 3.3, 1.5, 2.8, 4.0, 1.4, 1.3, 5.6],
'petal width (cm)': [0.2, 1.5, 2.1, 1.0, 0.3, 0.7, 1.3, 0.2, 0.4, 2.4]
}
new_df = pd.DataFrame(new_flowers_data)
# 记得!新数据需要经过和训练数据完全相同的预处理流程
# 1. 计算我们之前添加的特征
new_df['sepal_area'] = new_df['sepal length (cm)'] * new_df['sepal width (cm)']
new_df['petal_area'] = new_df['petal length (cm)'] * new_df['petal width (cm)']
# 2. 使用之前拟合好的scaler对新数据的原始特征进行标准化
# 注意:我们只对原始特征+新特征进行标准化,scaler对象记得要保存
new_features_scaled = scaler.transform(new_df[selected_features])
# 选择我们训练好的随机森林模型进行预测
best_model = RandomForestClassifier(n_estimators=100, random_state=42)
best_model.fit(X_train, y_train) # 这里重新拟合一下,实际应用中模型应该是已经训练好的对象
predictions = best_model.predict(new_features_scaled)
prediction_proba = best_model.predict_proba(new_features_scaled) # 获取预测概率
# 将预测结果添加到数据框中
new_df['predicted_species'] = predictions
new_df['prediction_confidence'] = np.max(prediction_proba, axis=1) # 取最大类别的概率作为置信度
print("新花朵的测量数据及预测结果:")
print(new_df[['sepal length (cm)', 'petal length (cm)', 'predicted_species', 'prediction_confidence']].round(3))
```
输出结果不仅给出了预测种类,还给出了模型做出该预测的“信心”有多高(概率)。这对于实际应用至关重要。例如,一个预测为“versicolor”但置信度只有0.51的样本,其可靠性远低于一个置信度为0.95的样本。对于低置信度的预测,我们可能需要标记出来,交由专家进行二次确认。
**更进一步,我们可以解读模型的预测依据。** 对于随机森林这类模型,我们可以查看特征重要性,了解模型在做决策时更看重哪些特征。
```python
# 获取特征重要性
feature_importance = best_model.feature_importances_
importance_df = pd.DataFrame({
'feature': selected_features,
'importance': feature_importance
}).sort_values('importance', ascending=False)
print("特征重要性排序:")
print(importance_df)
# 可视化特征重要性
plt.figure(figsize=(8,5))
sns.barplot(x='importance', y='feature', data=importance_df, palette='viridis')
plt.title('随机森林模型特征重要性')
plt.xlabel('重要性得分')
plt.tight_layout()
plt.show()
```
不出所料,花瓣相关的特征(尤其是花瓣长度和宽度)重要性最高,这与我们之前可视化观察到的结论一致——花瓣特征对区分鸢尾花种类贡献最大。这种解读增强了我们对模型的信任,也印证了我们的领域知识。
最后,记得将训练好的模型和预处理对象(如`scaler`)保存下来,以便将来直接加载使用,无需重新训练。
```python
import joblib
# 保存模型和标准化器
joblib.dump(best_model, 'iris_species_predictor.pkl')
joblib.dump(scaler, 'iris_scaler.pkl')
print("模型和预处理对象已保存。")
# 未来加载使用
# loaded_model = joblib.load('iris_species_predictor.pkl')
# loaded_scaler = joblib.load('iris_scaler.pkl')
```
走完这一整套流程,你不仅学会了如何用Pandas和sklearn处理一个具体的数据集,更重要的是掌握了一个标准的数据分析项目框架:从数据获取、探索、清洗、可视化,到建模、评估、调优,再到最终的应用与部署。这个框架可以迁移到无数其他数据集和问题上。Iris数据集就像一块敲门砖,门后的数据科学世界,正等着你用这些工具去探索和创造。