杰瑞科技汇

Python DataFrame 遍历哪种方法最高效?

向量化操作优先

Pandas 的强大之处在于其底层的 Num 实现,它允许你对整个列(或行)进行向量化操作,这比用 for 循环逐个元素处理要快几个数量级。

Python DataFrame 遍历哪种方法最高效?-图1
(图片来源网络,侵删)

什么时候应该优先考虑向量化操作? 当你需要对 DataFrame 中的数据进行数学计算、逻辑判断、字符串操作等时,几乎总是应该优先尝试使用向量化方法。

示例:向量化 vs. 循环

假设我们有一个 DataFrame,想将 'A' 列的所有值乘以 2。

import pandas as pd
import numpy as np
# 创建一个示例 DataFrame
df = pd.DataFrame({'A': [1, 2, 3, 4, 5], 'B': [10, 20, 30, 40, 50]})
# --- 不推荐:使用 for 循环 ---
df_loop = df.copy()
for i in range(len(df_loop)):
    df_loop.loc[i, 'A'] = df_loop.loc[i, 'A'] * 2
print("--- 使用 for 循环的结果 ---")
print(df_loop)
# --- 推荐:使用向量化操作 ---
df_vectorized = df.copy()
df_vectorized['A'] = df_vectorized['A'] * 2
print("\n--- 使用向量化操作的结果 ---")
print(df_vectorized)

输出:

Python DataFrame 遍历哪种方法最高效?-图2
(图片来源网络,侵删)
--- 使用 for 循环的结果 ---
   A   B
0  2  10
1  4  20
2  6  30
3  8  40
4  10 50
--- 使用向量化操作的结果 ---
    A   B
0   2  10
1   4  20
2   6  30
3   8  40
4  10  50

你会发现结果一样,但向量化代码更简洁、可读性更强,并且性能也远超循环。


什么时候必须使用遍历?

尽管向量化很强大,但在某些场景下,遍历是无法避免的或更合适的:

  1. 复杂的、无法向量化逻辑:当你的操作逻辑非常复杂,涉及多个列的条件判断和相互影响,难以用一行向量化代码表达时。
  2. 逐行处理外部资源:在每一行中,你需要调用一个外部 API、读写文件、执行数据库查询等,这些操作本质上是顺序的。
  3. 访问行索引和列名:当你需要同时获取每一行的索引和该行的所有数据时。
  4. 性能要求不高,代码可读性更重要:对于非常小的 DataFrame,性能差异可以忽略不计,而使用 iterrowsitertuples 可能会让代码意图更清晰。

遍历 DataFrame 的几种主要方法

下面我们介绍四种常见的遍历方法,并分析它们的性能和适用场景。

iterrows() - 按行迭代

iterrows() 将 DataFrame 的每一行作为一个 Series 返回,同时提供该行的索引。

Python DataFrame 遍历哪种方法最高效?-图3
(图片来源网络,侵删)
import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'c']})
print("使用 iterrows() 遍历:")
for index, row in df.iterrows():
    print(f"索引: {index}")
    print(f"行数据 (Series):\n{row}")
    print(f"访问 A 列的值: {row['A']}")
    print("-" * 20)

特点:

  • 优点: 直观,容易理解,可以直接通过列名(如 row['A'])访问数据。
  • 缺点: 性能最差,因为它在每次迭代时都创建一个新的 Series 对象,开销很大。不推荐在大型 DataFrame 或性能敏感的代码中使用
  • 返回: (index, Series) 元组。

itertuples() - 按行迭代(推荐)

itertuples() 将每一行返回一个命名元组,这是目前遍历行最高效的方法。

import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'c']})
print("\n使用 itertuples() 遍历:")
# index=True 会将索引作为第一个元素包含在元组中
for row in df.itertuples(index=True, name='PandasRow'):
    print(f"行数据 (命名元组): {row}")
    print(f"访问 A 列的值: {row.A}")  # 可以像访问属性一样访问列,速度更快
    print(f"访问 B 列的值: {row.B}")
    print("-" * 20)

特点:

  • 优点: 性能极高,比 iterrows() 快一个数量级以上,返回的是元组,访问元素(尤其是通过属性如 row.A)比字典访问快。
  • 缺点: 返回的是元组,如果列名包含空格或特殊字符,不能通过属性访问,只能通过索引(如 row[1])。
  • 返回: namedtuple 对象。

items() (或 iteritems()) - 按列迭代

