Python Pandas详解:数据处理与分析核心库 – wiki基地


Python Pandas详解:数据处理与分析核心库

在当今数据驱动的时代,能够高效地处理和分析数据是各行各业专业人士必备的核心技能。Python 语言凭借其简洁的语法、丰富的库支持以及活跃的社区,已成为数据科学领域的首选语言。而在 Python 的数据科学生态系统中,Pandas 库无疑是最耀眼的明星之一,它为处理结构化数据提供了强大、灵活且易用的工具,是进行数据清洗、转换、分析和可视化的基石。

一、Pandas 简介:为何如此重要?

Pandas(Panel Data Analysis)最初由 Wes McKinney 于 2008 年开发,旨在为金融数据分析提供一个高性能、易用的工具。如今,Pandas 已经发展成为一个通用的数据分析库,广泛应用于学术研究、商业智能、机器学习等多个领域。

Pandas 的核心优势:

  1. 强大的数据结构: 提供了 Series(一维)和 DataFrame(二维)两种核心数据结构,能够高效处理不同类型的数据(数值、字符串、时间序列、布尔值等)。
  2. 便捷的数据读写: 支持从多种格式的文件中读取数据(如 CSV, Excel, SQL 数据库, JSON, HTML 等),并能将处理后的数据轻松写回。
  3. 灵活的数据操作: 提供了丰富的数据清洗、转换、重塑、切片、索引、合并、分组和聚合等功能。
  4. 高效的性能: 底层部分代码由 Cython 或 C 实现,保证了在处理大规模数据集时的运算效率。
  5. 与生态系统无缝集成: 与 NumPy(数值计算)、Matplotlib/Seaborn(数据可视化)、Scikit-learn(机器学习)等 Python 库紧密集成,构成了强大的数据分析流水线。
  6. 时间序列处理能力: 内置了强大的时间序列数据处理功能,非常适合金融、经济等领域的数据分析。

二、Pandas 的核心数据结构

Pandas 的功能主要围绕其两个核心数据结构构建:Series 和 DataFrame。

1. Series:一维带标签数组

Series 是一种类似于一维 NumPy 数组的对象,但它带有一个与之关联的标签数组,称为索引(index)。索引可以是数字、字符串或其他 Python 对象。

创建 Series:

“`python
import pandas as pd
import numpy as np

从列表创建 Series,默认数字索引

s1 = pd.Series([1, 3, 5, np.nan, 6, 8])
print(“s1:\n”, s1)

从列表创建 Series,并指定索引

data = [10, 20, 30, 40]
labels = [‘a’, ‘b’, ‘c’, ‘d’]
s2 = pd.Series(data, index=labels)
print(“\ns2:\n”, s2)

从字典创建 Series,字典的键作为索引

data_dict = {‘apple’: 10, ‘banana’: 20, ‘orange’: 15}
s3 = pd.Series(data_dict)
print(“\ns3:\n”, s3)
“`

Series 的基本操作:

  • 访问元素: 可以通过索引标签或位置进行访问。
    python
    print("\ns2['b']:", s2['b']) # 通过标签访问
    print("s2[1]:", s2[1]) # 通过位置访问 (如果索引是默认数字索引,标签和位置可能重合)
    print("s2[['a', 'c']]:\n", s2[['a', 'c']]) # 选择多个元素
  • 属性: index (获取索引), values (获取值,NumPy 数组形式), dtype (数据类型), shape (形状), name (名称)。
  • 向量化运算: Series 支持 NumPy 风格的向量化运算。
    python
    print("\ns2 * 2:\n", s2 * 2)
    print("np.exp(s2/10):\n", np.exp(s2/10))
  • 布尔索引:
    python
    print("\ns2[s2 > 15]:\n", s2[s2 > 15])

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

DataFrame 是一个表格型的数据结构,它包含一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame 既有行索引(index),也有列索引(columns)。可以将其看作是由多个 Series 共享相同索引的字典。

创建 DataFrame:

