Python入门到高级(知识点六)

📚 Python入门到高级(知识点六)


💡 关键知识点

一、Pandas中的缺失值是什么

1.1 三种空值表现形式

Pandas中,缺失值主要有三种表现形式:

类型 说明 示例
None Python原生的空值,类型为 NoneType None
np.nan NumPy中的空值,类型为 float np.nan
pd.NA Pandas 1.0+ 引入的专属空值 pd.NA
python 复制代码
import numpy as np
import pandas as pd

print(None, type(None))        # None <class 'NoneType'>
print(np.nan, type(np.nan))    # nan <class 'float'>
print(pd.NA, type(pd.NA))      # <NA> <class 'pandas._libs.missing.NAType'>

1.2 注意:这些「看起来像空值」的其实不是

是否识别为空值 说明
'null' 只是一个普通字符串
''(空字符串) Pandas认为空字符串是有内容的
np.inf(无穷大) 是有效数值

1.3 为什么不能用 == 判断空值?

在Python中,np.nan 有一个特殊性质------它不等于自身

python 复制代码
print(np.nan == np.nan)   # False
print(np.nan is np.nan)   # False

因此,判断空值必须使用Pandas提供的专用方法,而不是 == 运算符。

二、如何检测缺失值

2.1 核心检测方法

Pandas提供了两套功能完全等价的方法:

方法 含义 返回值
isna() / isnull() 检测是否为缺失值 True表示缺失
notna() / notnull() 检测是否不是缺失值 True表示非缺失
python 复制代码
import pandas as pd

# 单个值测试
print(pd.isna(None))        # True
print(pd.isna(np.nan))      # True
print(pd.isna(pd.NA))       # True
print(pd.isna('null'))      # False
print(pd.isna(''))          # False

2.2 实战:检测DataFrame中的缺失值

python 复制代码
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)
#    姓名   年龄   城市    收入
# 0  张三  25.0   北京  5000.0
# 1  李四   NaN   上海  6000.0
# 2  王五  30.0  NaN   5500.0
# 3  赵六  28.0   广州     NaN

# 检测缺失值
print(df.isnull())
#      姓名     年龄     城市    收入
# 0  False  False  False  False
# 1  False   True  False  False
# 2  False  False   True  False
# 3  False  False  False   True

2.3 快速统计缺失情况

python 复制代码
# 每列缺失数量
print(df.isnull().sum())
# 姓名    0
# 年龄    1
# 城市    1
# 收入    1
# dtype: int64

# 每列缺失比例(%)
print(df.isnull().sum() / len(df) * 100)
# 姓名     0.0
# 年龄    25.0
# 城市    25.0
# 收入    25.0
# dtype: float64

# 总缺失数量
print(df.isnull().sum().sum())  # 3

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(默认) 自动将常见缺失值(空值、NULLnullNoneNAnan等)填充为 NaN
False 关闭自动识别,所有值保持原始内容,不填充 NaN

2. na_values 参数

  • 作用 :手动指定哪些值应被识别为缺失值并填充为 NaN
  • 使用前提 :通常与 keep_default_na=False 配合使用
  • 注意 :设置 na_values 后,只有列表中指定的值会被替换为 NaN

方式一:默认读取(自动填充)

python 复制代码
import pandas as pd
import numpy as np

# keep_default_na=True(默认),自动填充常见缺失值为 NaN
df = pd.read_csv('data/survey_visited.csv')

方式二:关闭自动填充

python 复制代码
import pandas as pd
import numpy as np

# keep_default_na=False,不填充任何值,保留原始数据
df = pd.read_csv('data/survey_visited.csv', keep_default_na=False)

方式三:关闭自动填充 + 自定义缺失值

python 复制代码
import pandas as pd
import numpy as np

# keep_default_na=False 关闭自动填充 + na_values 手动指定哪些值算缺失
df = pd.read_csv('data/survey_visited.csv', keep_default_na=False, na_values=[None, np.nan, pd.NA])

四、Pandas删除缺失值详解

python 复制代码
# 1. 导包
import pandas as pd

# 2. 加载数据
train = pd.read_csv('data/titanic_train.csv')

# 3. 了解数据
train.info()
print(train.shape)  # (892, 12)

# ============================================================
# 4. 删除缺失值
# ============================================================