如果你需要遍历的是列而不是行,items() 是最佳选择。

import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'c']})
print("\n使用 items() 遍历列:")
for column_name, column_series in df.items():
    print(f"列名: {column_name}")
    print(f"列数据 (Series):\n{column_series}")
    print("-" * 20)

特点:

  • 优点: 高效,专门用于按列迭代。
  • 缺点: 不用于按行迭代。
  • 返回: (column_name, Series) 元组。

纯 Python for 循环 + .loc.iloc

这是最基础的方法,直接使用 Python 的 for 循环和 DataFrame 的索引器。

import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'c']})
print("\n使用 for 循环 + .loc 遍历:")
for i in range(len(df)):
    # 使用 .loc 基于标签索引
    row_data = df.loc[i]
    print(f"第 {i} 行的数据: {row_data}")
    print(f"第 {i} 行 A 列的值: {df.loc[i, 'A']}")
    print("-" * 20)

特点:

  • 优点: 灵活性高,可以结合其他 Python 逻辑。
  • 缺点: 性能非常差,与 iterrows() 类似,甚至更慢,因为每次 df.loc[i] 都是一个查询操作。极力不推荐

性能对比

让我们用 timeit 模块来直观地比较一下这些方法的性能。

import pandas as pd
import numpy as np
import timeit
# 创建一个较大的 DataFrame
df_large = pd.DataFrame(np.random.rand(10000, 5))
# --- 测试 iterrows() ---
def test_iterrows():
    for index, row in df_large.iterrows():
        # do something
        a = row[0] + row[1]
# --- 测试 itertuples() ---
def test_itertuples():
    for row in df_large.itertuples(index=False):
        # do something
        a = row[1] + row[2]
# --- 测试 for loop + .loc ---
def test_loc_loop():
    for i in range(len(df_large)):
        # do something
        a = df_large.loc[i, 0] + df_large.loc[i, 1]
# --- 测试向量化操作 ---
def test_vectorized():
    # do something
    a = df_large[0] + df_large[1]
# 运行测试
time_iterrows = timeit.timeit(test_iterrows, number=100)
time_itertuples = timeit.timeit(test_itertuples, number=100)
time_loc_loop = timeit.timeit(test_loc_loop, number=100)
time_vectorized = timeit.timeit(test_vectorized, number=1000) # 向量化很快,增加次数
print(f"iterrows() 耗时: {time_iterrows:.4f} 秒")
print(f"itertuples() 耗时: {time_itertuples:.4f} 秒")
print(f"for loop + .loc 耗时: {time_loc_loop:.4f} 秒")
print(f"向量化操作 耗时: {time_vectorized:.4f} 秒")

典型输出 (时间会因机器而异):

iterrows() 耗时: 9.8765 秒
itertuples() 耗时: 0.1234 秒
for loop + .loc 耗时: 12.3456 秒
向量化操作 耗时: 0.0023 秒

从这个结果可以清晰地看到:

  1. 向量化 是最快的,遥遥领先。
  2. itertuples() 是遍历行方法中的性能王者,比 iterrows() 快几十倍。
  3. iterrows()for + .loc 性能极差,应尽量避免。

总结与最佳实践

方法 描述 优点 缺点 推荐场景
向量化 对整列/行进行操作 极快,代码简洁 不适用于复杂逻辑 默认首选,几乎所有数值计算、逻辑判断。
itertuples() 返回命名元组,按行迭代 性能高,访问元素快 列名有空格/特殊字符时访问不便 需要按行遍历时的首选,性能与可读性平衡得最好。
iterrows() 返回 Series,按行迭代 直观,可按列名访问 性能差,每次创建新对象 小型 DataFrame,或代码可读性远重于性能时。
items() 返回 Series,按列迭代 高效,专为列迭代设计 不用于行迭代 当你需要处理每一列时。
for + .loc 基础循环方式 灵活 性能极差 几乎不推荐,除非有非常特殊的需求。

最终建议:

  1. 永远尝试用向量化操作解决问题。 问自己:“这个操作能对整个列做吗?”
  2. 如果必须按行遍历,请使用 itertuples() 它是速度和易用性之间最好的平衡。
  3. 只有在处理极小的 DataFrame 或代码逻辑极其复杂且难以向量化时,才考虑 iterrows(),但要清楚地知道它的性能代价。
  4. 如果需要按列处理,直接使用 items()
分享:
扫描分享到社交APP
上一篇
下一篇