“`python

1. 从字典创建,字典的键作为列名,值为列表或 Series

data_df1 = {
‘name’: [‘Alice’, ‘Bob’, ‘Charlie’, ‘David’],
‘age’: [25, 30, 35, 28],
‘city’: [‘New York’, ‘Paris’, ‘London’, ‘Tokyo’]
}
df1 = pd.DataFrame(data_df1)
print(“df1:\n”, df1)

指定行索引

df1_indexed = pd.DataFrame(data_df1, index=[‘p1’, ‘p2’, ‘p3’, ‘p4’])
print(“\ndf1_indexed:\n”, df1_indexed)

2. 从 NumPy 数组创建,指定列名和行索引

dates = pd.date_range(‘20230101’, periods=6) # 创建日期索引
df2 = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list(‘ABCD’))
print(“\ndf2:\n”, df2)

3. 从 Series 字典创建

s_a = pd.Series([1, 2, 3], index=[‘x’, ‘y’, ‘z’])
s_b = pd.Series([4, 5, 6, 7], index=[‘x’, ‘y’, ‘z’, ‘w’])
df3 = pd.DataFrame({‘col1’: s_a, ‘col2’: s_b}) # 会根据索引自动对齐,缺失处填充 NaN
print(“\ndf3:\n”, df3)

4. 从列表的列表或元组的列表创建

data_list_of_lists = [[‘Alice’, 25], [‘Bob’, 30]]
df4 = pd.DataFrame(data_list_of_lists, columns=[‘Name’, ‘Age’])
print(“\ndf4:\n”, df4)
“`

DataFrame 的基本操作:

  • 查看数据:

    • head(n): 查看前 n 行数据,默认为 5。
    • tail(n): 查看后 n 行数据,默认为 5。
    • index: 查看行索引。
    • columns: 查看列名。
    • values: 获取 DataFrame 的 NumPy 表示。
    • describe(): 生成描述性统计数据(计数、均值、标准差、最小值、四分位数、最大值等)。
    • info(): 提供 DataFrame 的简要摘要(索引类型、列数据类型、非空值数量、内存使用情况)。
    • shape: 返回一个表示 DataFrame 维度的元组 (行数, 列数)。
    • dtypes: 返回每列的数据类型。

    python
    print("\ndf1.head(2):\n", df1.head(2))
    print("\ndf1.index:\n", df1.index)
    print("\ndf1.columns:\n", df1.columns)
    print("\ndf1.describe():\n", df1.describe(include='all')) # include='all' 会包含非数值列的统计
    print("\ndf1.info():")
    df1.info()

  • 选择数据(索引和切片):

    • 选择列:
      python
      print("\ndf1['name']:\n", df1['name']) # 选择单列,返回 Series
      print("\ndf1[['name', 'city']]:\n", df1[['name', 'city']]) # 选择多列,返回 DataFrame
    • 选择行(基于标签):loc
      loc 主要基于标签进行选择,但也可以接受布尔数组。
      python
      print("\ndf1.loc[0]:\n", df1.loc[0]) # 选择第一行 (如果索引是数字)
      print("\ndf1_indexed.loc['p1']:\n", df1_indexed.loc['p1']) # 选择标签为 'p1' 的行
      print("\ndf1.loc[0:2]:\n", df1.loc[0:2]) # 选择第0行到第2行 (包含结束标签)
      print("\ndf1_indexed.loc['p1':'p3']:\n", df1_indexed.loc['p1':'p3'])
    • 选择行(基于整数位置):iloc
      iloc 完全基于整数位置进行选择(从0开始)。
      python
      print("\ndf1.iloc[0]:\n", df1.iloc[0]) # 选择第一行
      print("\ndf1.iloc[0:2]:\n", df1.iloc[0:2]) # 选择前两行 (不包含结束位置)
      print("\ndf1.iloc[[0, 2, 1], [0, 2]]:\n", df1.iloc[[0, 2, 1], [0, 2]]) # 选择特定行和列
    • 选择行和列:
      python
      print("\ndf1.loc[0, 'name']:", df1.loc[0, 'name']) # 第0行 'name' 列的元素
      print("\ndf1.loc[0:2, ['name', 'age']]:\n", df1.loc[0:2, ['name', 'age']])
      print("\ndf1.iloc[0:2, 0:2]:\n", df1.iloc[0:2, 0:2])
    • 布尔索引:
      python
      print("\ndf1[df1['age'] > 25]:\n", df1[df1['age'] > 25])
      print("\ndf1[df1['city'].isin(['New York', 'Tokyo'])]:\n", df1[df1['city'].isin(['New York', 'Tokyo'])])