# 重要概念:
# dropna(inplace=True)  → 直接在原数据上修改
# dropna()              → 默认复制一份,原数据不变,返回新DataFrame

方式一:dropna() 默认删除任意缺失行

python 复制代码
# 4.1 dropna() 默认 how='any',删除包含任何缺失值的行
new_train1 = train.dropna()  # 默认 how='any'
print(new_train1.shape)  # (183, 12)

# 示例数据理解:
# 原始数据 (892行, 12列)
# Age列缺失 178行,Cabin列缺失 688行,Embarked列缺失 3行
# dropna() 删除所有包含缺失值的行后,只剩下 183行
# 这183行是 Age、Cabin、Embarked 三列都没有缺失的乘客

方式二:dropna(how='all') 删除全为空的行

python 复制代码
# 4.2 dropna(how='all') 只有整行都是缺失值才删除
new_train2 = train.dropna(how='all')
print(new_train2.shape)  # (891, 12)

# 示例数据理解:
# 原始数据有 892行,删除了 1行(该行所有列都是NaN)
# 因为泰坦尼克数据中,只有极少数行可能全为空
# 所以结果几乎没变化:892 → 891

方式三:dropna(subset=[列名]) 指定列检测缺失

python 复制代码
# 4.3 dropna(subset=[列名]) 只检测指定列是否有缺失
# 如果指定列有缺失,就删除该行

# 示例1:只检测 Age 列
new_train3 = train.dropna(subset=['Age'])
print(new_train3.shape)  # (714, 12)

# 示例数据理解:
# Age列原来有 178个缺失值 → 删除178行
# 892 - 178 = 714  ✓

# 示例2:同时检测多列(Age 和 Cabin)
new_train3 = train.dropna(subset=['Age', 'Cabin'])
print(new_train3.shape)  # (183, 12)

# 示例数据理解:
# 删除 Age 或 Cabin 任一列有缺失的行
# 结果和 dropna() 一样,因为 Cabin 缺失最多
# 892 - 688 = 183(Cabin非空的行数)✓

方式四:drop() 直接删除指定列

python 复制代码
# 4.4 drop() 直接删除指定的列(不是删除行)
# axis=1 表示按列删除

# 删除 Cabin 列(缺失比例高达77%)
new_train4 = train.drop('Cabin', axis=1)
print(new_train4.shape)  # (892, 11)

# 示例数据理解:
# 原始数据有 12列,删除了 'Cabin' 这一列
# 892行不变,列数 12 → 11

# 也可以删除多列
new_train5 = train.drop(['Cabin', 'Age'], axis=1)
print(new_train5.shape)  # (892, 10)

五种方法对比总结

方法 代码 删除对象 结果 (行, 列)
原始数据 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填充缺失值详解(带示例数据)

python 复制代码
# 1. 导包
import pandas as pd

# 忽略所有警告
import warnings
warnings.filterwarnings('ignore')

# 2. 读取文件
df = pd.read_csv('data/students.csv')
print(df)

# ============================================================
# 3. fillna() 填充数据
# ============================================================

# 假设 students.csv 数据如下(示例):
#      name    age  score   gender
# 0    张三   18.0   90.0     男
# 1    李四    NaN   85.0     女
# 2    王五   20.0    NaN     男
# 3    赵六   22.0   88.0    NaN
# 4    孙七    NaN   92.0     男
# 5    周八   19.0    NaN     女

方式一:直接填充固定值(0)

python 复制代码
# 方式1:直接填充0
new_df1 = df.fillna(0)
print(new_df1)

# 示例输出:
#      name    age  score   gender
# 0    张三   18.0   90.0     男
# 1    李四    0.0   85.0     女    ← age缺失,填充为0
# 2    王五   20.0    0.0     男    ← score缺失,填充为0
# 3    赵六   22.0   88.0      0    ← gender缺失,填充为0
# 4    孙七    0.0   92.0     男    ← age缺失,填充为0
# 5    周八   19.0    0.0     女    ← score缺失,填充为0

# ⚠️ 缺点:age填充0会导致年龄异常(成年学生变成0岁)

方式二:填充平均值

python 复制代码
# 方式2:填充平均年龄
new_df2 = df.fillna(df['age'].mean())
print(new_df2)

