Pandas 核心数据结构详解(精简版)

发布于:2025-08-19 ⋅ 阅读:(13) ⋅ 点赞:(0)

Pandas 综合使用教程

Pandas 是一个强大的开源数据分析和操作工具库,建立在 NumPy 之上,提供高性能的数组运算能力。它专为处理结构化数据(如表格、时间序列)而设计,是数据挖掘、数据分析和数据清洗的“利器”。其核心数据结构是 SeriesDataFrame


核心数据结构概览

特性 Series DataFrame
维度 一维 二维
类比 带标签的数组 电子表格或 SQL 表
组成 一组数据 + 一组索引 (标签) 多个 Series 的集合(按列)
关系 DataFrame 的每一行或每一列都是一个 Series 可视为一个由 Series 组成的字典,其中键是列名,值是 Series
数据类型 通常为同质(所有元素类型相同) 通常为异构(不同列可有不同数据类型)

1. Series:一维带标签数组

Series 是 Pandas 中最基本的数据结构,表示一个一维的、带标签的数组。

1.1 创建 Series

import pandas as pd
import numpy as np

# 从列表、元组创建 (默认整数索引)
s1 = pd.Series([1, 3, 5, np.nan, 6, 8])
s2 = pd.Series((1, 2, 3, 4, 5))  # 元组

# 从字典创建 (字典的键自动成为索引)
s3 = pd.Series({'a': 1, 'b': 2, 'c': 3})

# 从 NumPy 数组创建,并指定索引
s4 = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])

# 从标量创建 (标量值被重复)
s5 = pd.Series(5., index=['a', 'b', 'c', 'd'])

# 创建时指定索引
s6 = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])

1.2 常用属性

  • .values: 返回底层的 NumPy 数组。
  • .index: 返回索引对象(行标签)。
  • .name: 设置或获取 Series 的名称。
  • .dtype: 返回数据类型。
  • .size: 返回元素总数。
  • .shape: 返回形状(元组,例如 (5,))。
  • .is_unique: 检查索引是否唯一。
  • .keys(): 等同于 .index,返回索引。
s = pd.Series([10, 20, 30, 40], index=['w', 'x', 'y', 'z'], name='Scores')

print("Values (底层数组):", s.values)        # [10 20 30 40]
print("Index (索引):", s.index)              # Index(['w', 'x', 'y', 'z'], dtype='object')
print("Name (名称):", s.name)                # Scores
print("Dtype (数据类型):", s.dtype)          # int64 (或 float64)
print("Size (元素个数):", s.size)            # 4
print("Shape (形状):", s.shape)              # (4,)
print("Is Unique (索引是否唯一):", s.index.is_unique) # True
print("Keys (等同于 index):", s.keys().tolist())     # ['w', 'x', 'y', 'z']

1.3 常用方法

  • 查看数据:
    • .head(n=5): 显示前 n 个元素。
    • .tail(n=5): 显示后 n 个元素。
  • 统计计算 (数值型):
    • .describe(): 生成描述性统计摘要。
    • .mean(), .sum(), .max(), .min(), .std(): 计算均值、总和、最大值、最小值、标准差。
  • 数据转换:
    • .abs(): 计算绝对值。
    • .round(decimals=0): 四舍五入。
    • .to_list(): 转换为 Python 列表。
  • 缺失值处理:
    • .dropna(): 删除缺失值。
    • .fillna(value): 填充缺失值。
  • 排序:
    • .sort_values(ascending=True): 按值排序。
    • .sort_index(): 按索引排序。
  • 去重与计数:
    • .unique(): 返回唯一值数组。
    • .nunique(): 返回唯一值的数量(排除 NaN)。
    • .value_counts(): 返回每个唯一值的出现次数。
  • 函数应用:
    • .apply(func): 将函数 func 应用于每个元素。
    • .map(dict_or_func): 将字典映射或函数应用于每个元素。
np.random.seed(0)  # 保证结果可重现
s = pd.Series(np.random.randn(10))

