Pandas 基础:核心对象与常见操作
Series:一维标签化数据
创建 Series
Series 是一维标签化数组,类似于电子表格的一列。可以从列表、字典或标量值创建:
import pandas as pd
import numpy as np
# 创建一个最简单的 Series:不指定索引时,会自动使用0,1,2,...作为索引
s1 = pd.Series([10, 20, 30, 40])
print(s1)
# 输出:
# 0 10
# 1 20
# 2 30
# 3 40
# dtype: int64
# 创建 Series 时指定索引
s2 = pd.Series([100, 98, 67, 23], index=['zs', 'ls', 'ww', 'sl'])
print(s2)
# 输出:
# zs 100
# ls 98
# ww 67
# sl 23
# dtype: int64
# 这是一个 Series:左边是索引(index),右边是对应的值,dtype 表示值的数据类型
# 通过字典创建 Series,键会成为索引,值成为数据
d = {'zs': 100, 'ls': 98, 'ww': 67, 'sl': 23}
s3 = pd.Series(d)
print(s3)
# 输出与上面 s2 相同(字典键的插入顺序即索引顺序)
从列表创建时,可以明确指定索引;不指定则用默认整数索引(0, 1, 2, …)。从字典创建时,字典的键成为索引,顺序为插入顺序。两种方式都生成带索引的一维数据序列。
对于重复值,传入标量和索引:
# 创建一个包含10个0.2的 Series
s4 = pd.Series(0.2, index=range(10))
print(s4.head(3)) # 先看头3行
# 输出:
# 0 0.2
# 1 0.2
# 2 0.2
# dtype: float64
s4 有 10 个元素,全部为 0.2,索引为 0–9。head(3) 方法显示前三行。
索引 Series
Series 兼具 Python 列表和字典的特性:既可按位置访问元素,也可按标签访问。这两套索引机制相互独立。
位置索引(0, 1, 2, …): 按元素的顺序位置访问。s[0] 取第一个元素;s[1:3] 返回切片。
标签索引: 按索引标签访问。若索引为 'zs',则 s['zs'] 取该元素。
对比示例:
s = pd.Series([100, 78, 98, 79], index=['zs', 'ls', 'ww', 'sl'])
# 这个 Series 的索引为 'zs','ls','ww','sl',对应的值如列表所给
# 按位置索引访问
print(s[3]) # 第4个元素,位置从0开始计数
print(s[:2]) # 前2个元素的切片(不包含位置2)
print(s[[0, 2]]) # 位置0和2的元素,返回一个新的 Series
# 按标签索引访问
print(s['sl']) # 索引标签为 'sl' 的元素
print(s['zs':'ww']) # 按标签切片,包含 'zs' 到 'ww'(注意包含末端)
print(s[['zs', 'ww', 'sl']])# 指定标签列表,返回多个元素
s[3] 按位置获取第 3 个位置的元素(值为 79);s['sl'] 按标签获取标记为 'sl' 的元素(也是 79)。
切片行为有差异:
s[:2]按位置切片,不包括位置 2(Python 标准做法)。s['zs':'ww']按标签切片,包括末端标签 'ww'(闭区间)。
这种不对称需要注意。
Series 不支持负索引如 s[-1]。用 s.iloc[-1] 替代。位置索引仅支持正向。
检查 Series 的结构:
print(s.index) # Index(['zs', 'ls', 'ww', 'sl'], dtype='object')
print(s.values) # [100 78 98 79],值本身是一个 numpy.ndarray
print(s.dtype) # int64,值的数据类型
print(s.shape) # (4,),Series 的形状,相当于长度为4的一维数组
print(s.size) # 4,元素个数
print(s.ndim) # 1,维度,对于Series永远是1
index 属性返回包含所有标签的 Index 对象。values 返回数据的 NumPy 数组。dtype 显示元素类型(此例为 int64;若有 NaN 或小数则为 float64)。shape、size 和 ndim 遵循 NumPy 约定。
DataFrame:二维标签化表格
DataFrame 是有行列标签的表格——可视为电子表格,或多个共享同一索引的 Series。每一列是 Series,列名是键。
例子:包含 Name、Age、Gender 列的 DataFrame 表示多个人的属性,分布在各行。从字典、列表嵌套或多个 Series 创建:
import pandas as pd
# 通过字典创建 DataFrame,每个键代表一列
data = {
'Name': ['Tom', 'Jerry', 'Jack', 'Rose'],
'Age': [18, 18, 20, 20]
}
df = pd.DataFrame(data)
print(df)
# 输出:
# Name Age
# 0 Tom 18
# 1 Jerry 18
# 2 Jack 20
# 3 Rose 20
该 DataFrame 有 Name 和 Age 两列,行索引为默认整数(0, 1, 2, 3)。打印时显示行列标签和单元格值。
可以指定行索引和列顺序:
# 用字典包含多个 Series 来创建 DataFrame,各 Series 按索引自动对齐
data2 = {
'Name': pd.Series(['Tom', 'Jerry', 'Jack', 'Rose'], index=['a', 'b', 'c', 'd']),
'Age': pd.Series([18, 18, 20], index=['a', 'b', 'c']), # 少了索引 'd'
'Gender': pd.Series(['M', 'M', 'F'], index=['a', 'c', 'd']) # 少了索引 'b'
}
df2 = pd.DataFrame(data2)
print(df2)
# 输出:
# Name Age Gender
# a Tom 18.0 M
# b Jerry 18.0 NaN
# c Jack 20.0 F
# d Rose NaN M
这里行索引设为 'a', 'b', 'c', 'd'。Name 列有所有四个值,但 Age 缺少 'd' 的值,Gender 缺少 'b' 的值。Pandas 按索引对齐行,缺失数据填为 NaN。索引 'b' 的 Gender 为 NaN;索引 'd' 的 Age 为 NaN。NaN(Not a Number)表示缺失值——是特殊的浮点类型。
注意: 某列出现 NaN 时,Pandas 会将该列上升为浮点类型,因为 NaN 是浮点值。例如,Age 列虽初为整数,但出现 NaN 后变为 float64。
DataFrame 属性与操作
DataFrame 提供属性和方法来检查结构:
df.index– 行索引(Index 对象)。df.columns– 列名(Index 对象)。df.values– 单元格值作为二维 NumPy 数组。df.shape– 元组 (行数, 列数)。df.dtypes– 各列的数据类型。df.size– 总元素数(行数 × 列数)。df.empty– 布尔值:DataFrame 是否为空?df.head(n)– 前 n 行(默认 n=5)。df.tail(n)– 最后 n 行。df.describe()– 数值列的统计摘要。df.T– 转置后的 DataFrame(行列互换)。
检查我们的 DataFrame:
print(df2.index) # Index(['a', 'b', 'c', 'd'], dtype='object')
print(df2.columns) # Index(['Name', 'Age', 'Gender'], dtype='object')
print(df2.shape) # (4, 3) -> 4 行 3 列
print(df2.dtypes)
# Name object
# Age float64
# Gender object
# dtype: object
print(df2.head(2))
# Name Age Gender
# a Tom 18.0 M
# b Jerry 18.0 NaN
print(df2.describe())
# Age
# count 3.000000
# mean 18.666667
# std 1.154701
# min 18.000000
# 25% 18.000000
# 50% 18.000000
# 75% 19.000000
# max 20.000000
index 和 columns 列出行列标签。shape 显示 4 行 3 列。dtypes 表明 Name 和 Gender 为 object(字符串),Age 为 float64。head(2) 显示前两行;describe() 汇总 Age 列(非数值列被跳过)。
列操作
DataFrame 的列类似于字典的键:每列是 Series。用这些模式来读取、添加、修改或删除列。
读取列
通过列名访问列,使用 df['列名'],返回 Series。获取多列,传入列表:df[['Name', 'Age']] 返回新 DataFrame。不要用切片如 df['col1':'col3']——方括号默认选列,切片默认选行(这种设计保持语义一致)。
示例:
df = df2 # 继续使用前面的 df2: 有 Name, Age, Gender 三列,索引 a,b,c,d
# 访问单列数据
name_col = df['Name']
print(name_col) # 获取 Name 列(类型为 Series)
# 访问多列数据
subset = df[['Name', 'Age']]
print(subset) # 获取 Name 和 Age 两列,返回 DataFrame
# 利用 df.columns 得到列名列表,比如去掉最后一列:
print(df[df.columns[:-1]])# 获取除最后一列外的所有列
name_col 是 Series,包含每个人的名字。subset 是 DataFrame,含 Name 和 Age。最后一行用 df.columns[:-1] 获取除最后列外的所有列名,演示另一种检索方法。
Pandas 允许属性访问有效的列名:df.Name 等同于 df['Name']。但若列名含空格、特殊字符或与 DataFrame 方法冲突,则失败。优先用 df[...] 以保证清晰。
注意: 若列名有空格(如 "Total Sales"),必须用
df['Total Sales'],不能用df.Total Sales。同样,列名含特殊字符或与方法名冲突时,只能用df['列名']。要启用点访问,重命名列:df.columns = df.columns.str.replace(' ', '_')。
添加列
通过赋值添加列:df['新列名'] = ...。右侧可以是:
- 标量:整列填充该值。
- 列表或 ndarray:长度需与 DataFrame 行数相同;值按顺序填充。
- Series:若其索引与 DataFrame 索引对齐,按标签匹配;不匹配的位置变为 NaN。
用 pd.concat 同时添加多列:
# 原始 DataFrame 回顾:
print(df)
# Name Age Gender
# a Tom 18.0 M
# b Jerry 18.0 NaN
# c Jack 20.0 F
# d Rose NaN M
# 1. 直接赋值列表,添加一列
df['Math'] = [100, 100, 100, 100] # 所有行的 Math 值都是100
print(df)
# 新增了 Math 列:
# Name Age Gender Math
# a Tom 18.0 M 100
# b Jerry 18.0 NaN 100
# c Jack 20.0 F 100
# d Rose NaN M 100
# 2. 赋值一个 Series,按索引对齐添加新列
df['English'] = pd.Series([95, 96, 97], index=['a', 'b', 'd'])
print(df)
# 新增 English 列(对齐索引,有缺失):
# Name Age Gender Math English
# a Tom 18.0 M 100 95.0
# b Jerry 18.0 NaN 100 96.0
# c Jack 20.0 F 100 NaN # c 没有 English 值,NaN
# d Rose NaN M 100 97.0
# 3. 用 concat 横向拼接添加多列
new_cols = pd.DataFrame({
'Chinese': [50, 50, 50, 50],
'P.E.': [60, 60, 60, 60]
}, index=['a', 'b', 'c', 'd'])
df = pd.concat([df, new_cols], axis=1)
print(df)
# 新增了 Chinese 和 P.E. 两列:
# Name Age Gender Math English Chinese P.E.
# a Tom 18.0 M 100 95.0 50 60
# b Jerry 18.0 NaN 100 96.0 50 60
# c Jack 20.0 F 100 NaN 50 60
# d Rose NaN M 100 97.0 50 60
添加 Math 列(全为 0.9)、English 列('c' 处为 NaN)和两个通过 concat 添加的列。注意 'P.E.' 含点,只能用 df['P.E.'] 访问,不能用 df.P.E.。
修改列
修改列与添加列相同——对现有列名赋值以覆盖它:
df['Chinese'] = [100, 100, 100, 100]
print(df)
# Chinese 列的值已更新为100:
# Name Age Gender Math English Chinese P.E.
# a Tom 18.0 M 100 95.0 100 60
# b Jerry 18.0 NaN 100 96.0 100 60
# c Jack 20.0 F 100 NaN 100 60
# d Rose NaN M 100 97.0 100 60
这将 Chinese 列替换为全部 100。类似地,用 df.loc 修改特定单元格(在行操作中介绍)。
删除列
用以下方式移除列:
del df['列名'](类似删除字典键)。df.pop('列名')(返回被删列的 Series)。df.drop(columns=[...])或df.drop([...], axis=1)(默认返回新 DataFrame;用inplace=True原地修改)。
示例:
# 1. del 关键字删除列
del df['P.E.']
print(df.columns) # Index(['Name', 'Age', 'Gender', 'Math', 'English', 'Chinese'], dtype='object')
# 2. pop 方法删除列
df.pop('Chinese')
print(df.columns) # Index(['Name', 'Age', 'Gender', 'Math', 'English'], dtype='object')
# 3. drop 删除多列
df.drop(['Math', 'English'], axis=1, inplace=True)
print(df.columns) # Index(['Name', 'Age', 'Gender'], dtype='object')
print(df)
# Name Age Gender
# a Tom 18.0 M
# b Jerry 18.0 NaN
# c Jack 20.0 F
# d Rose NaN M
删除后,DataFrame 仅保留 Name、Age、Gender 和原始行索引。使用 drop 时,列删除记得加 axis=1。若不用 inplace,需赋值回去:df = df.drop([...], axis=1)。
行操作
行通过索引访问——按标签或按整数位置。loc(标签)和 iloc(位置)都很关键。
读取行
df[...] 不能直接按标签选行。df['a'] 试图获取名为 'a' 的列,不是行。改用:
df.loc[标签]– 按行标签选择。df.iloc[位置]– 按整数位置选择。
使用 loc:
# 使用 loc 按标签索引行
print(df.loc['a']) # 索引为 'a' 的整行数据,返回一个 Series
print(df.loc['b':'c']) # 从索引 'b' 到 'c' 的行(包括 'c' 行)
print(df.loc[['a', 'c'], ['Name', 'Gender']])
# 上面一行选取索引 a 和 c 的两行,且只取其中的 Name 和 Gender 两列
使用 iloc:
# 使用 iloc 按位置索引行
print(df.iloc[0]) # 第 0 号位置的行(即索引 'a' 这一行)
print(df.iloc[1:3]) # 位置 1 到 2 的行(不包括位置3),对应索引 b, c 两行
print(df.iloc[[0, 2], [0, 2]])
# 上面一行选取位置 0 和 2 的两行,以及位置 0 和 2 的两列,实现行列交叉选取
loc 和 iloc 都接受单个索引、切片和列表。关键差异:
loc切片两端都闭:df.loc['b':'c']包括 'b' 和 'c'。iloc切片遵循 Python 规则:左闭右开。df.iloc[1:3]包括位置 1 和 2,不含 3。iloc容许重复或乱序索引,按请求顺序返回行(用于复制数据时有用)。- 索引为数字时,明确用
loc或iloc以避免歧义。
添加行
Pandas 1.4+ 移除了 df.append()。用 pd.concat 代替。构造与原 DataFrame 列结构相同的新 DataFrame 或 Series,然后拼接:
# 构造一行新数据,注意列名需与原 DataFrame 一致
new_row = pd.DataFrame([['ZhangSan', 21, 'M']],
columns=['Name', 'Age', 'Gender'],
index=['e'])
# 用 concat 连接新行
df = pd.concat([df, new_row], axis=0)
print(df)
# 输出:
# Name Age Gender
# a Tom 18.0 M
# b Jerry 18.0 NaN
# c Jack 20.0 F
# d Rose NaN M
# e ZhangSan 21.0 M
这里添加标签为 'e' 的行。不指定 index=['e'] 时,新行默认用索引 0,可能与现有索引冲突。总是提供唯一索引。
修改行
用 loc 或 iloc 修改行:
df.loc['e'] = ['Peter', 22.0, 'M'] # 将索引 'e' 的整行重置为新的值
print(df.loc['e'])
# 输出:
# Name Peter
# Age 22.0
# Gender M
# Name: e, dtype: object
行 'e' 更新为 Name = Peter、Age = 22.0、Gender = 'M'。修改单个单元格时,用 df.at[行标签, 列名] = 值 或 df.iat[行位置, 列位置] = 值。
删除行
用 df.drop() 加 axis=0(或省略,默认就是 0):
# 删除索引为 'b' 和 'd' 的两行
df = df.drop(['b', 'd'], axis=0)
print(df)
# 输出:
# Name Age Gender
# a Tom 18.0 M
# c Jack 20.0 F
# e Peter 22.0 M
行 'b' 和 'd' 被删除。DataFrame 现在仅含行 'a'、'c'、'e'。若索引是默认整数,可直接传索引值(如 df.drop([0]) 删除索引 0)。自定义索引时用标签。按条件删除行,构造布尔掩码:df.drop(df[condition].index, axis=0)。
CSV 文件 I/O
多数数据存储在文件中。Pandas 读写许多格式;CSV(逗号分隔值)最常见。
读取 CSV 文件
使用 pd.read_csv(文件路径):
import pandas as pd
# 基本读取(假设 CSV 第一行为表头)
df = pd.read_csv('data.csv')
print(df.head(5)) # 查看前5行
若 CSV 有表头行,read_csv 自动将其作为列名。否则设 header=None 并通过 names 提供列名:
# 如果 CSV 没有表头行,指定 header=None 并提供列名
df = pd.read_csv('data_no_header.csv', header=None, names=['col1', 'col2', 'col3'])
常用参数:
sep– 分隔符(默认:逗号)。制表符用sep='\t'。header– 哪一行是表头(默认:0)。无表头时设为 None。names– 要应用的列名。index_col– 哪列成为行索引(如index_col='Date')。usecols– 要读的列列表(忽略其他;加快大文件读取)。dtype– 指定列数据类型(如dtype={'col': str})。nrows– 仅读前 n 行(预览大文件时有用)。encoding– 文件编码(默认:'utf-8';某些中文文件用 'gbk')。engine– 解析引擎。通常不需;不寻常的分隔符用engine='python'。
综合示例——读取无表头的股票数据,选择日期(作为索引)和 OHLC 列:
df = pd.read_csv('aapl.csv',
sep=',', # 分隔符为逗号
header=None, # 文件中无表头行
names=['name', 'date', '_', 'open', 'high', 'low', 'close', 'volume'], # 列名列表
index_col='date',# 将"date"列作为行索引
usecols=['date', 'open', 'high', 'low', 'close'] # 只读取需要的五列
)
print(df.head(3))
# 输出示例:
# open high low close
# date
# 2011-01-28 344.17 344.40 333.53 336.10
# 2011-01-31 335.80 340.04 334.30 339.32
# 2011-02-01 341.30 345.65 340.98 345.03
这里 names 提供 8 个列名(一个标为 _ 以忽略),usecols 选 5 列,index_col='date' 使日期成为行索引。head(3) 验证读取。usecols 避免读不需要的数据,加快加载。
常见问题: "读取 50,000 行会很慢吗?"
不会。 Pandas 在毫秒级处理 50k 行。数百万行需注意内存和性能。极大文件,用分块读取:
pd.read_csv(..., chunksize=10000)返回可迭代的 TextFileReader,每次生成 10,000 行。逐块迭代处理以避免过量内存用。或指定dtype避免 Pandas 猜测类型,节省开销。
写出 CSV 文件
用 df.to_csv() 保存 DataFrame:
df.to_csv('output.csv', index=False)
将 DataFrame 写入 output.csv,index=False 省略行索引(通常不必要)。默认 to_csv 保存索引和列标签;用 index=False 和 header=False 分别禁用。方法支持 sep 和 encoding 参数。
Pandas 还处理其他格式——Excel(read_excel、to_excel)、JSON(read_json、to_json)、SQL(read_sql、to_sql)、Parquet、HTML 表等。这套统一接口使数据格式转换直截:在 CSV、Excel、JSON 和数据库间无缝移动数据。
小结
Series 和 DataFrame 是 Pandas 处理表格数据的基础对象。Series 索引——位置和标签——支撑所有选择逻辑。行列 CRUD 模式对大型数据集通用。文件 I/O 无缝集成,从原始数据迅速迭代到分析。这些基础为后续的数据清洗、合并、重塑和可视化任务搭建平台。