三、常用数据操作

Pandas 提供了大量函数和方法来执行各种数据操作。

1. 数据加载与保存

Pandas 可以轻松读写多种数据格式。

  • CSV 文件:
    python
    # 假设有一个名为 'data.csv' 的文件
    # df_csv = pd.read_csv('data.csv')
    # df1.to_csv('output.csv', index=False) # index=False 表示不将行索引写入文件
  • Excel 文件:
    python
    # 需要安装 openpyxl 或 xlrd/xlwt 库
    # df_excel = pd.read_excel('data.xlsx', sheet_name='Sheet1')
    # df1.to_excel('output.xlsx', sheet_name='MyData', index=False)
  • SQL 数据库:
    python
    # 需要安装 SQLAlchemy 和相应的数据库驱动 (如 psycopg2 for PostgreSQL, mysqlclient for MySQL)
    # from sqlalchemy import create_engine
    # engine = create_engine('sqlite:///mydatabase.db') # 示例:SQLite
    # df_sql = pd.read_sql_query('SELECT * FROM my_table', engine)
    # df1.to_sql('new_table', engine, if_exists='replace', index=False) # if_exists: 'fail', 'replace', 'append'

2. 数据清洗

真实世界的数据往往是不完美的,包含缺失值、重复值或错误数据。

  • 处理缺失值 (NaN):

    • isnull() / notnull(): 检测缺失值,返回布尔型的 DataFrame 或 Series。
    • dropna(axis=0, how='any', thresh=None, subset=None, inplace=False): 删除包含缺失值的行或列。
      • axis: 0 表示行,1 表示列。
      • how: ‘any’ (只要有一个 NaN 就删除),’all’ (所有值都是 NaN 才删除)。
      • thresh: 保留至少有 thresh 个非 NaN 值的行/列。
      • subset: 在指定的列中查找 NaN。
    • fillna(value=None, method=None, axis=None, inplace=False, limit=None): 填充缺失值。
      • value: 用于填充的标量值或字典/Series/DataFrame。
      • method: ‘ffill’ (向前填充), ‘bfill’ (向后填充)。

    python
    data_missing = {'col1': [1, 2, np.nan, 4], 'col2': [np.nan, 5, 6, 7], 'col3': [8, 9, 10, np.nan]}
    df_miss = pd.DataFrame(data_missing)
    print("\nDataFrame with missing values:\n", df_miss)
    print("\nDetect missing values:\n", df_miss.isnull())
    print("\nDrop rows with any NaN:\n", df_miss.dropna())
    print("\nFill NaN with a scalar (0):\n", df_miss.fillna(value=0))
    print("\nFill NaN with mean of column:\n", df_miss.fillna(df_miss.mean()))
    print("\nForward fill:\n", df_miss.fillna(method='ffill'))

  • 处理重复数据:

    • duplicated(subset=None, keep='first'): 判断行是否重复,返回布尔 Series。
      • subset: 指定要考虑的列。
      • keep: ‘first’ (标记除第一个外的重复项), ‘last’ (标记除最后一个外的重复项), False (标记所有重复项)。
    • drop_duplicates(subset=None, keep='first', inplace=False): 删除重复行。

    python
    data_duplicates = {'colA': ['X', 'Y', 'X', 'Z', 'Y'], 'colB': [1, 2, 1, 3, 2]}
    df_dup = pd.DataFrame(data_duplicates)
    print("\nDataFrame with duplicates:\n", df_dup)
    print("\nBoolean series indicating duplicates (keeping first):\n", df_dup.duplicated())
    print("\nDrop duplicates (keeping first):\n", df_dup.drop_duplicates())
    print("\nDrop duplicates based on 'colA', keeping last:\n", df_dup.drop_duplicates(subset=['colA'], keep='last'))