print("原始 Series:\n", s)
print("\n前3个元素 (head):\n", s.head(3))
print("\n后3个元素 (tail):\n", s.tail(3))
print("\n描述性统计 (describe):\n", s.describe())
print("\n平均值 (mean):", s.mean())
print("\n绝对值 (abs):\n", s.abs())
print("\n四舍五入到2位小数 (round):\n", s.round(2))
print("\n转换为列表 (to_list):\n", s.head(3).to_list())
print("\n按值降序排序 (sort_values):\n", s.sort_values(ascending=False))
print("\n唯一值 (unique):", s.round().unique()) # 先取整再找唯一值
print("\n唯一值数量 (nunique):", s.round().nunique())
print("\n值计数 (value_counts):\n", s.round().value_counts())

1.4 索引与选择

  • .loc[label]: 基于标签(索引名)进行选择。
  • .iloc[position]: 基于位置(整数索引)进行选择。
scientist = pd.Series(['Rosaline Franklin', '1920-07-25', '1958-04-16', 37, 'Chemist'],
                      index=['Name', 'Born', 'Died', 'Age', 'Occupation'])

print("Series:\n", scientist)
print("\n通过标签获取 'Name':", scientist.loc['Name'])      # Rosaline Franklin
print("通过位置获取第4个元素:", scientist.iloc[3])           # 37
print("通过标签获取 'Age' 和 'Name':\n", scientist.loc[['Age', 'Name']])
print("通过位置获取前3个元素:\n", scientist.iloc[:3])

1.5 布尔索引

使用布尔条件选择元素。

s = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])

# 基本条件
filtered_s = s[s > 3]  # 保留值大于 3 的元素

# 复合条件 (使用 &, |, ~,注意括号)
filtered_s2 = s[(s > 1) & (s < 5)]

# 检查成员资格
mask = s.isin([2, 4])
print(s[mask])

1.6 运算

支持向量化运算,基于索引对齐。

s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([4, 5, 6], index=['a', 'b', 'c'])

# 基本算术 (索引对齐)
result = s1 + s2  # a:5, b:7, c:9

# 索引不匹配 -> 结果为 NaN
s3 = pd.Series([1, 2], index=['a', 'b'])
s4 = pd.Series([3, 4], index=['b', 'c'])
result = s3 + s4  # a:NaN, b:5, c:NaN

# 与标量运算
scaled = s1 * 10  # 所有元素乘以 10

2. DataFrame:二维表格型数据结构

DataFrame 是 Pandas 的核心,是一个大小可变、通常异构的二维带标签数据结构。

2.1 创建 DataFrame

# 从字典创建 (键为列名,值为数据)
data = {
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'City': ['NYC', 'LA', 'Chicago']
}
df = pd.DataFrame(data)

# 从列表的字典或字典的列表创建
df2 = pd.DataFrame([
    {'Name': 'Alice', 'Age': 25},
    {'Name': 'Bob', 'Age': 30}
])

# 从 NumPy 数组创建
df3 = pd.DataFrame(np.random.randn(4, 3), columns=['A', 'B', 'C'], index=['w', 'x', 'y', 'z'])

# 从 Series 字典创建
series_dict = {'col1': pd.Series([1, 2]), 'col2': pd.Series([3, 4])}
df4 = pd.DataFrame(series_dict)

# 创建时指定列顺序和行索引
df5 = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6]},
                   columns=['c', 'b', 'a'], index=['x', 'y'])

2.2 常用属性

  • .values: 返回底层的 NumPy 二维数组。
  • .index: 返回行索引。
  • .columns: 返回列索引(列名)。
  • .dtypes: 返回每列的数据类型。
  • .shape: 返回形状 (行数, 列数)
  • .size: 返回元素总数。
  • .ndim: 返回维度数(2)。
  • .T / .transpose(): 返回转置后的 DataFrame。
  • .keys(): 等同于 .columns,返回列名。
print("DataFrame:\n", df)
print("\nValues (底层数组):\n", df.values)
print("Index (行索引):", df.index.tolist())
print("Columns (列名):", df.columns.tolist())
print("Dtypes (各列数据类型):\n", df.dtypes)
print("Shape (形状):", df.shape)  # (4, 4)
print("Size (总元素数):", df.size) # 16
print("NDim (维度):", df.ndim)    # 2
print("T (转置):\n", df.T)

