可视化帕默群岛企鹅数据¶
分析目标¶
对帕默群岛上企鹅样本的相关变量进行可视化,从而探索和分析种类、性别、所在岛屿等因素,与企鹅的身体属性,包括体重、嘴峰长度和深度、鳍的长度之间的关系。
简介¶
原始数据Penguins.csv包括334个收集自南极洲帕尔默群岛的3个岛屿上的企鹅样本,以及企鹅相关属性数据,包括种类名、所在岛、嘴峰长度、嘴峰深度、鳍长度、体重、性别。
Penguins.csv每列的含义如下:
- species:企鹅的种类
- island:企鹅所在岛
- culmen_length_mm:企鹅嘴峰的长度(单位为毫米)
- culmen_depth_mm:企鹅嘴峰的深度(单位为毫米)
- flipper_length_mm:企鹅鳍的长度(单位为毫米)
- body_mass_g:企鹅体重(单位为克)
- sex:企鹅性别
读取数据¶
导入数据分析所需要的库,并通过Pandas的read_csv函数,将原始数据文件Penguins.csv里的数据内容,解析为DataFrame并赋值给变量original_data。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
original_data = pd.read_csv("Penguins.csv")
original_data.head()
| species | island | culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | sex | |
|---|---|---|---|---|---|---|---|
| 0 | Adelie | Torgersen | 39.1 | 18.7 | 181.0 | 3750.0 | MALE |
| 1 | Adelie | Torgersen | 39.5 | 17.4 | 186.0 | 3800.0 | FEMALE |
| 2 | Adelie | Torgersen | 40.3 | 18.0 | 195.0 | 3250.0 | FEMALE |
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN |
| 4 | Adelie | Torgersen | 36.7 | 19.3 | 193.0 | 3450.0 | FEMALE |
评估和清理数据¶
对在上一部分建立的original_dataDataFrame所包含的数据进行评估和清理。
从两个方面进行:结构和内容,即整齐度和干净度。
数据的结构性问题指不符合“每个变量为一列,每个观察值为一行,每种类型的观察单位为一个表格”这三个标准;数据的内容性问题包括存在丢失数据、重复数据、无效数据等。
为了区分开经过清理的数据和原始的数据,我们创建新的变量cleaned_data,让它为original_data复制出的副本。我们之后的清理步骤都将被运用在cleaned_data上。
cleaned_data = original_data.copy()
数据整齐度¶
cleaned_data.head(10)
| species | island | culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | sex | |
|---|---|---|---|---|---|---|---|
| 0 | Adelie | Torgersen | 39.1 | 18.7 | 181.0 | 3750.0 | MALE |
| 1 | Adelie | Torgersen | 39.5 | 17.4 | 186.0 | 3800.0 | FEMALE |
| 2 | Adelie | Torgersen | 40.3 | 18.0 | 195.0 | 3250.0 | FEMALE |
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN |
| 4 | Adelie | Torgersen | 36.7 | 19.3 | 193.0 | 3450.0 | FEMALE |
| 5 | Adelie | Torgersen | 39.3 | 20.6 | 190.0 | 3650.0 | MALE |
| 6 | Adelie | Torgersen | 38.9 | 17.8 | 181.0 | 3625.0 | FEMALE |
| 7 | Adelie | Torgersen | 39.2 | 19.6 | 195.0 | 4675.0 | MALE |
| 8 | Adelie | Torgersen | 34.1 | 18.1 | 193.0 | 3475.0 | NaN |
| 9 | Adelie | Torgersen | 42.0 | 20.2 | 190.0 | 4250.0 | NaN |
从头部的10行数据来看,数据符合“每个变量为一列,每个观察值为一行,每种类型的观察单位为一个表格”,因此不存在结构性问题。
数据干净度¶
接下来通过info,对数据内容进行大致了解。
cleaned_data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 344 entries, 0 to 343 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 species 344 non-null object 1 island 344 non-null object 2 culmen_length_mm 342 non-null float64 3 culmen_depth_mm 342 non-null float64 4 flipper_length_mm 342 non-null float64 5 body_mass_g 342 non-null float64 6 sex 334 non-null object dtypes: float64(4), object(3) memory usage: 18.9+ KB
从输出结果来看,cleaned_data数据共有344条观察值,culmen_length_mm、culmen_depth_mm、flipper_length_mm、body_mass_g变量存在缺失值,将在后续进行评估和清理。
数据类型方面,我们已知species(企鹅种类)sex(企鹅性别)、island(企鹅所在岛)都是分类数据,因此可以把数据类型都转换为Category。
cleaned_data['species'] = cleaned_data['species'].astype("category")
cleaned_data['sex'] = cleaned_data['sex'].astype("category")
cleaned_data['island'] = cleaned_data['island'].astype("category")
cleaned_data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 344 entries, 0 to 343 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 species 344 non-null category 1 island 344 non-null category 2 culmen_length_mm 342 non-null float64 3 culmen_depth_mm 342 non-null float64 4 flipper_length_mm 342 non-null float64 5 body_mass_g 342 non-null float64 6 sex 334 non-null category dtypes: category(3), float64(4) memory usage: 12.3 KB
处理缺失数据¶
从info方法的输出来看,在cleaned_data中,culmen_length_mm、culmen_depth_mm、flipper_length_mm、body_mass_g、sex变量存在缺失值。
先提取出缺失这些变量的观察值进行查看。
cleaned_data.query("culmen_length_mm.isna()")
| species | island | culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | sex | |
|---|---|---|---|---|---|---|---|
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN |
| 339 | Gentoo | Biscoe | NaN | NaN | NaN | NaN | NaN |
cleaned_data.query("culmen_depth_mm.isna()")
| species | island | culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | sex | |
|---|---|---|---|---|---|---|---|
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN |
| 339 | Gentoo | Biscoe | NaN | NaN | NaN | NaN | NaN |
cleaned_data.query("flipper_length_mm.isna()")
| species | island | culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | sex | |
|---|---|---|---|---|---|---|---|
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN |
| 339 | Gentoo | Biscoe | NaN | NaN | NaN | NaN | NaN |
cleaned_data.query("body_mass_g.isna()")
| species | island | culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | sex | |
|---|---|---|---|---|---|---|---|
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN |
| 339 | Gentoo | Biscoe | NaN | NaN | NaN | NaN | NaN |
以上,可以看到索引为3和339的观察值,除了种类和所属岛屿外所有变量都为空,无法为探索企鹅身体属性相关因素提供价值,因此可以把这两行直接删除。
cleaned_data.drop(3, inplace=True)
cleaned_data.drop(339, inplace=True)
cleaned_data.query("sex.isna()")
| species | island | culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | sex | |
|---|---|---|---|---|---|---|---|
| 8 | Adelie | Torgersen | 34.1 | 18.1 | 193.0 | 3475.0 | NaN |
| 9 | Adelie | Torgersen | 42.0 | 20.2 | 190.0 | 4250.0 | NaN |
| 10 | Adelie | Torgersen | 37.8 | 17.1 | 186.0 | 3300.0 | NaN |
| 11 | Adelie | Torgersen | 37.8 | 17.3 | 180.0 | 3700.0 | NaN |
| 47 | Adelie | Dream | 37.5 | 18.9 | 179.0 | 2975.0 | NaN |
| 246 | Gentoo | Biscoe | 44.5 | 14.3 | 216.0 | 4100.0 | NaN |
| 286 | Gentoo | Biscoe | 46.2 | 14.4 | 214.0 | 4650.0 | NaN |
| 324 | Gentoo | Biscoe | 47.3 | 13.8 | 216.0 | 4725.0 | NaN |
缺失性别变量的观察值具备其它数据,仍然可以为分析提供价值。由于Pandas以及Matplotlib、Seaborn会自动忽略缺失值,可以保留这些行。
处理重复数据¶
根据数据变量的含义以及内容来看,允许变量重复,我们不需要对此数据检查是否存在重复值。
处理不一致数据¶
不一致数据可能存在于所有分类变量中,我们要查看是否存在不同值实际指代同一目标的情况。
cleaned_data["species"].value_counts()
species Adelie 151 Gentoo 123 Chinstrap 68 Name: count, dtype: int64
cleaned_data["island"].value_counts()
island Biscoe 167 Dream 124 Torgersen 51 Name: count, dtype: int64
cleaned_data["sex"].value_counts()
sex MALE 168 FEMALE 165 . 1 Name: count, dtype: int64
从以上输出来看,species和island列里并不存在不一致数据,但sex列里存在一个英文句号值,并不代表任何有效性别,我们应当把该值替换为NaN空值。
cleaned_data['sex'] = cleaned_data['sex'].cat.add_categories(['nan']).replace(".", 'nan')
#cleaned_data['sex']是分类(Categorical)类型,而 replace()方法的当前行为会隐式修改分类的类别(Categories)
C:\Users\25778\AppData\Local\Temp\ipykernel_2028\1051071439.py:1: FutureWarning: The behavior of Series.replace (and DataFrame.replace) with CategoricalDtype is deprecated. In a future version, replace will only be used for cases that preserve the categories. To change the categories, use ser.cat.rename_categories instead.
cleaned_data['sex'] = cleaned_data['sex'].cat.add_categories(['nan']).replace(".", 'nan')
查看英文句号值是否还存在。
cleaned_data["sex"].value_counts()
sex MALE 168 FEMALE 165 nan 1 Name: count, dtype: int64
英文句号值已被替换为空值,因此sex列里不存在不一致数据。
处理无效或错误数据¶
可以通过DataFrame的describe方法,对数值统计信息进行快速了解。
cleaned_data.describe()
| culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | |
|---|---|---|---|---|
| count | 342.000000 | 342.000000 | 342.000000 | 342.000000 |
| mean | 43.921930 | 17.151170 | 200.915205 | 4201.754386 |
| std | 5.459584 | 1.974793 | 14.061714 | 801.954536 |
| min | 32.100000 | 13.100000 | 172.000000 | 2700.000000 |
| 25% | 39.225000 | 15.600000 | 190.000000 | 3550.000000 |
| 50% | 44.450000 | 17.300000 | 197.000000 | 4050.000000 |
| 75% | 48.500000 | 18.700000 | 213.000000 | 4750.000000 |
| max | 59.600000 | 21.500000 | 231.000000 | 6300.000000 |
从以上统计信息来看,cleaned_house_price里不存在脱离现实意义的数值。
数据探索¶
通过数据可视化,进行探索和分析,从图表中获得数据的相关洞察
# 设置图表色盘为粉彩pastel
sns.set_palette('pastel')
cleaned_data
| species | island | culmen_length_mm | culmen_depth_mm | flipper_length_mm | body_mass_g | sex | |
|---|---|---|---|---|---|---|---|
| 0 | Adelie | Torgersen | 39.1 | 18.7 | 181.0 | 3750.0 | MALE |
| 1 | Adelie | Torgersen | 39.5 | 17.4 | 186.0 | 3800.0 | FEMALE |
| 2 | Adelie | Torgersen | 40.3 | 18.0 | 195.0 | 3250.0 | FEMALE |
| 4 | Adelie | Torgersen | 36.7 | 19.3 | 193.0 | 3450.0 | FEMALE |
| 5 | Adelie | Torgersen | 39.3 | 20.6 | 190.0 | 3650.0 | MALE |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 338 | Gentoo | Biscoe | 47.2 | 13.7 | 214.0 | 4925.0 | FEMALE |
| 340 | Gentoo | Biscoe | 46.8 | 14.3 | 215.0 | 4850.0 | FEMALE |
| 341 | Gentoo | Biscoe | 50.4 | 15.7 | 222.0 | 5750.0 | MALE |
| 342 | Gentoo | Biscoe | 45.2 | 14.8 | 212.0 | 5200.0 | FEMALE |
| 343 | Gentoo | Biscoe | 49.9 | 16.1 | 213.0 | 5400.0 | MALE |
342 rows × 7 columns
企鹅种类比例¶
cleaned_data.groupby('species',observed=False)['island'].count()
#未来版本中observed的默认值从False改为True,因此要设置observed=False
species Adelie 151 Chinstrap 68 Gentoo 123 Name: island, dtype: int64
species_count=cleaned_data['species'].value_counts()
species_count
species Adelie 151 Gentoo 123 Chinstrap 68 Name: count, dtype: int64
plt.pie(species_count,autopct='%.1f%%',labels=species_count.index)
plt.show()
样本中Adelie最多,Chinstrap最少
企鹅所属岛屿比例¶
island_count=cleaned_data['island'].value_counts()
plt.pie(species_count,autopct='%.1f%%',labels=island_count.index)
plt.show()
一半左右的企鹅都来自于Biscoe岛屿
企鹅性别比例¶
sex_count=cleaned_data['sex'].value_counts()
plt.pie(sex_count,autopct='%.1f%%',labels=sex_count.index)
plt.show()
性别占比持平,符合随机抽样
不同岛屿上的企鹅种类数量¶
sns.countplot(cleaned_data,x='island',hue='species')
plt.show()
不同岛屿上企鹅性别数量¶
sns.countplot(cleaned_data,x='island',hue='sex')
plt.show()
基本持平,符合随机抽样预期
sns.pairplot(cleaned_data)
plt.show()
在 seaborn.pairplot 的对角线直方图中:
- x 轴和 y 轴的含义
x 轴:表示该变量的 取值区间(分箱/bins)。
y 轴:表示落入每个区间的观测值数量(频数)。
变量名相同:因为这是单变量分布分析(自己 vs 自己),所以 x 轴和 y 轴标注相同,但实际含义不同(x=值,y=频数)。
- 直方图的“高度”代表什么?
直接反映该区间内数据的数量,例如,若某个柱子的高度为 50,表示有 50 个数据点落在该区间。
如果设置 density=True(或 kde=True),y 轴会转换为 概率密度(总面积=1),此时高度代表相对频率而非绝对数量。
在直方图中看出四个变量分布均不是正态分布,可能是样本数不够大或是包含多组存在差异的样本数据。
在散点图中看出明显的多个集群,可能与企鹅种类、性别等有关
根据种类查看数值之间的相关关系¶
sns.pairplot(cleaned_data,hue='species')
plt.show()
同一种类的企鹅数据在散点图是基本都聚在一起,说明同一种类的企鹅在这些变量上有相似性
sns.pairplot(cleaned_data,hue='species',kind='reg')
plt.show()
sns.pairplot(cleaned_data,hue='species',kind='reg',plot_kws={'scatter_kws':{'alpha':0.3}})
plt.show()
散点图结合线性回归线来看,同种企鹅的属性数据之间均成线性正比,可由此大致根据企鹅种类判断企鹅属性,或根据企鹅属性判断企鹅种类
对角线上的图为密度图,密度图的纵坐标不是直接的概率值,而是概率密度,单位是 “概率/单位X”(例如:概率/厘米、概率/美元等)
概率密度可以大于1(因为它是单位区间内的概率强度)
概率必须通过积分计算一定范围内的面积得到
峰值对应数据分布的最高概率密度区域,即数据最集中的位置,例如:身高数据的峰值在1.75m,说明大多数人的身高接近1.75m
总面积 = 1(所有可能事件的概率总和为1)
根据性别查看数值之间的相关关系¶
sns.pairplot(cleaned_data,hue='sex')
plt.show()