3. 数据转换与操作

  • 应用函数 (Apply):

    • apply(func, axis=0, ...): 将函数应用于 DataFrame 的行或列。
    • map(arg, na_action=None): (Series 方法) 用于对 Series 的每个元素应用一个函数或映射。
    • applymap(func, na_action=None): (DataFrame 方法) 用于对 DataFrame 的每个元素应用一个函数。

    “`python
    print(“\ndf2:\n”, df2)
    print(“\nApplying lambda to each column (e.g., max-min):\n”, df2.apply(lambda x: x.max() – x.min())) # axis=0 (default)
    print(“\nApplying custom function to each row:\n”, df2.apply(np.sum, axis=1))

    map for Series

    s_map = pd.Series([1,2,3,4])
    print(“\nSeries map example:\n”, s_map.map(lambda x: x*100))

    applymap for DataFrame

    print(“\nDataFrame applymap example (format as string):\n”, df2.applymap(lambda x: f”{x:.2f}”))
    “`

  • 数据类型转换:

    • astype(dtype): 转换列的数据类型。
      python
      df_types = pd.DataFrame({'A': ['1', '2', '3'], 'B': [4.0, 5.1, 6.2]})
      df_types.info()
      df_types['A'] = df_types['A'].astype(int)
      df_types['B'] = df_types['B'].astype(str)
      print("\nAfter astype:")
      df_types.info()
  • 字符串操作:
    Pandas Series 具有 .str 访问器,可以方便地对字符串列执行向量化的字符串操作。
    python
    s_str = pd.Series(['apple pie', 'banana bread', 'Orange Juice', 'GRAPES'])
    print("\nString Series:\n", s_str)
    print("\nLowercase:\n", s_str.str.lower())
    print("\nSplit by space:\n", s_str.str.split(' '))
    print("\nContains 'an':\n", s_str.str.contains('an'))
    print("\nReplace ' ' with '-':\n", s_str.str.replace(' ', '-'))

4. 分组与聚合 (Group By)

groupby() 操作是 Pandas 中最强大的功能之一,它遵循 “split-apply-combine” 的模式:
1. Split: 根据某些标准将数据分成组。
2. Apply: 对每个组独立应用一个函数(如求和、计数、平均值等)。
3. Combine: 将结果组合成一个新的数据结构。

“`python
data_group = {
‘Team’: [‘A’, ‘B’, ‘A’, ‘B’, ‘A’, ‘C’, ‘C’, ‘B’],
‘Player’: [‘P1’, ‘P2’, ‘P3’, ‘P4’, ‘P5’, ‘P6’, ‘P7’, ‘P8’],
‘Points’: [10, 12, 8, 15, 11, 9, 10, 13],
‘Assists’: [5, 7, 4, 8, 6, 3, 5, 6]
}
df_group = pd.DataFrame(data_group)
print(“\nDataFrame for grouping:\n”, df_group)

按 ‘Team’ 分组并计算每队的平均 ‘Points’ 和 ‘Assists’

grouped_team_mean = df_group.groupby(‘Team’).mean()
print(“\nMean points and assists per team:\n”, grouped_team_mean)

按 ‘Team’ 分组并计算每队的 ‘Points’ 总和

grouped_team_sum_points = df_group.groupby(‘Team’)[‘Points’].sum()
print(“\nSum of points per team:\n”, grouped_team_sum_points)

按 ‘Team’ 分组并应用多个聚合函数

grouped_team_agg = df_group.groupby(‘Team’).agg({
‘Points’: [‘sum’, ‘mean’, ‘count’],
‘Assists’: [‘mean’, ‘min’, ‘max’]
})
print(“\nMultiple aggregations per team:\n”, grouped_team_agg)

按多列分组

grouped_multi = df_group.groupby([‘Team’, df_group[‘Points’] > 10]).size() # 第二个分组条件是Points是否大于10
print(“\nGroup by Team and (Points > 10):\n”, grouped_multi)
“`