# 示例输出(假设 age 平均值为 19.8):
#      name    age  score   gender
# 0    张三   18.0   90.0     男
# 1    李四   19.8   85.0     女    ← age缺失,填充为平均值19.8
# 2    王五   20.0    NaN     男
# 3    赵六   22.0   88.0    NaN
# 4    孙七   19.8   92.0     男    ← age缺失,填充为平均值19.8
# 5    周八   19.0    NaN     女

# ⚠️ 注意:只有age列被填充了,其他列(score、gender)的缺失值没有被填充

方式三:前向填充(ffill)

python 复制代码
# 方式3:填充当前缺失值的前一个数据(向前填充)
new_df3 = df.fillna(method='ffill')
print(new_df3)

# 示例输出:
#      name    age  score   gender
# 0    张三   18.0   90.0     男
# 1    李四   18.0   85.0     女    ← age用上一行的18.0填充
# 2    王五   20.0   85.0     男    ← score用上一行的85.0填充
# 3    赵六   22.0   88.0    NaN    ← gender用上一行的"男"填充
# 4    孙七   22.0   92.0     男    ← age用上一行的22.0填充
# 5    周八   19.0   92.0     女    ← score用上一行的92.0填充

# ✅ 适用场景:时间序列数据,用前一时刻的值填充
# ⚠️ 注意:第3行gender原本是NaN,前向填充时用了第2行的"男"

方式四:后向填充(bfill)

python 复制代码
# 方式4:填充当前缺失值的后一个数据(向后填充)
new_df4 = df.fillna(method='bfill')
print(new_df4)

# 示例输出:
#      name    age  score   gender
# 0    张三   18.0   90.0     男
# 1    李四   20.0   85.0     女    ← age用下一行的20.0填充
# 2    王五   20.0   88.0     男    ← score用下一行的88.0填充
# 3    赵六   22.0   88.0    NaN    ← gender用下一行的"男"填充
# 4    孙七   19.0   92.0     男    ← age用下一行的19.0填充
# 5    周八   19.0    NaN     女    ← score后面没有数据,无法填充(仍然NaN)

# ✅ 适用场景:时间序列数据,用后一时刻的值填充
# ⚠️ 注意:最后一行score缺失,后面没有数据,填充失败

四种方式对比总结

方式 代码 填充逻辑 适用场景
固定值 fillna(0) 所有缺失填同一个值 临时占位
平均值 fillna(df['age'].mean()) 用该列平均值填充 数值列,无明显异常值
前向填充 fillna(method='ffill') 用上一行同列的值 时间序列,数据渐变
后向填充 fillna(method='bfill') 用下一行同列的值 时间序列,数据渐变

常用填充方式汇总

python 复制代码
# 1. 填充固定值
df.fillna(0)

# 2. 填充指定值
df.fillna('未知')

# 3. 填充平均值(数值列)
df['age'] = df['age'].fillna(df['age'].mean())

# 4. 填充中位数(更稳健,不受异常值影响)
df['age'] = df['age'].fillna(df['age'].median())

# 5. 填充众数(分类列)
df['gender'] = df['gender'].fillna(df['gender'].mode()[0])

# 6. 前向填充
df.fillna(method='ffill')

# 7. 后向填充
df.fillna(method='bfill')

# 8. 不同列填不同值
df.fillna({
    'age': df['age'].median(),
    'score': 0,
    'gender': '未知'
})

# 9. 限制连续填充数量
df.fillna(method='ffill', limit=1)  # 连续缺失只填1个

特殊知识点

1. 为什么 df.fillna(df['age'].mean()) 只填充了 age 列?

因为只传入了 df['age'].mean() 这一个值,Pandas 会将这个值应用到所有列的缺失值上。

  • age 列是数值型,填充平均值 → 合理
  • score 列也是数值型,填充 age 的平均值 → 错误!应该用 score 自己的平均值
  • gender 列是字符串,填充数值 → 无法填充

正确做法:对不同列使用不同的填充值

python 复制代码
# 分别填充
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. ffillbfill 的局限性

  • 如果开头就有缺失,ffill 无法填充(前面没有数据)
  • 如果结尾就有缺失,bfill 无法填充(后面没有数据)
  • 连续多个缺失时,ffill 会用同一个值填充所有缺失
