Pandas数据类型
pandas的数据类型几乎和python的数据类型一致,不一致的有:
- python的string 相当于 pandas的object,并且存放任意 Python 对象:str、list、dict、自定义类、混合类型。
- python的None 相当于pandas的nan、NAN、NaN
使用空值:np.nan - 分类类型 category(Pandas 独有),Python 无对应类型。
适用:一列重复值非常多 - 时间类型:datetime64ns
可以直接 .dt.year / .dt.month / .dt.day 批量提取时间,原生 Python 做不到批量列操作。 - 时间差类型:timedelta64ns
表示两个日期相减后的间隔(几天、几小时)
Pandas数据结构
Pandas 所有操作都基于两个结构:
Series(一维带索引数组)
DataFrame(二维带索引表格,最常用)
Series 一维序列
结构:索引(index) → 值(values)
Series 创建方式
py
# 1.列表生成,默认0,1,2...自动索引
s1 = pd.Series([1,2,3,])
print(s1)
#0 1
#1 2
#2 3
# 2.手动指定索引
s2 = pd.Series([10,20,30], index=["a","b","c"])
print(s2)
#a 10
#b 20
#c 30
# 3.字典生成:key=索引,value=数据
dic = {"语文":90, "数学":95, "英语":88}
s3 = pd.Series(dic)
print(s3)
#语文 90
#数学 95
#英语 88
# 4.元组创建
tup = (11,22,33)
s4 = pd.Series(tup)
print(s4)
#0 11
#1 22
#2 33
# 5.标量创建(单个值重复填充)
s5 = pd.Series(5, index=range(4))
print(s5)
#0 5
#1 5
#2 5
#3 5
Series 常用基础属性
py
s.index # 获取索引
s.values # 获取numpy数组格式的数据
s.dtype # 数据类型
s.name # Series名称(对应DataFrame列名)
取值方式
py
# 按索引名取
print(s2["a"]) # 10
# 按位置下标取
print(s2.iloc[1]) # 20
# 切片
print(s2[1:])
#b 20
#c 30
# 条件筛选
print(s2[s2 < 30])
#a 10
#b 20
DataFrame 二维表格
多行 + 多列,像 Excel 表格:
行索引:index(行标签)
列索引:columns(列名)
每一列本质就是一个 Series
DataFrame 创建方式
1.通过字典+列表构造
每个字典相当于一列数据
py
data = {
"姓名": ["张三", "李四", "王五"],
"年龄": [18, 19, 20],
"分数": [85, 92, 78]
}
df = pd.DataFrame(data)
print(df)
# 姓名 年龄 分数
#0 张三 18 85
#1 李四 19 92
#2 王五 20 78
2.通过列表+元组+指定列名(可以不指定,从0开始)
每个元组相当于一行数据
py
data = [("孙悟空",20,80),
("唐僧",22,100),
("八戒",19,70)]
df = pd.DataFrame(data,columns=["name","age","score"])
print(df)
# name age score
#0 孙悟空 20 80
#1 唐僧 22 100
#2 八戒 19 70
3.嵌套列表 + 指定列名
py
arr = [
[1, "A"],
[2, "B"],
[3, "C"]
]
df = pd.DataFrame(arr, columns=["id", "name"])
print(df)
# id name
#0 1 A
#1 2 B
#2 3 C
4.由多个 Series 合并
py
s_name = pd.Series(["小明","小红"])
s_age = pd.Series([17,18])
df = pd.DataFrame({
"name": s_name,
"age": s_age
})
print(df)
# name age
#0 小明 17
#1 小红 18
5.通过numpy的ndarray对象封装成pands的dataframe对象
py
data = np.arange(6).reshape(2,3)
df = pd.DataFrame(data)
print(df)
# 0 1 2
#0 0 1 2
#1 3 4 5
DataFrame 属性
py
df.index # 行索引
df.columns # 列名列表
df.values # 转为二维numpy数组
df.shape # (行数, 列数)
df.dtypes # 每一列数据类型
df.ndim # 维度:DataFrame是2,Series是1
df.T # 行列转置
基本函数
py
df.head(3) # 前3行,默认前5行
df.tail(2) # 最后2行
df.sample(n) # 随机抽取 n 行
df.info() # 数据概览、缺失值、类型
df.describe() # 数值列统计:均值、最值、分位数
# 修改列名
df.rename(columns={"姓名":"name", "语文":"chinese"}, inplace=True)
# 修改行索引
df.rename(index={0:"行0"})
# drop=True删掉旧索引
df.reset_index(drop=True)
# 创建新索引
df.set_index("姓名", inplace=True)
Series 和 DataFrame 互相转换
Series 转换 DataFrame
py
score = pd.Series([77,55],name="成绩")
data = score.to_frame()
print(data)
# 成绩
#0 77
#1 55
DataFrame 某一列 转换 Series
py
student = pd.DataFrame({"姓名":["张三","李四"],
"年龄":[18,19]})
print(student)
ser = student["姓名"]
print(type(ser)) #<class 'pandas.core.series.Series'>
一行转为 Series
py
row = student.iloc[0]
print(row)
#姓名 张三
#年龄 18
Pandas 索引操作
基本概念:
行索引 index:每一行的标识,默认 0,1,2,3...
列索引 columns:就是列名 "姓名","年龄","分数"
链式索引
分两步索引:先取列,再取行。
py
df = pd.DataFrame({
"姓名":["张三","李四","王五","赵六"],
"年龄":[18,19,18,20],
"分数":[85,92,78,96]
})
print( df['姓名'][0] )
#张三
loc /iloc 索引取值
核心两类索引器:
loc:按索引标签名操作
iloc:按数字位置下标操作
loc 标签索引(匹配 index 名字、columns 名字)
语法:df.loc行条件, 列名
py
df = pd.DataFrame({
"姓名":["张三","李四","王五","赵六"],
"年龄":[18,19,18,20],
"分数":[85,92,78,96]
})
print(df.loc[:,'姓名']) #:代表全部
#0 张三
#1 李四
#2 王五
#3 赵六
iloc 位置索引(只认 0 开始数字下标)
语法:df.iloc行号, 列号
py
df = pd.DataFrame({
"姓名":["张三","李四","王五","赵六"],
"年龄":[18,19,18,20],
"分数":[85,92,78,96]
})
print( df.iloc[0] ) #获取行
#姓名 张三
#年龄 18
#分数 85
df'列名' 和 df\['列名'] 的区别
df'列名':传入字符串 → 返回 Series(一维序列)
df\['列名']:传入包含字符串的列表 → 返回 DataFrame(二维表格)
py
df = pd.DataFrame({'姓名':['张三','李四','王五'], '年龄':[18,22,19]})
print(df['姓名'])
#返回 Series
#0 张三
#1 李四
#2 王五
print(df[['姓名']])
#返回 DataFrame
# 姓名
#0 张三
#1 李四
#2 王五
Pandas 赋值操作
以下例子操作数据:
py
import pandas as pd
df = pd.DataFrame({
"姓名":["张三","李四","王五"],
"年龄":[18,19,20],
"分数":[80,85,90]
})
inplace参数 原地修改说明
带 inplace=True 会直接修改原 DataFrame,不返回新数据;
不带则必须用变量接收
整列赋值
1.新增一列
py
# 整列统一值
df["班级"] = "一班"
# 由已有列计算生成
df["总分"] = df["分数"] + df["年龄"]
# 列表一一对应赋值(长度必须和行数一致)
df["等级"] = ["及格","良好","优秀"]
- 直接覆盖原有整列
py
df["分数"] = df["分数"] + 5
df["年龄"] = 22
单个单元格精准赋值
1.loc 按行索引 + 列名
py
# 行索引0,姓名列改为"小张"
df.loc[0, "姓名"] = "小张"
# 行索引1,分数改成100
df.loc[1, "分数"] = 100
2.iloc 按行列数字位置
py
# 第2行第3列(下标从0开始)
df.iloc[2, 2] = 99
批量行赋值
py
# 选中索引0、2两行,年龄全部设为21
df.loc[[0,2], "年龄"] = 21
# 切片连续行,从0到1
df.loc[0:1, "年龄"] = 88
Pandas 排序操作
两类排序:
按值排序:sort_values() 按某一列 / 多列的数据大小排序(最常用)
按索引排序:sort_index() 按照行 / 列索引标签排序
一下例子的操作数据:
py
# 排序操作
df = pd.DataFrame({
"姓名":["张三","李四","王五","赵六"],
"年龄":[18,19,18,20],
"分数":[85,92,86,96]
})
print(df)
# 姓名 年龄 分数
#0 张三 18 85
#1 李四 19 92
#2 王五 18 86
#3 赵六 20 96
sort_values () 按字段值排序
基础语法:
py
df.sort_values(
by="排序列名",
ascending=True, # True升序(默认从小到大);False降序
inplace=False, # 是否原地修改原df
ignore_index=False # 排序后是否重置行索引为0,1,2,3
)
升序操作
py
data = df.sort_values(by = '年龄') # 默认升序操作
print(data)
# 姓名 年龄 分数
#0 张三 18 85
#2 王五 18 86
#1 李四 19 92
#3 赵六 20 96
降序操作
py
data = df.sort_values(by = '年龄', ascending = False) # 降序操作
print(data)
# 姓名 年龄 分数
#3 赵六 20 96
#1 李四 19 92
#0 张三 18 85
#2 王五 18 86
多列组合排序
先按第一列排,第一列相同再按第二列:
py
data = df.sort_values(by = ['年龄','分数'], ascending = [False,False]) # 降序操作
print(data)
# 姓名 年龄 分数
#3 赵六 20 96
#1 李四 19 92
#2 王五 18 86
#0 张三 18 85
排序后重置索引
排序后原来行索引会乱,加上 ignore_index=True 直接生成新序号索引:
py
# 排序后重置索引
data = df.sort_values("分数", ascending=False, ignore_index=True)
print(data)
# 姓名 年龄 分数
#0 赵六 20 96
#1 李四 19 92
#2 王五 18 86
#3 张三 18 85
sort_index () 按索引排序
按行索引排序
py
# 行索引从小到大
data = df.sort_index()
print(data)
# 姓名 年龄 分数
#0 张三 18 85
#1 李四 19 92
#2 王五 18 86
#3 赵六 20 96
# 行索引倒序
data = df.sort_index(ascending=False)
print(data)
# 姓名 年龄 分数
#3 赵六 20 96
#2 王五 18 86
#1 李四 19 92
#0 张三 18 85
按列索引(列名)左右排序
axis=1 代表操作列
py
#按列索引(列名)左右排序
data = df.sort_index(axis=1)
print(data)
# 分数 姓名 年龄
#0 85 张三 18
#1 92 李四 19
#2 86 王五 18
#3 96 赵六 20
Series 排序
py
data = df['年龄']
data.sort_values()
print(data)
#0 18
#1 19
#2 18
#3 20
Pandas 基本运算
基础算术运算
py
s1 + 5
s1 - 2
s1 * 10
s1 / 2
s1 ** 2 # 平方
s1 // 2 # 整除
s1 % 2 # 取余
条件逻辑运算(& 与 | 或~非)
单条件
py
df = pd.DataFrame({
"语文": [80, 90, 70],
"数学": [85, 95, 75]
}, index=[0, 1, 2])
# 筛选语文大于80的数据
df[df.语文>70]
df[df["语文"]>70] #效果同上
df.query('语文>70') #使用query语法,效果同上
多条件
py
# 筛选语文大于70且数学大于85
df[(df.语文>70) & (df.数学 > 85)]
df.query('语文 > 70 & 数学 > 85') #效果同上
自定义函数(apply)
apply:按列 / 按行传入一组数据 → 跨行多列计算、整列聚合
底层就是各列或者各行分别传入函数,计算结果
py
# 找到最大年龄和最小年龄差
#写法1:func写法
def my_func(column):
return column.max()-column.min()
df[['年龄']].apply(my_func)
# 写法2:lambda写法,效果同上
df[['年龄']].apply(lambda c : c.max()-c.min())
Pandas 常用统计函数(Series & DataFrame)
| 函数 | 作用 |
|---|---|
| .sum() | 求和,自动跳过 NaN |
| .mean() | 算术平均值 |
| .median() | 中位数 |
| .max() | 最大值 |
| .min() | 最小值 |
| .count() | 非空元素个数(不计 NaN) |
| .size | 总行数(包含 NaN) |
| .std() | 标准差 |
| .var() | 方差 |
| .quantile(q) | 分位数,q=0.5就是中位数 |
| .mode() | 众数(出现次数最多的值) |
方差和标准差计算过程如下:

Pandas 文件读写
csv文件
写csv文件
py
# 写csv文件
# index=False 不把pandas自带行索引导出成一列(必加)
students = pd.DataFrame({"姓名":["张三","李四"],
"年龄":[18,19]})
students.to_csv("students.csv", index=False, encoding="utf-8-sig")
当前文件夹下生成students.csv文件:

写入参数
py
df.to_csv(
# 保存路径
path_or_buf="out.csv",
# 是否导出pandas自带行索引(99%场景必须False)
index=False,
# 列名是否写入第一行
header=True,
# 分隔符
sep=",",
# 编码(Excel打开不乱码首选 utf-8-sig)
encoding="utf-8-sig",
# 缺失值填充为指定字符串,默认空
na_rep="",
# 小数保留位数
float_format=None,
# 指定只导出哪些列
columns=None,
# 追加写入文件,而不是覆盖
mode="w",
# mode="a" 追加,注意第一次写入要写header,后续追加建议header=False
# 换行符兼容Windows
lineterminator=None,
# 是否压缩
compression="infer"
)
读取csv文件
py
# 读取csv文件
df = pd.read_csv("students.csv")
print(df)
# 姓名 年龄
#0 张三 18
#1 李四 19
读取参数
py
pd.read_csv(
# 1. 文件路径:相对路径/绝对路径/URL
filepath_or_buffer="data.csv",
# 分隔符
sep=",",
# 自动识别分隔符(适合杂乱txt/csv)
delimiter=None,
# 表头设置
header=0, # 第0行作为列名;header=None 无表头,列自动0,1,2
names=None, # 手动指定列名列表,搭配header=None使用
# 指定行索引列
index_col=None, # 填数字/列名,把某一列设为行索引
# 跳过行
skiprows=None, # 跳过前N行,或传入列表跳过指定行号
skipfooter=0, # 从文件末尾跳过几行
# 只读取指定列
usecols=None, # 列表:["姓名","分数"] 或 [0,2]
# 读取行数限制
nrows=None, # 只读取前n行,调试大文件必备
# 编码(中文乱码核心)
encoding="utf-8",
# 常用备选:gbk / gb2312 / utf-8-sig
# 缺失值标记(把指定文本识别为NaN)
na_values=["NA", "null", "空"],
# 布尔值识别
true_values=["是"],
false_values=["否"],
# 跳过空行
skip_blank_lines=True,
# 大文件分块读取(超大csv防止内存溢出)
chunksize=None,
# 解析日期列
parse_dates=False,
# parse_dates=["日期列"] 直接将该列转为datetime64
# 列数据类型强制指定
dtype=None,
# 示例 dtype={"手机号":str} 避免长数字科学计数法
# 不自动推断类型
converters=None, # 字典{列名:函数},自定义列读取处理
)
csv文件和tsv文件区别
本质分隔符区别:
CSV = Comma-Separated Values
逗号 , 作为字段分隔符
TSV = Tab-Separated Values
制表符 \t(键盘 Tab 键) 作为字段分隔符
MySQL 数据库
测试sql数据
sql
create database py_db;
use py_db;
create table users(
name char(20),
gender char(5),
age int
)
insert into users values('张三','男',18),('李四','女',22),('王五','男',20);