5. 合并、连接与拼接

Pandas 提供了多种方式来组合多个 DataFrame 或 Series。

  • concat(): 拼接
    用于沿特定轴(行或列)将多个 Pandas 对象堆叠在一起。
    “`python
    df_c1 = pd.DataFrame({‘A’: [‘A0’, ‘A1’], ‘B’: [‘B0’, ‘B1’]})
    df_c2 = pd.DataFrame({‘A’: [‘A2’, ‘A3’], ‘B’: [‘B2’, ‘B3’]})
    df_c3 = pd.DataFrame({‘C’: [‘C0’, ‘C1’], ‘D’: [‘D0’, ‘D1’]})

    按行拼接 (默认 axis=0)

    concatenated_rows = pd.concat([df_c1, df_c2])
    print(“\nConcatenated by rows:\n”, concatenated_rows)

    按列拼接 (axis=1)

    concatenated_cols = pd.concat([df_c1, df_c3], axis=1)
    print(“\nConcatenated by columns:\n”, concatenated_cols)
    “`

  • merge(): 数据库风格的连接
    基于一个或多个键将 DataFrame 的行连接起来,类似于 SQL 中的 JOIN。
    “`python
    left = pd.DataFrame({‘key’: [‘K0’, ‘K1’, ‘K2’, ‘K3’],
    ‘A’: [‘A0’, ‘A1’, ‘A2’, ‘A3’],
    ‘B’: [‘B0’, ‘B1’, ‘B2’, ‘B3’]})
    right = pd.DataFrame({‘key’: [‘K0’, ‘K1’, ‘K2’, ‘K4’],
    ‘C’: [‘C0’, ‘C1’, ‘C2’, ‘C4’],
    ‘D’: [‘D0’, ‘D1’, ‘D2’, ‘D4’]})

    内连接 (how=’inner’, 默认)

    merged_inner = pd.merge(left, right, on=’key’, how=’inner’)
    print(“\nInner merge on ‘key’:\n”, merged_inner)

    左连接 (how=’left’)

    merged_left = pd.merge(left, right, on=’key’, how=’left’)
    print(“\nLeft merge on ‘key’:\n”, merged_left)

    右连接 (how=’right’)

    merged_right = pd.merge(left, right, on=’key’, how=’right’)
    print(“\nRight merge on ‘key’:\n”, merged_right)

    外连接 (how=’outer’)

    merged_outer = pd.merge(left, right, on=’key’, how=’outer’)
    print(“\nOuter merge on ‘key’:\n”, merged_outer)

    如果连接键的列名不同

    left_diff_key = pd.DataFrame({‘lkey’: [‘K0’, ‘K1’], ‘val_l’: [1,2]})
    right_diff_key = pd.DataFrame({‘rkey’: [‘K0’, ‘K1’], ‘val_r’: [3,4]})
    merged_diff_key = pd.merge(left_diff_key, right_diff_key, left_on=’lkey’, right_on=’rkey’)
    print(“\nMerge with different key names:\n”, merged_diff_key)
    “`

  • join(): 基于索引的连接
    join() 主要用于基于索引合并 DataFrame,也可以通过 on 参数指定列。
    “`python
    left_idx = pd.DataFrame({‘A’: [‘A0’, ‘A1’, ‘A2’]}, index=[‘K0’, ‘K1’, ‘K2’])
    right_idx = pd.DataFrame({‘B’: [‘B0’, ‘B1’, ‘B2’]}, index=[‘K0’, ‘K1’, ‘K3’])

    joined_df = left_idx.join(right_idx, how=’outer’) # 默认左连接
    print(“\nJoined DataFrame (outer):\n”, joined_df)
    “`

6. 时间序列处理

Pandas 最初就是为金融时间序列分析而设计的,因此其时间序列功能非常强大。
* 创建时间序列索引: pd.date_range()
* 时间戳对象: pd.Timestamp
* 时间间隔对象: pd.Timedelta
* 重采样: resample() 方法,用于将时间序列数据从一个频率转换到另一个频率(例如,从日数据到月数据)。
* 窗口函数: rolling()expanding(),用于计算移动平均、移动总和等。