python 复制代码
# 示例:连续缺失
# 原数据:10, NaN, NaN, 40
# ffill后:10, 10, 10, 40  ← 两个NaN都被填充为10
# bfill后:10, 40, 40, 40  ← 两个NaN都被填充为40

六、Pandas分组聚合(GroupBy)详解(带示例数据)

python 复制代码
# 1. 导包
import numpy as np
import pandas as pd

# 2. 读取文件
# csv: 具有固定分隔符的结构化数据,都是csv格式,不用关注后缀名是不是.csv
df = pd.read_csv('data/gapminder.txt', sep='\t')
print(df.shape)  # (1704, 6)

数据简介

gapminder.txt 数据包含以下列:

  • country:国家
  • year:年份
  • pop:人口
  • continent:大洲
  • lifeExp:平均寿命
  • gdpPercap:人均GDP

需求1:求每个年份的平均寿命

四种写法对比

python 复制代码
# 写法1:df.列名(返回Series)
print(df.groupby('year').lifeExp.mean())
# 示例输出:
# year
# 1952    49.057620
# 1957    51.507401
# 1962    53.609247
# 1967    55.678290
# 1972    57.647386
# 1977    59.570157
# 1982    61.533197
# 1987    63.212613
# 1992    64.160338
# 1997    65.014676
# 2002    65.694923
# 2007    67.007423
# Name: lifeExp, dtype: float64

# 写法2:df['列名'](返回Series)
print(df.groupby('year')['lifeExp'].mean())
# 输出与写法1相同

# 写法3:df[['列名']](返回DataFrame)
print(df.groupby('year')[['lifeExp']].mean())
# 示例输出:
#              lifeExp
# year                
# 1952      49.057620
# 1957      51.507401
# 1962      53.609247
# 1967      55.678290
# 1972      57.647386
# 1977      59.570157
# 1982      61.533197
# 1987      63.212613
# 1992      64.160338
# 1997      65.014676
# 2002      65.694923
# 2007      67.007423

# 写法4:agg()方法(推荐,可扩展)
print(df.groupby('year').agg({"lifeExp": 'mean'}))
# 输出与写法3相同

print(df.groupby('year').agg({"lifeExp": np.mean}))
# 输出与写法3相同

四种写法对比总结

写法 代码 返回类型 特点
写法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

python 复制代码
# 需求2:求每个年份,每个大洲的平均寿命和最高GDP
# 伪SQL:SELECT AVG(lifeExp), MAX(gdpPercap) FROM 表名 GROUP BY year, continent

print(df.groupby(['year', 'continent']).agg({"lifeExp": 'mean', "gdpPercap": 'max'}))
# 或
print(df.groupby(['year', 'continent']).agg({"lifeExp": np.mean, "gdpPercap": np.max}))

# 示例输出(部分):
#                         lifeExp    gdpPercap
# year continent                              
# 1952 Africa           39.135500   4725.00000
#      Americas         53.279840  12000.00000
#      Asia             46.317394  10000.00000
#      Europe           64.408500  15000.00000
#      Oceania          69.255000  12000.00000
# 1957 Africa           41.266346   5489.00000
#      Americas         55.960280  13000.00000
# ...(省略)

常用聚合函数

函数 作用 示例
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'

多个聚合函数同时使用

python 复制代码
# 对同一列使用多个聚合函数
# 球每个年份的平均寿命的平均值、最大值、最小值、标准差
print(df.groupby('year')['lifeExp'].agg(['mean', 'max', 'min', 'std']))

# 示例输出(部分):
#           mean   max    min       std
# year                                  
# 1952  49.057620  72.0  30.0  10.0000
# 1957  51.507401  73.0  31.0  10.0000
# 1962  53.609247  74.0  32.0  10.0000
# ...

# 对不同列使用不同聚合函数
print(df.groupby('year').agg({
    'lifeExp': ['mean', 'max'],
    'gdpPercap': ['mean', 'max'],
    'pop': 'sum'
}))

关键知识点总结

1. groupby() 返回类型

python 复制代码
# 分组后选择列的方式决定返回类型
df.groupby('year')['lifeExp']      # SeriesGroupBy
df.groupby('year')[['lifeExp']]    # DataFrameGroupBy
df.groupby('year')[['lifeExp', 'gdpPercap']]  # DataFrameGroupBy