创建mysql连接
方案一:pymysql + pd.read_sql
py
# 1. 建立数据库连接
conn = ps.connect(
host="127.0.0.1", # 数据库地址
port=3306, # mysql端口默认3306
user="root", # 用户名
password="123", # 密码
database="py_db", # 数据库名
charset="utf8mb4" # 支持emoji中文
)
# 2. 直接写SQL语句读取整张表/查询结果
sql = "SELECT * FROM users;"
df = pd.read_sql(sql, con=conn)
print(df.head()) #输出
# name gender age
#0 张三 男 18
#1 李四 女 22
#2 王五 男 20
# 3. 用完关闭连接
conn.close()
方案二:SQLAlchemy 连接
py
from sqlalchemy import create_engine
import pandas as pd
# 连接格式:mysql+pymysql://用户名:密码@host:端口/数据库
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/py_db")
sql = "select * from users"
df = pd.read_sql(sql, engine)
print(df)
# name gender age
#0 张三 男 18
#1 李四 女 22
#2 王五 男 20
engine.dispose() #销毁连接池
写数据进MySQL
to_sql函数参数介绍
py
df.to_sql(
# 目标数据库中的表名,字符串;表不存在会自动创建,已存在行为由if_exists控制
name,
# 数据库连接对象,推荐传入SQLAlchemy的engine引擎,原生pymysql连接会提示警告
con,
# 当目标表已经存在时的处理策略
# fail:默认,直接抛出报错终止程序
# replace:删除原有整张表,新建表后插入当前DataFrame数据
# append:在原有表末尾追加插入数据,不会删除原有数据
if_exists="fail",
# 是否将DataFrame自带的行索引作为一列写入数据库表
# True:写入索引列;False:不写入索引,日常业务几乎都设为False
index=True,
# 仅index=True时生效,给索引那一列指定数据库字段名称;不指定则默认列名为index
index_label=None,
# 分批插入行数,一次性写入数据量过大时拆分批次提交,避免数据库锁表、内存溢出
# 例:chunksize=1000 代表每1000行执行一次插入SQL
chunksize=None,
# 手动指定每一列在数据库中对应的字段类型,字典格式 {列名: SQLAlchemy类型对象}
# 用来约束字符串长度、整型、文本大字段等,防止自动建表字段类型不符合需求
dtype=None,
# 插入SQL语句执行方式,优化大批量数据写入效率
# None:默认逐行单条insert插入,数据量大速度慢
# "multi":多条数据合并为单条insert批量插入,大幅提升写入速度
# 也可传入自定义函数,自定义插入逻辑(如主键重复更新ON DUPLICATE KEY)
method=None
)
写入数据库例子
py
# 将数据写入mysql数据库
#DataFrame数据
df = pd.DataFrame({"name":["孙悟空","唐僧","八戒"],"gender":["男","男","男"],"age":[18,22,19]})
df
# 1.创建数据库引擎
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/py_db")
# 2.写入MySQL
df.to_sql(
name="users", #要写入表名
con=engine, #数据库引擎
if_exists="append", #追加操作
index=False #
)
# 3.销毁连接
engine.dispose()
JSON 文件
read_json的参数
py
pd.read_json(
# 文件路径 / 网络url / json字符串
path_or_buf,
# 编码格式,中文一般 utf-8
encoding="utf-8",
# 是否按行解析JSON Lines格式,单行一条{}就设为True
lines=False,
# 数据组织格式,控制json结构映射成DataFrame方式
orient=None,
# 解析日期字段,自动转datetime64
convert_dates=True,
# 指定哪些列强制解析为日期
convert_axes=None,
# 对列名/索引做类型推断
dtype=True,
# 字典手动指定每列数据类型 {列名:类型}
dtype_backend=None,
# 只读取前n行,大文件调试用
nrows=None,
# 压缩格式:zip/gz/bz2等,自动识别压缩包
compression="infer",
# 读取后设置行索引列
index_col=None,
# 只加载指定列
usecols=None
)
to_json的参数
py
df.to_json(
# 输出文件路径;传None则返回JSON字符串,不写入文件
path_or_buf=None,
# 控制JSON整体组织结构,最核心参数
orient=None,
# 是否输出为JSON Lines格式(每行一条独立JSON对象,无外层[])
lines=False,
# 浮点数保留小数位数,None不限制
double_precision=10,
# 日期格式化字符串,默认ISO标准格式
date_format="epoch",
# 时间单位,epoch时间戳基准单位
date_unit="ms",
# 中文/特殊字符是否强制转为ASCII unicode编码(\uXXXX)
# False:中文正常显示;True:中文转unicode
force_ascii=True,
# 缩进空格数,None输出紧凑无换行;设为2格式化美观
indent=None,
# 最小浮点数精度
default_handler=None,
# 压缩方式:infer自动识别、gzip/bz2/zip等
compression="infer",
# 换行符
lineterminator="\n",
# 模式,w覆盖;a追加写入文件
mode="w"
)
重点参数详细说明
orient 组织结构:
| 取值 | JSON 结构说明 | 适用场景 |
|---|---|---|
| records | {"列1":值,"列2":值}, {...} 数组包裹每行数据 | 最常用,方便后续用read_json读取 |
| columns | 默认值 {"列名":{"行索引":数值}} | 以列为顶层 key |
| index | {"行索引":{"列名":数值}} | 以行索引为顶层 key |
| values | 纯二维数组 \[行1列1,行1列2,行2列1,行2列2] | 只导出纯数据矩阵 |
| table | 带表结构元信息,适合 Pandas 标准交互 | 跨程序数据交换 |
lines:
False(默认):整体一个 JSON 数组,外层 \[\]
True:必须搭配 orient="records",生成JSON Lines,每行一条{},无外层数组
force_ascii:
True:中文变成 "\u5f20\u4e09",通用性强但可读性差
False:直接输出原生中文,日常导出必开
读写例子:
py
# 读json
df = pd.read_json("data.json")
print(df)
#写json
df.to_json('data2.json',force_ascii=False, orient="records", lines=True)
DataFrame数据的增删改查操作
查操作(查询数据)
py
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五"],
"年龄": [18, 22, 19],
"分数": [80, 95, 72]
})
df["姓名"] #获取姓名列 series对象
df.姓名 #效果同上
df[["姓名"]] #获取姓名列 dataframe对象
pd.DataFrame(df.姓名) #效果同上,将series对象转换为dataframe对象
df[['姓名','年龄']] #获取多列 dataframe对象
py
# 根据索引值来获取数据
df[:2] #获取前
df[1:3:2] #从1到3,步长为2获取数据(包左不包右)
df[1::1] #从1开始,到末尾结束,步长为1
rank函数
rank参数:
py
ser.rank(
method="average", # 并列排名规则(核心)
ascending=True, # True升序(小在前);False降序(大在前)
pct=False, # 是否输出百分比排名
na_option="keep", # 缺失值处理方式
)
method参数:
average:同分取平均名次
min:同分取最前名(班级排名首选)
max:同分取最后名
first:按出场顺序强行分先后
dense:排名连续不空位
ascending参数:
True:升序排名,数值越小排名越靠前
False:降序排名,数值越大排名越靠前(分数排行必选)
例子:
py
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五"],
"年龄": [18, 22, 19],
"分数": [80, 95, 72]
})
df['分数排名'] = df['分数'].rank(method="min",ascending=False)

