📚 Python入门到高级(知识点六)
💡 关键知识点
一、Pandas中的缺失值是什么
1.1 三种空值表现形式
Pandas中,缺失值主要有三种表现形式:
| 类型 | 说明 | 示例 |
|---|
None | Python原生的空值,类型为 NoneType | None |
np.nan | NumPy中的空值,类型为 float | np.nan |
pd.NA | Pandas 1.0+ 引入的专属空值 | pd.NA |
import numpy as np
import pandas as pd
print(None, type(None))
print(np.nan, type(np.nan))
print(pd.NA, type(pd.NA))
1.2 注意:这些「看起来像空值」的其实不是
| 值 | 是否识别为空值 | 说明 |
|---|
'null' | ❌ | 只是一个普通字符串 |
''(空字符串) | ❌ | Pandas认为空字符串是有内容的 |
np.inf(无穷大) | ❌ | 是有效数值 |
1.3 为什么不能用 == 判断空值?
在Python中,np.nan 有一个特殊性质——它不等于自身:
print(np.nan == np.nan)
print(np.nan is np.nan)
因此,判断空值必须使用Pandas提供的专用方法,而不是 == 运算符。
二、如何检测缺失值
2.1 核心检测方法
Pandas提供了两套功能完全等价的方法:
| 方法 | 含义 | 返回值 |
|---|
isna() / isnull() | 检测是否为缺失值 | True表示缺失 |
notna() / notnull() | 检测是否不是缺失值 | True表示非缺失 |
import pandas as pd
print(pd.isna(None))
print(pd.isna(np.nan))
print(pd.isna(pd.NA))
print(pd.isna('null'))
print(pd.isna(''))
2.2 实战:检测DataFrame中的缺失值
import pandas as pd
import numpy as np
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六'],
'年龄': [25, np.nan, 30, 28],
'城市': ['北京', '上海', np.nan, '广州'],
'收入': [5000, 6000, 5500, np.nan]
})
print(df)
print(df.isnull())
2.3 快速统计缺失情况
print(df.isnull().sum())
print(df.isnull().sum() / len(df) * 100)
print(df.isnull().sum().sum())
2.4 关于 isna() 和 isnull() 的疑问
很多初学者会问:为什么有两套名字不同的方法,做的事情却完全一样?
官方的说法是:isnull() 是 isna() 的别名(alias),两者功能完全相同。
原因在于Pandas的设计借鉴了R语言。在R中,NA(Not Available)和 NULL 是两个不同的概念,而Python/Numpy中统一使用 NaN 表示缺失值。Pandas为了兼容不同背景的用户,同时保留了两套命名。
建议:任选其一,在项目中保持风格统一即可。我个人习惯使用 isna() / notna(),因为更简短。
三、Pandas读取CSV时的缺失值处理参数总结
1. keep_default_na 参数
| 参数值 | 作用 |
|---|
True(默认) | 自动将常见缺失值(空值、NULL、null、None、NA、nan等)填充为 NaN |
False | 关闭自动识别,所有值保持原始内容,不填充 NaN |
2. na_values 参数
- 作用:手动指定哪些值应被识别为缺失值并填充为
NaN
- 使用前提:通常与
keep_default_na=False 配合使用
- 注意:设置
na_values 后,只有列表中指定的值会被替换为 NaN
方式一:默认读取(自动填充)
import pandas as pd
import numpy as np
df = pd.read_csv('data/survey_visited.csv')
方式二:关闭自动填充
import pandas as pd
import numpy as np
df = pd.read_csv('data/survey_visited.csv', keep_default_na=False)
方式三:关闭自动填充 + 自定义缺失值
import pandas as pd
import numpy as np
df = pd.read_csv('data/survey_visited.csv', keep_default_na=False, na_values=[None, np.nan, pd.NA])
四、Pandas删除缺失值详解
import pandas as pd
train = pd.read_csv('data/titanic_train.csv')
train.info()
print(train.shape)
方式一:dropna() 默认删除任意缺失行
new_train1 = train.dropna()
print(new_train1.shape)
方式二:dropna(how='all') 删除全为空的行
new_train2 = train.dropna(how='all')
print(new_train2.shape)
方式三:dropna(subset=[列名]) 指定列检测缺失
new_train3 = train.dropna(subset=['Age'])
print(new_train3.shape)
new_train3 = train.dropna(subset=['Age', 'Cabin'])
print(new_train3.shape)
方式四:drop() 直接删除指定列
new_train4 = train.drop('Cabin', axis=1)
print(new_train4.shape)
new_train5 = train.drop(['Cabin', 'Age'], axis=1)
print(new_train5.shape)
五种方法对比总结
| 方法 | 代码 | 删除对象 | 结果 (行, 列) |
|---|
| 原始数据 | train | 无 | (892, 12) |
dropna() | train.dropna() | 任意列有缺失的行 | (183, 12) |
dropna(how='all') | train.dropna(how='all') | 整行全为空的行 | (891, 12) |
dropna(subset=['Age']) | train.dropna(subset=['Age']) | Age列有缺失的行 | (714, 12) |
dropna(subset=['Age','Cabin']) | train.dropna(subset=['Age','Cabin']) | Age或Cabin有缺失的行 | (183, 12) |
drop('Cabin', axis=1) | train.drop('Cabin', axis=1) | Cabin这一列 | (892, 11) |
关键区别:dropna() vs drop()
| 方法 | 作用 | axis参数 |
|---|
dropna() | 删除行(有缺失的行) | 默认 axis=0 |
drop() | 删除列(指定的列) | 需指定 axis=1 |
常用参数速查表
| 参数 | 作用 | 示例 |
|---|
how='any' | 只要有缺失就删除(默认) | dropna(how='any') |
how='all' | 全部缺失才删除 | dropna(how='all') |
subset=[列名] | 指定检测哪些列 | dropna(subset=['Age','Cabin']) |
axis=0 | 删除行(默认) | dropna(axis=0) |
axis=1 | 删除列 | drop('列名', axis=1) |
inplace=True | 原地修改,不返回新对象 | dropna(inplace=True) |
五、Pandas填充缺失值详解(带示例数据)
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
df = pd.read_csv('data/students.csv')
print(df)
方式一:直接填充固定值(0)
new_df1 = df.fillna(0)
print(new_df1)
方式二:填充平均值
new_df2 = df.fillna(df['age'].mean())
print(new_df2)
方式三:前向填充(ffill)
new_df3 = df.fillna(method='ffill')
print(new_df3)
方式四:后向填充(bfill)
new_df4 = df.fillna(method='bfill')
print(new_df4)
四种方式对比总结
| 方式 | 代码 | 填充逻辑 | 适用场景 |
|---|
| 固定值 | fillna(0) | 所有缺失填同一个值 | 临时占位 |
| 平均值 | fillna(df['age'].mean()) | 用该列平均值填充 | 数值列,无明显异常值 |
| 前向填充 | fillna(method='ffill') | 用上一行同列的值 | 时间序列,数据渐变 |
| 后向填充 | fillna(method='bfill') | 用下一行同列的值 | 时间序列,数据渐变 |
常用填充方式汇总
df.fillna(0)
df.fillna('未知')
df['age'] = df['age'].fillna(df['age'].mean())
df['age'] = df['age'].fillna(df['age'].median())
df['gender'] = df['gender'].fillna(df['gender'].mode()[0])
df.fillna(method='ffill')
df.fillna(method='bfill')
df.fillna({
'age': df['age'].median(),
'score': 0,
'gender': '未知'
})
df.fillna(method='ffill', limit=1)
特殊知识点
1. 为什么 df.fillna(df['age'].mean()) 只填充了 age 列?
因为只传入了 df['age'].mean() 这一个值,Pandas 会将这个值应用到所有列的缺失值上。
- age 列是数值型,填充平均值 → 合理
- score 列也是数值型,填充 age 的平均值 → 错误!应该用 score 自己的平均值
- gender 列是字符串,填充数值 → 无法填充
正确做法:对不同列使用不同的填充值
df['age'] = df['age'].fillna(df['age'].mean())
df['score'] = df['score'].fillna(df['score'].mean())
df['gender'] = df['gender'].fillna(df['gender'].mode()[0])
2. ffill 和 bfill 的局限性
- 如果开头就有缺失,
ffill 无法填充(前面没有数据)
- 如果结尾就有缺失,
bfill 无法填充(后面没有数据)
- 连续多个缺失时,
ffill 会用同一个值填充所有缺失
六、Pandas分组聚合(GroupBy)详解(带示例数据)
import numpy as np
import pandas as pd
df = pd.read_csv('data/gapminder.txt', sep='\t')
print(df.shape)
数据简介
gapminder.txt 数据包含以下列:
country:国家
year:年份
pop:人口
continent:大洲
lifeExp:平均寿命
gdpPercap:人均GDP
需求1:求每个年份的平均寿命
四种写法对比
print(df.groupby('year').lifeExp.mean())
print(df.groupby('year')['lifeExp'].mean())
print(df.groupby('year')[['lifeExp']].mean())
print(df.groupby('year').agg({"lifeExp": 'mean'}))
print(df.groupby('year').agg({"lifeExp": np.mean}))
四种写法对比总结
| 写法 | 代码 | 返回类型 | 特点 |
|---|
| 写法1 | df.groupby('year').lifeExp.mean() | Series | 最简洁,点语法 |
| 写法2 | df.groupby('year')['lifeExp'].mean() | Series | 括号语法,适合列名有空格 |
| 写法3 | df.groupby('year')[['lifeExp']].mean() | DataFrame | 双层括号,返回DataFrame |
| 写法4 | df.groupby('year').agg({"lifeExp": 'mean'}) | DataFrame | 最推荐,可多个字段多个函数 |
需求2:求每个年份、每个大洲的平均寿命和最高GDP
print(df.groupby(['year', 'continent']).agg({"lifeExp": 'mean', "gdpPercap": 'max'}))
print(df.groupby(['year', 'continent']).agg({"lifeExp": np.mean, "gdpPercap": np.max}))
常用聚合函数
| 函数 | 作用 | 示例 |
|---|
mean() | 平均值 | 'mean' 或 np.mean |
max() | 最大值 | 'max' 或 np.max |
min() | 最小值 | 'min' 或 np.min |
sum() | 总和 | 'sum' 或 np.sum |
count() | 计数 | 'count' |
std() | 标准差 | 'std' 或 np.std |
var() | 方差 | 'var' 或 np.var |
median() | 中位数 | 'median' |
多个聚合函数同时使用
print(df.groupby('year')['lifeExp'].agg(['mean', 'max', 'min', 'std']))
print(df.groupby('year').agg({
'lifeExp': ['mean', 'max'],
'gdpPercap': ['mean', 'max'],
'pop': 'sum'
}))
关键知识点总结
1. groupby() 返回类型
df.groupby('year')['lifeExp']
df.groupby('year')[['lifeExp']]
df.groupby('year')[['lifeExp', 'gdpPercap']]
2. 点语法 df.列名 和括号语法 df['列名']
| 语法 | 适用场景 |
|---|
df.lifeExp | 列名无空格、无特殊字符 |
df['lifeExp'] | 列名有空格、有特殊字符,或需要动态列名 |
df[['lifeExp']] | 需要返回DataFrame(单列也返回DataFrame) |
3. agg() 是万能方法
df.groupby('year').agg({"lifeExp": 'mean'})
df.groupby('year').agg({"lifeExp": ['mean', 'max', 'min']})
df.groupby('year').agg({
"lifeExp": ['mean', 'max'],
"gdpPercap": ['mean', 'max'],
"pop": 'sum'
})
df.groupby('year').agg(
平均寿命=('lifeExp', 'mean'),
最高GDP=('gdpPercap', 'max')
)
七、Pandas分组聚合:groupby() vs pivot_table()
import numpy as np
import pandas as pd
df = pd.read_csv('data/gapminder.txt', sep='\t')
print(df.shape)
print(df.groupby('year').agg({"lifeExp": 'mean'}))
result = df.pivot_table(index=['year'], values='lifeExp', aggfunc='mean')
print(result)
两种方式对比
| 对比项 | groupby() + agg() | pivot_table() |
|---|
| 语法 | df.groupby('year').agg({"lifeExp": 'mean'}) | df.pivot_table(index=['year'], values='lifeExp', aggfunc='mean') |
| 参数含义 | groupby() 指定分组字段 agg() 指定聚合字段和函数 | index 指定分组字段 values 指定聚合字段 aggfunc 指定聚合函数 |
| 返回类型 | DataFrame | DataFrame |
| 适用场景 | 简单分组聚合 | 类似Excel透视表习惯 |
参数说明
groupby() + agg() 写法
df.groupby('year').agg({"lifeExp": 'mean'})
pivot_table() 写法
df.pivot_table(index=['year'], values='lifeExp', aggfunc='mean')
两种方式等价关系
df.groupby('year').agg({"lifeExp": 'mean'})
df.pivot_table(index=['year'], values='lifeExp', aggfunc='mean')
多字段扩展写法
多分组字段
df.groupby(['year', 'continent']).agg({"lifeExp": 'mean'})
df.pivot_table(index=['year', 'continent'], values='lifeExp', aggfunc='mean')
多聚合字段
df.groupby('year').agg({
"lifeExp": 'mean',
"gdpPercap": 'max'
})
df.pivot_table(
index='year',
values=['lifeExp', 'gdpPercap'],
aggfunc={'lifeExp': 'mean', 'gdpPercap': 'max'}
)
多聚合函数
df.groupby('year').agg({"lifeExp": ['mean', 'max', 'min']})
df.pivot_table(
index='year',
values='lifeExp',
aggfunc=['mean', 'max', 'min']
)
选择建议
| 场景 | 推荐方式 |
|---|
| 简单分组聚合 | groupby() + agg() |
| 习惯Excel透视表操作 | pivot_table() |
| 需要将字段值转为列名 | pivot_table()(加 columns 参数) |
| 追求性能 | groupby() 通常更快 |