2.3 常用方法

  • 查看与信息:
    • .head(n=5) / .tail(n=5): 显示前/后 n 行。
    • .info(): 显示综合信息(列名、非空计数、数据类型、内存使用)。
  • 统计计算:
    • .describe(): 生成数值列的描述性统计。
    • .mean(), .sum(), .max(), .min(), .std(): 计算每列(axis=0)或每行(axis=1)的统计量。
    • .count(): 每列非空值的数量。
    • .corr(): 计算数值列间的相关系数矩阵。
  • 数据操作:
    • .drop(labels, axis=0/1): 删除行 (axis=0) 或列 (axis=1)。
    • .dropna(axis=0, how='any', subset=None): 删除包含缺失值的行/列。
    • .fillna(value): 填充缺失值。
    • .rename(columns={'old': 'new'}, inplace=False): 重命名列或行索引。
    • .astype(dtype): 转换列的数据类型。
    • .duplicated() / .drop_duplicates(): 检测和删除重复行。
  • 排序与索引:
    • .sort_values(by, ascending=True): 按一列或多列的值排序。
    • .sort_index(axis=0): 按行或列索引排序。
    • .reset_index(drop=False, inplace=False): 重置索引(原索引可变为一列)。
    • .set_index(keys, inplace=False): 将一列或多列设置为新的行索引。
  • 函数应用:
    • .apply(func, axis=0): 将函数应用于每列 (axis=0) 或每行 (axis=1)。
    • .agg(func) / .aggregate(func): 聚合操作,可对不同列应用不同函数。
  • 分组与聚合:
    • .groupby(by): 根据指定列分组,是聚合的基础。
    • .transform(func): 分组后应用函数,返回与原 DataFrame 形状相同的对象。
  • 成员资格检查:
    • .isin(values): 检查每个元素是否在给定值中,返回布尔 DataFrame。
  • 抽样:
    • .sample(n=None, frac=None): 随机抽样行。
print("前2行 (head):\n", df.head(2))
print("\n后2行 (tail):\n", df.tail(2))
print("\n综合信息 (info):")
df.info()  # 打印到控制台

print("\n描述性统计 (describe):\n", df.describe())
print("\n每列均值 (mean):\n", df.mean(numeric_only=True))
print("\n每列最大值 (max):\n", df.max(numeric_only=True))

# 添加、删除、重命名列
df['Pass'] = df['Score'] > 80  # 添加新列
print("\n添加 'Pass' 列后:\n", df)

df_dropped = df.drop('Pass', axis=1)  # 删除列,返回新对象
print("\n删除 'Pass' 列后 (新对象):\n", df_dropped)

df_renamed = df.rename(columns={'Score': 'Final_Score'}) # 重命名列,返回新对象
print("\n重命名 'Score' 为 'Final_Score' 后:\n", df_renamed)

# 排序
df_sorted = df.sort_values(by='Age', ascending=False)
print("\n按 'Age' 降序排序:\n", df_sorted)

# 分组聚合
grouped = df.groupby('City')['Score'].mean()
print("\n按 'City' 分组计算 'Score' 平均值:\n", grouped)

# 检测重复行
duplicates = df.duplicated()
print("\n重复行检测:\n", duplicates)

2.4 布尔索引

使用布尔条件选择行或元素。

df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie', 'Diana'],
    'Age': [25, 30, 35, 28],
    'City': ['NYC', 'LA', 'Chicago', 'NYC'],
    'Score': [85.5, 90.0, 78.0, 92.5]
})

# 选择满足条件的行
adults = df[df['Age'] >= 30]

# 复合条件
ny_young = df[(df['City'] == 'NYC') & (df['Age'] < 30)]

# 使用 isin
in_cities = df[df['City'].isin(['NYC', 'Chicago'])]

# 结合访问器 (如 str)
starts_with_a = df[df['Name'].str.startswith('A')]

# 选择特定列的同时进行布尔索引
names_over_30 = df.loc[df['Age'] > 30, 'Name']

2.5 运算

支持向量化运算,基于行和列索引对齐。

df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]}, index=['x', 'y'])
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]}, index=['x', 'y'])

# DataFrame 间运算 (索引对齐)
add_df = df1 + df2