2. 点语法 df.列名 和括号语法 df['列名']

语法 适用场景
df.lifeExp 列名无空格、无特殊字符
df['lifeExp'] 列名有空格、有特殊字符,或需要动态列名
df[['lifeExp']] 需要返回DataFrame(单列也返回DataFrame)

3. agg() 是万能方法

python 复制代码
# 单个字段单个聚合
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()

python 复制代码
# 1. 导包
import numpy as np
import pandas as pd

# 2. 读取文件
df = pd.read_csv('data/gapminder.txt', sep='\t')
print(df.shape)  # (1704, 6)

# 3. 分析数据(分组聚合操作)
# 需求:求每个年份的平均寿命

# 方式1:groupby() + agg()
print(df.groupby('year').agg({"lifeExp": 'mean'}))

# 方式2:pivot_table() 透视表
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() 写法

python 复制代码
df.groupby('year').agg({"lifeExp": 'mean'})
#    │              │    │        │
#    │              │    │        └── 聚合函数
#    │              │    └─────────── 聚合字段
#    │              └──────────────── 聚合方法
#    └─────────────────────────────── 分组字段

pivot_table() 写法

python 复制代码
df.pivot_table(index=['year'], values='lifeExp', aggfunc='mean')
#    │              │             │              │
#    │              │             │              └── 聚合函数
#    │              │             └───────────────── 聚合字段
#    │              └─────────────────────────────── 分组字段
#    └────────────────────────────────────────────── 透视表方法

两种方式等价关系

python 复制代码
# 两者完全等价,输出结果相同

# 方式1
df.groupby('year').agg({"lifeExp": 'mean'})

# 方式2
df.pivot_table(index=['year'], values='lifeExp', aggfunc='mean')

多字段扩展写法

多分组字段

python 复制代码
# groupby 写法
df.groupby(['year', 'continent']).agg({"lifeExp": 'mean'})

# pivot_table 写法
df.pivot_table(index=['year', 'continent'], values='lifeExp', aggfunc='mean')

多聚合字段

python 复制代码
# groupby 写法
df.groupby('year').agg({
    "lifeExp": 'mean',
    "gdpPercap": 'max'
})

# pivot_table 写法
df.pivot_table(
    index='year',
    values=['lifeExp', 'gdpPercap'],
    aggfunc={'lifeExp': 'mean', 'gdpPercap': 'max'}
)

多聚合函数

python 复制代码
# groupby 写法
df.groupby('year').agg({"lifeExp": ['mean', 'max', 'min']})

# pivot_table 写法
df.pivot_table(
    index='year',
    values='lifeExp',
    aggfunc=['mean', 'max', 'min']
)

选择建议

场景 推荐方式
简单分组聚合 groupby() + agg()
习惯Excel透视表操作 pivot_table()
需要将字段值转为列名 pivot_table()(加 columns 参数)
追求性能 groupby() 通常更快
相关推荐
维克兜率天19 分钟前
【维克】动量指标家族:RSI、ROC、CCI、Momentum全面解析
python·算法
XZ-0700011 小时前
week2-1-可视化
开发语言·python
Java后端的Ai之路1 小时前
14、Python - 责任链模式
服务器·开发语言·人工智能·python·责任链模式
张人玉1 小时前
基于 Python 机器学习 与 PyQt5 桌面框架开发的可视化分析系统——基于大数据的网上购物消费行为分析可视化大屏
python·qt·机器学习·echarts·three.js
一位正在转型AI全栈的前端工程师2 小时前
AI 全栈学习之旅 -Week 5:RAG 知识库问答系统:从零到生产级部署的全栈实践总结
前端·python
2601_962381862 小时前
Python办公联动:Excel数据一键自动生成PPT图表
python·数据分析·自动化·excel·ppt
东莞市永盛电气2 小时前
三相208V变380V变压器,出海工业设备供电适配方案
python·单片机·嵌入式硬件
未若君雅裁3 小时前
上下文压缩双刃剑-Summarization与ContextEditing中间件实战
python·中间件·langchain
IT毕设实战小研3 小时前
电影数据可视化推荐系统
大数据·后端·爬虫·python·算法·信息可视化·课程设计