增操作
py
df = pd.DataFrame({
"name": ["张三", "李四", "王五"],
"age": [18, 22, 19],
"score": [88, 92, 75]
})
# 新增一列
df['money'] = 100 # 写法1:固定值
df['gender'] = ['男','女','男'] # 写法2:列表
# 写法3:通过已有的列(series)计算新的列
# print(str(df['age']))
df['num'] = df['name'] + "_0"
# 写法4:通过函数计算新值
df['func'] = (lambda : 5)()
# 通过assign()方式新增列
# 新增1列
data = df.assign(c1=11)
# 新增多列
data = df.assign(c2=22,c3=['xxx','yyy','zzz'])
删除与去重操作
drop()
默认删除指定索引值的行,如果有参数axis=1,则删除指定列名的列
返回删除后的数据,原数据没有被删除
py
df.drop(
labels, # 要删除的索引名/列名
axis=0, # axis=0 删除行;axis=1 删除列
index=None, # 直接指定行索引(推荐)
columns=None, # 直接指定列名(推荐)
inplace=False, # True 原地修改原df;False 返回新df,原数据不变
)
删除列:
py
# 删除单列
df1 = df.drop(columns="score")
# 删除多列
df1 = df.drop(columns=["age", "score"])
# 旧写法 axis=1
df1 = df.drop(["age", "score"], axis=1)
删除行:
py
# 删除索引0这一行
df1 = df.drop(index=0)
# 删除索引 0、3 两行
df1 = df.drop(index=[0, 3])
del()
永久删除原数据的数据
drop_duplicates ()
去重函数参数
py
df.drop_duplicates(
subset=None, # 指定依据哪些列判断重复;默认所有列
keep="first", # first:保留第一条;last:保留最后一条;False:全部删掉
inplace=False
)
整行完全重复才去重
py
df.drop_duplicates() #默认情况
只看 name + age 两列,相同就判定重复
py
df1 = df.drop_duplicates(subset=["name", "age"])
修改操作
py
import pandas as pd
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五"],
"年龄": [18, 22, 19],
"分数": [80, 95, 72]
})
# 1.某列赋值
df['分数'] = 100
# 2.修改某列的列值
df['年龄'] = [21,22,23]
# 3.replace 将性别这列的张三改为张四
df.replace({'姓名': {'张三': '张四'}}, inplace=True)
Pandas的高级处理
空值处理
例子数据:
py
# 构造带空值的数据
data = {
"姓名": ["张三", None, "李四", "王五", "赵六", "钱七", "孙八"],
"年龄": [22, 25, np.nan, 30, 28, None, 35],
"工资": [8500, 9200, 7800, np.nan, 10500, 9800, np.nan],
"部门": ["技术部", "市场部", "", "技术部", None, "人事部", "市场部"],
"入职日期": ["2022-03-15", "2021-11-22", np.nan, "2023-01-05", "2022-07-30", "2020-09-12", None]
}
df = pd.DataFrame(data)
判断空值
py
# 1. 逐个单元格判断是否为空
print(df.isna())
# 2. 统计每列缺失值数量
print("\n每列缺失值个数:")
print(df.isna().sum())
# 3. 统计每列缺失值占比
print("\n每列缺失占比:")
print(df.isna().mean().round(2))
# 4. 查看非空值数量
print("\n每列非空值数量:")
print(df.notna().sum())
isna() / isnull() 完全等价,检测空值
notna() / notnull() 等价,检测非空
Pandas 中None 会自动转为 NaN,但空字符串不会,必须手动替换:replace("", np.nan)
默认情况下:all() 和 any() 都会开启 skipna=True,自动跳过 NaN/None 缺失值,不参与布尔运算。
py
# all():所有元素都为真,才返回 True;只要有一个假 / 空字符,就返回 False
print(df.all())
# 姓名 True
# 年龄 True
# 工资 True
# 部门 False
# 入职日期 True
print(df.any()) #有一个元素为真直接返回True;全部为假就返回 False
# 姓名 True
# 年龄 True
# 工资 True
# 部门 True
# 入职日期 True
删除空值
py
# 默认删除【任意一列存在空值】的整行
df_drop_row = df.dropna()
# 只删除 全部列都是空 的行
df_drop_all = df.dropna(how="all")
# 删除存在空值的整列
df_drop_col = df.dropna(axis=1)
# 子集:只看姓名、年龄两列有空才删除
df_drop_sub = df.dropna(subset=["姓名", "年龄"])
填充空值
py
# 1. 全部空值统一填充固定值
df_fill_all = df.fillna("未知")
# 2. 不同列分别填充
df_fill_dict = df.fillna({
"姓名": "匿名用户",
"年龄": df["年龄"].mean(), # 年龄用均值填充
"工资": df["工资"].median(), # 工资用中位数填充
"部门": "未分配",
"入职日期": "2000-01-01"
})
如果有字符串为空值是不处理的,只处理NaN
替换值
替换指定缺失值(比如把空字符串改成 NaN)
py
df_replace = df.replace("", np.nan)
数据合并
例子数据:
py
# 表1:员工基础信息
df1 = pd.DataFrame({
"员工id": [1, 2, 3, 4, 5],
"姓名": ["张三", "李四", "王五", "赵六", "钱七"],
"部门": ["技术部", "市场部", "人事部", "技术部", "财务部"]
})
# 表2:员工薪资信息
df2 = pd.DataFrame({
"员工id": [2, 3, 4, 6, 7],
"工资": [9000, 8500, 12000, 7800, 11000],
"入职年限": [3, 2, 5, 1, 4]
})
# 表3:追加用数据(同结构)
df3 = pd.DataFrame({
"员工id": [6, 7],
"姓名": ["孙八", "周九"],
"部门": ["市场部", "人事部"]
})
pd.concat ()
常用参数:
py
pd.concat(
# objs:必填参数,接收列表,里面放需要拼接的DataFrame/Series对象
objs,
# axis:拼接轴向,默认0
# axis=0 → 纵向拼接(上下合并,堆叠行),按列名对齐
# axis=1 → 横向拼接(左右合并,新增列),按行索引对齐
axis=0,
# join:索引对齐方式,仅对无法匹配的索引生效,默认outer
# outer:并集,保留所有索引/列,缺失位置填充NaN
# inner:交集,只保留两张表共有的索引/列,丢弃不匹配部分
join='outer',
# ignore_index:是否重置行索引,默认False
# False:沿用原始各自索引,容易出现索引重复混乱
# True:拼接后行索引从0开始重新顺序编号
ignore_index=False,
# keys:给每组拼接的数据添加外层索引,用于标记数据来源
# 传入列表,长度和objs内表数量一致,后续可根据key筛选单张表数据
keys=None,
# levels:配合keys生成多层索引时,自定义层级数量,一般极少手动设置
levels=None,
# names:给keys生成的外层索引命名,搭配keys使用,给层级索引起字段名
names=None,
# verify_integrity:校验最终结果行索引是否存在重复,默认False
# True:如果拼接后索引有重复值,直接抛出异常报错,防止脏数据
verify_integrity=False,
# sort:拼接完成后是否自动对列名(axis=0)或行索引(axis=1)排序,默认False
sort=False,
# copy:是否强制拷贝数据,默认True
# True:创建新数据副本,不修改原数据内存
# False:尽可能复用原数据内存视图,节省内存,一般无需修改
copy=True
)
纵向合并数据
py
res_concat_row = pd.concat([df1, df3], axis='rows', ignore_index=True)
print(res_concat_row)
# 员工id 姓名 部门
# 0 1 张三 技术部
# 1 2 李四 市场部
# 2 3 王五 人事部
# 3 4 赵六 技术部
# 4 5 钱七 财务部
# 5 6 孙八 市场部
# 6 7 周九 人事部
横向合并数据
py
# 左右拼列,按行索引对齐,不是按某一列匹配
# 只看行号对齐,不会根据员工 id 匹配,索引错位就会拼错
new_data = pd.concat([df1, df2], axis='columns')
print(new_data)
# 员工id 姓名 部门 员工id 工资 入职年限
# 0 1 张三 技术部 2 9000 3
# 1 2 李四 市场部 3 8500 2
# 2 3 王五 人事部 4 12000 5
# 3 4 赵六 技术部 6 7800 1
# 4 5 钱七 财务部 7 11000 4
merge()
参数:
py
pd.merge(
left, # 左表 DataFrame
right, # 右表 DataFrame
on=None, # 两张表**列名完全相同**的关联键(主键),可以单列/多列列表
left_on=None, # 左表用作关联的列(左右键名字不一样时用)
right_on=None, # 右表用作关联的列
left_index=False, # 是否用左表索引作为关联键
right_index=False, # 是否用右表索引作为关联键
how='inner', # 连接方式:inner/left/right/outer/cross
sort=False, # 是否按关联键排序结果
suffixes=('_x', '_y'), # 左右表重名列后缀,左表_x,右表_y
copy=True, # 是否复制数据,默认True
indicator=False, # 新增一列标记数据来自左/右/两者
validate=None # 校验连接关系:1:1 / 1:m / m:1 防笛卡尔积
)
例子:
py
res = pd.merge(df1, df2, on="员工id")
print(res)
# 员工id 姓名 部门 工资 入职年限
# 0 2 李四 市场部 9000 3
# 1 3 王五 人事部 8500 2
# 2 4 赵六 技术部 12000 5
how="inner" 默认【内连接】
只保留两边都存在匹配键的数据,取交集。
分组聚合
语法格式:
py
# 分组
df.groupby(分组字段)
# 分组+聚合
df.groupby(分组字段)[需要计算的列].聚合函数()
单层分组
py
# 单层分组
df.groupby('部门')['工资'].max()
# 部门
# 人事部 8800
# 市场部 10500
# 技术部 9200
多层分组
py
# 多层分组
df.groupby(['部门','性别'])['工资'].max()
# 部门 性别
# 人事部 女 7200
# 男 8800
# 市场部 女 10500
# 男 9800
# 技术部 女 7800
# 男 9200
获取某个分组数据
get_group(分组值):从分组结果里,单独提取某一个指定分组的全部数据,返回该分组对应的子 DataFrame。
py
data = df.groupby('部门').get_group('技术部')
print(data)
# 部门 姓名 性别 工资 工龄
# 0 技术部 张三 男 8500 3
# 1 技术部 李四 男 9200 5
# 2 技术部 王五 女 7800 2
聚合函数
统计数值类
| 函数字符串 | 作用 |
|---|---|
| sum | 求和 |
| mean | 算术平均值 |
| median | 中位数(抗极端异常值) |
| min | 最小值 |
| max | 最大值 |
| std | 标准差 |
| var | 方差 |
| quantile | 分位数,默认 0.5 中位数 |
计数统计类
| 函数字符串 | 作用 |
|---|---|
| count | 非空元素个数(排除 NaN) |
| size | 分组总行数(包含 NaN 行,只看行数) |
| nunique | 去重后唯一值数量 |
文本 / 特征类
| 函数字符串 | 作用 |
|---|---|
| first | 分组内第一条数据 |
| last | 分组内最后一条数据 |
agg 函数
py
#写法 1:单列单聚合
# 按部门分组,工资求平均
df.groupby("部门")["工资"].mean()
#写法 2:单列多个聚合(列表传参)
df.groupby("部门")["工资"].agg(["sum", "mean", "max", "min"])
#写法 3:多列不同聚合(最常用,支持自定义列名)
df.groupby("部门").agg(
工资总和=("工资", "sum"),
平均工资=("工资", "mean"),
员工人数=("姓名", "count"),
工龄最大值=("工龄", "max")
)
nunique 去重计数
py
# 每个部门有多少个不同姓名的员工
df.groupby("部门")["姓名"].nunique()
transform 聚合
聚合结果行数和原表一致,常用于新增列
py
# 新增一列:每行对应所在部门平均工资
df["部门均值工资"] = df.groupby("部门")["工资"].transform("mean")
分组过滤
语法格式:
py
df.groupby(分组键).filter(func)
filter函数:
1.入参(传给函数的东西)
每次循环传入单个分组的子 DataFrame
2.出参(函数必须 return 的东西)
只能是单独 1 个布尔值
True:保留这一整个分组全部数据
False:直接删掉这一整个分组
py
# 只要平均工资大于9000的部门
data = df.groupby('部门').filter(lambda group: group['工资'].mean() > 9000 )
print(data)
# 部门 姓名 性别 工资 工龄
# 3 市场部 赵六 女 10500 4
# 4 市场部 钱七 男 9800 3
交叉表与透视表
数据:
py
df = pd.DataFrame({
"部门": ["技术部","技术部","技术部","市场部","市场部","人事部","人事部"],
"姓名": ["张三","李四","王五","赵六","钱七","孙八","周九"],
"性别": ["男","男","女","女","男","女","男"],
"工资": [8500,9200,7800,10500,9800,7200,8800]
})
交叉表
语法规则:
py
pd.crosstab(
index=df["行字段"],
columns=df["列字段"],
values=df["数值列"], # 可选,指定后必须搭配aggfunc
aggfunc="聚合函数",
margins=True,
normalize=True # 转为占比百分比
)
例子:
py
ct = pd.crosstab(
index=df["部门"],
columns=df["性别"]
)
print(ct)
# 性别 女 男
# 部门
# 人事部 1 1
# 市场部 1 1
# 技术部 1 2
透视表
语法规则:
py
pd.pivot_table(
data=df,
index="行字段", # 放在行上的分组维度
columns="列字段", # 放在列上的分组维度
values="要聚合的数值列",
aggfunc="聚合方式", # mean/sum/count/max/min 等
fill_value=0, # 空值填充
margins=True # 开启合计行/列
)
例子:
py
pt = pd.pivot_table(
df,
index="部门",
columns="性别",
values="工资",
aggfunc="mean"
)
print(pt)
# 性别 女 男
# 部门
# 人事部 7200.0 8800.0
# 市场部 10500.0 9800.0
# 技术部 7800.0 8850.0