# DataFrame 与 Series 运算 (广播,Series 索引匹配 DataFrame 列)
s = pd.Series([10, 20], index=['A', 'B'])
df_plus_s = df1 + s  # 每行都加上 Series s

# 与标量运算
scaled_df = df1 * 100

# 索引不匹配 -> 结果为 NaN
df3 = pd.DataFrame({'A': [1], 'C': [2]}, index=['x'])
df4 = pd.DataFrame({'A': [3], 'B': [4]}, index=['y'])
result_df = df3 + df4

3. 数据读取与写入

3.1 常用格式

操作 读取 写入
CSV pd.read_csv('file.csv') df.to_csv('file.csv', index=False)
Excel pd.read_excel('file.xlsx') df.to_excel('file.xlsx', sheet_name='Sheet1')
SQL pd.read_sql('query', connection) df.to_sql('table_name', connection, if_exists='replace')
Pickle pd.read_pickle('file.pkl') df.to_pickle('file.pkl')

注意: 读写 Excel 需要额外安装包:xlrd, openpyxl, xlwt

# --- 读取 ---
# df_csv = pd.read_csv('data.csv')
# df_excel = pd.read_excel('data.xlsx')
# df_sql = pd.read_sql('SELECT * FROM table', connection)
# df_pickle = pd.read_pickle('data.pkl')

# --- 写入 ---
# df.to_csv('output.csv', index=False)
# df.to_excel('output.xlsx', sheet_name='Sheet1', index=False)
# df.to_sql('table_name', connection, if_exists='replace', index=False)
# df.to_pickle('output.pkl')

# 示例:创建一个 DataFrame 并写入/读取 CSV
example_df = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
example_df.to_csv('temp_example.csv', index=False)
read_back_df = pd.read_csv('temp_example.csv')
print("从 CSV 读回的数据:\n", read_back_df)

# 示例:Pickle
example_df.to_pickle('temp_example.pkl')
read_back_pickle = pd.read_pickle('temp_example.pkl')
print("从 Pickle 读回的数据:\n", read_back_pickle)

3.2 Pickle 文件

  • 用途: 保存 Python 对象(如 DataFrame, Series)的中间结果,仅限 Python 环境复用。
  • 扩展名: .p, .pkl, .pickle

4. 数据操作进阶

4.1 合并与连接

  • pd.concat([df1, df2], axis=0): 沿行 (axis=0) 或列 (axis=1) 连接多个 DataFrame。
  • df1.merge(df2, on='key', how='inner'): 基于键进行数据库风格的连接 (inner, outer, left, right)。
left = pd.DataFrame({'key': ['K0', 'K1', 'K2'], 'A': ['A0', 'A1', 'A2'], 'B': ['B0', 'B1', 'B2']})
right = pd.DataFrame({'key': ['K0', 'K1', 'K3'], 'C': ['C0', 'C1', 'C3'], 'D': ['D0', 'D1', 'D3']})

print("Left DataFrame:\n", left)
print("Right DataFrame:\n", right)

# 内连接 (inner join)
merged_inner = pd.merge(left, right, on='key', how='inner')
print("\n内连接 (how='inner'):\n", merged_inner)

# 左连接 (left join)
merged_left = pd.merge(left, right, on='key', how='left')
print("\n左连接 (how='left'):\n", merged_left)

# 连接 (concat)
concatenated = pd.concat([left, right], axis=0, ignore_index=True)  # 沿行连接
print("\n沿行连接 (axis=0):\n", concatenated)

4.2 数据重塑

  • .pivot(index, columns, values): 将长格式数据转换为宽格式。
  • .melt(id_vars, value_vars, var_name, value_name): 将宽格式数据转换为长格式。
# 原始宽格式数据
wide_df = pd.DataFrame({
    'Student': ['Alice', 'Bob'],
    'Math': [85, 90],
    'Science': [78, 88],
    'English': [92, 85]
})
print("宽格式数据:\n", wide_df)

# 转换为长格式 (melt)
long_df = wide_df.melt(id_vars='Student', value_vars=['Math', 'Science', 'English'],
                       var_name='Subject', value_name='Score')
print("\n长格式数据 (melt):\n", long_df)

