文章 · 2023-04-23

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)。

切片行为有差异:

这种不对称需要注意。

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)。shapesizendim 遵循 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 提供属性和方法来检查结构:

检查我们的 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

indexcolumns 列出行列标签。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['新列名'] = ...。右侧可以是:

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 修改特定单元格(在行操作中介绍)。

删除列

用以下方式移除列:

示例:

# 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' 的列,不是行。改用:

使用 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 的两列,实现行列交叉选取

lociloc 都接受单个索引、切片和列表。关键差异:

添加行

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,可能与现有索引冲突。总是提供唯一索引。

修改行

lociloc 修改行:

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'])

常用参数:

综合示例——读取无表头的股票数据,选择日期(作为索引)和 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=Falseheader=False 分别禁用。方法支持 sepencoding 参数。

Pandas 还处理其他格式——Excel(read_excelto_excel)、JSON(read_jsonto_json)、SQL(read_sqlto_sql)、Parquet、HTML 表等。这套统一接口使数据格式转换直截:在 CSV、Excel、JSON 和数据库间无缝移动数据。

小结

Series 和 DataFrame 是 Pandas 处理表格数据的基础对象。Series 索引——位置和标签——支撑所有选择逻辑。行列 CRUD 模式对大型数据集通用。文件 I/O 无缝集成,从原始数据迅速迭代到分析。这些基础为后续的数据清洗、合并、重塑和可视化任务搭建平台。

© 2026 Yuxu Ge ·