“`python

创建一个时间序列 DataFrame

dates = pd.date_range(‘20230101′, periods=100, freq=’D’) # 100天的日数据
ts_df = pd.DataFrame(np.random.randn(100, 2), index=dates, columns=[‘Value1’, ‘Value2’])
print(“\nTime series DataFrame head:\n”, ts_df.head())

重采样到月度频率,计算每月平均值

monthly_mean = ts_df.resample(‘M’).mean() # ‘M’ for month end frequency
print(“\nMonthly mean:\n”, monthly_mean.head())

计算7天移动平均

rolling_mean_7d = ts_df[‘Value1’].rolling(window=7).mean()
print(“\n7-day rolling mean for Value1 (tail):\n”, rolling_mean_7d.tail())
“`

7. 数据可视化

Pandas DataFrame 和 Series 对象内置了 .plot() 方法,可以方便地调用 Matplotlib 库进行快速绘图。

“`python
import matplotlib.pyplot as plt # 通常需要导入

简单的线图

ts_df[‘Value1′].plot(figsize=(10, 4), title=’Value1 Over Time’)

plt.show() # 在脚本中需要 plt.show() 来显示图像,Jupyter Notebook中通常会自动显示

柱状图

df_group.groupby(‘Team’)[‘Points’].sum().plot(kind=’bar’, title=’Total Points by Team’)

plt.ylabel(‘Total Points’)

plt.show()

直方图

df_group[‘Points’].plot(kind=’hist’, bins=5, title=’Distribution of Points’)

plt.xlabel(‘Points’)

plt.show()

散点图

df_group.plot(kind=’scatter’, x=’Points’, y=’Assists’, title=’Points vs Assists’)

plt.show()

“`
为了更高级和美观的可视化,通常会结合 Seaborn 库使用。

四、Pandas 性能优化与最佳实践

  1. 使用向量化操作: 尽量避免使用 Python 循环遍历 DataFrame 的行,Pandas 和 NumPy 的内置函数(如 sum(), mean(), 算术运算符, apply() 等)都是向量化的,速度远快于显式循环。
  2. 选择正确的数据类型: 使用 astype() 将列转换为最合适的数据类型(如 category 类型对于低基数类别列,int8/16/32/64float32/64 对于数值列),可以显著减少内存占用并提高运算速度。
  3. 明智地使用 apply() 虽然 apply() 很灵活,但它通常比专门的向量化函数慢。如果可能,优先选择内置的 Pandas/NumPy 函数。
  4. 避免不必要的副本: 许多 Pandas 操作会返回数据的副本。如果不需要副本,可以使用 inplace=True(但需谨慎,因为它会修改原始 DataFrame),或者将结果赋回原变量。
  5. 处理大型数据集:
    • 分块读取: 使用 pd.read_csv()chunksize 参数可以分块读取大文件,逐块处理,避免一次性加载到内存。
    • 指定 dtype 在读取数据时(如 read_csv),通过 dtype 参数预先指定列的数据类型,可以节省内存和解析时间。
    • 考虑 Dask 或 Vaex: 对于远超内存容量的数据集,可以考虑使用 Dask 或 Vaex 等库,它们提供了与 Pandas 类似的 API,但支持并行计算和核外计算。

五、总结

Pandas 是 Python 数据科学生态系统中不可或缺的组成部分。它提供的 Series 和 DataFrame 数据结构,以及围绕它们构建的丰富功能集,使得数据的导入、清洗、转换、分析和可视化变得异常高效和便捷。无论是初学者还是经验丰富的数据分析师,掌握 Pandas 都是一项至关重要的技能。通过不断实践和探索,你会发现 Pandas 的强大之处远不止于此,它将是你探索数据世界、洞察数据价值的得力助手。随着数据量的持续增长和数据分析需求的日益复杂,Pandas 及其相关工具的重要性只会越来越突出。


希望这篇文章能够帮助你更好地理解和使用 Pandas!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部