# 转换回宽格式 (pivot)
pivoted_df = long_df.pivot(index='Student', columns='Subject', values='Score')
print("\n宽格式数据 (pivot):\n", pivoted_df)

4.3 时间序列

  • pd.to_datetime(): 将数据转换为日期时间类型。
  • df['date'] = pd.to_datetime(df['date']): 转换列为日期时间。
  • .resample('D').mean(): 按天重采样并计算均值(需日期索引)。
# 创建时间序列数据
dates = pd.date_range('20230101', periods=6)
ts = pd.Series(np.random.randn(6), index=dates)
print("时间序列 Series:\n", ts)

# 转换为日期时间
df_time = pd.DataFrame({
    'Date': ['2023-01-01', '2023-01-02', '2023-01-03'],
    'Value': [100, 105, 110]
})
df_time['Date'] = pd.to_datetime(df_time['Date'])
df_time.set_index('Date', inplace=True)
print("\n设置日期索引后的 DataFrame:\n", df_time)

# 重采样
daily_data = pd.DataFrame({
    'Value': np.random.randn(365)
}, index=pd.date_range('2023-01-01', periods=365, freq='D'))

monthly_mean = daily_data.resample('M').mean()  # 按月重采样取均值
print("\n按月重采样均值 (前几行):\n", monthly_mean.head())

4.4 访问器 (Accessors)

提供特定类型数据的便捷操作方法:

  • .str: 字符串操作(如 .str.upper(), .str.contains())。
  • .dt: 日期时间操作(如 .dt.year, .dt.month)。
  • .cat: 分类数据操作。
# 字符串访问器
df_str = pd.DataFrame({'Name': ['alice', 'BOB', 'charlie'], 'Email': ['A@G.COM', 'b@G.COM', 'C@G.COM']})
print("原始 DataFrame:\n", df_str)

print("\n名字转大写:\n", df_str['Name'].str.upper())
print("邮箱转小写:\n", df_str['Email'].str.lower())
print("名字长度:\n", df_str['Name'].str.len())
print("名字是否包含 'a':\n", df_str['Name'].str.contains('a', case=False))

# 日期时间访问器
df_time['Year'] = df_time.index.year
df_time['Month'] = df_time.index.month
df_time['DayOfWeek'] = df_time.index.dayofweek
print("\n添加日期时间信息:\n", df_time)

5. 实用技巧与最佳实践

  • 创建副本: 使用 .copy(deep=True) 避免意外修改原数据。
  • 链式操作: df.dropna().sort_values('col').head(10)
  • inplace 参数: 大多数修改方法有 inplace=True/FalseFalse (默认) 返回新对象,True 修改原对象并返回 None建议谨慎使用 inplace=True,优先返回新对象以保持代码清晰和可调试性。
  • 性能: Pandas 操作通常是向量化的,效率高。避免使用 iterrows() 进行循环,优先使用 .apply() 或向量化操作。
# 创建副本
df_original = df.copy(deep=True)  # 创建深拷贝
df_modified = df_original.drop('Pass', axis=1)  # 在副本上操作

# 链式操作
result = (df
          .dropna()                    # 删除缺失值
          .sort_values('Score', ascending=False)  # 按分数降序
          .head(3)                     # 取前3名
          .reset_index(drop=True)      # 重置索引
         )
print("\n链式操作结果 (前三名):\n", result)

# 谨慎使用 inplace
df_clean = df.drop('Pass', axis=1)  # 推荐:返回新对象
# df.drop('Pass', axis=1, inplace=True)  # 不推荐:修改原对象,返回 None

# 性能提示:避免 iterrows
# 低效
# for index, row in df.iterrows():
#     print(row['Name'], row['Age'])

# 高效 (向量化)
print("\n高效方式 (向量化):")
print(df['Name'] + " is " + df['Age'].astype(str) + " years old.")

# 使用 apply (当向量化不可行时)
def age_category(age):
    if age < 30:
        return 'Young'
    elif age < 40:
        return 'Adult'
    else:
        return 'Senior'

df['Age_Category'] = df['Age'].apply(age_category)
print("\n应用函数后的 DataFrame:\n", df)

网站公告

今日签到

点亮在社区的每一天
去签到