如何遍历 Pandas DataFrame 中的行?
- 2024-11-15 08:36:00
- admin 原创
- 25
问题描述:
我有一个熊猫数据框,df
:
c1 c2
0 10 100
1 11 110
2 12 120
如何迭代此数据框的行?对于每一行,我想通过列名访问其元素(单元格中的值)。例如:
for row in df.rows:
print(row['c1'], row['c2'])
我发现了一个类似的问题,建议使用以下任一项:
date, row in df.T.iteritems():
* ```
for row in df.iterrows():
但我不明白该row
对象是什么以及如何使用它。
解决方案 1:
DataFrame.iterrows
是一个产生索引和行(作为一个系列)的生成器:
import pandas as pd
df = pd.DataFrame({'c1': [10, 11, 12], 'c2': [100, 110, 120]})
df = df.reset_index() # make sure indexes pair with number of rows
for index, row in df.iterrows():
print(row['c1'], row['c2'])
10 100
11 110
12 120
文件中的强制性免责声明
遍历 pandas 对象通常很慢。在许多情况下,不需要手动遍历行,可以使用以下方法之一来避免:
寻找矢量化解决方案:许多操作可以使用内置方法或 NumPy 函数、(布尔)索引等执行。
当你有一个函数无法一次性处理整个 DataFrame/Series 时,最好使用
apply()
而不是迭代值。请参阅函数应用文档。如果您需要对值进行迭代操作,但性能很重要,请考虑使用 cython 或 numba 编写内部循环。请参阅增强性能部分,了解此方法的一些示例。
如果您有兴趣了解更多,本主题中的其他答案将更深入地探讨 iter* 函数的替代方案。
解决方案 2:
如何在 Pandas 中迭代 DataFrame 中的行
答案:不要*!
Pandas 中的迭代是一种反模式,只有在用尽所有其他选项时才应该这样做。您不应该将iter
名称中带有“ ”的任何函数用于超过几千行的数据,否则您将不得不习惯大量的等待。
您想打印 DataFrame 吗?使用DataFrame.to_string()
。
您想计算某些内容吗?在这种情况下,请按以下顺序搜索方法(列表从此处修改而来):
矢量化
Cython例程
列表推导(原始
for
循环)DataFrame.apply()
:i)可以在 Cython 中执行的缩减,ii)在 Python 空间中迭代items()
iteritems()
(自 v1.5.0 起已弃用)DataFrame.itertuples()
DataFrame.iterrows()
iterrows
和itertuples
(在该问题的答案中都获得了很多选票)应该在极少数情况下使用,例如生成用于顺序处理的行对象/名称元组,这实际上是这些函数唯一有用的地方。
诉诸权威
迭代的文档页面上有一个巨大的红色警告框,上面写着:
遍历 pandas 对象通常比较慢。在许多情况下,不需要手动遍历行 [...]。
实际上,这比“不要”要复杂一些。
df.iterrows()
是这个问题的正确答案,但“矢量化你的操作”是更好的答案。我承认,有些情况下迭代是无法避免的(例如,某些操作的结果取决于前一行的计算值)。但是,需要对库有一定的了解才能知道什么时候需要迭代。如果你不确定是否需要迭代解决方案,那么你可能不需要。PS:要了解有关我写这个答案的理由的更多信息,请跳到最下面。
比循环更快:矢量化、Cython
大量基本操作和计算都由 pandas “矢量化”(通过 NumPy 或 Cythonized 函数)。这包括算术、比较、(大多数)归约、重塑(例如旋转)、连接和 groupby 操作。查看基本功能文档,找到适合您问题的矢量化方法。
如果不存在,请随意使用自定义Cython 扩展来编写自己的扩展。
下一个最好的东西:列表推导*
如果 1) 没有可用的矢量化解决方案,2) 性能很重要,但并不重要到需要费力地对代码进行 cython 化,3) 您正在尝试对代码执行元素转换,那么列表推导应该是您的下一个选择。有大量证据表明,列表推导对于许多常见的 Pandas 任务来说足够快(有时甚至更快)。
公式很简单,
# Iterating over one column - `f` is some function that processes your data
result = [f(x) for x in df['col']]
# Iterating over two columns, use `zip`
result = [f(x, y) for x, y in zip(df['col1'], df['col2'])]
# Iterating over multiple columns - same data type
result = [f(row[0], ..., row[n]) for row in df[['col1', ...,'coln']].to_numpy()]
# Iterating over multiple columns - differing data type
result = [f(row[0], ..., row[n]) for row in zip(df['col1'], ..., df['coln'])]
如果您可以将业务逻辑封装到函数中,则可以使用列表推导来调用它。您可以通过原始 Python 代码的简单性和速度来实现任意复杂的事情。
注意事项
列表推导假设您的数据易于处理 - 这意味着您的数据类型是一致的并且没有 NaN,但这并不能总是得到保证。
第一个更明显,但是在处理 NaN 时,如果存在内置的 pandas 方法,则优先使用它们(因为它们具有更好的极端情况处理逻辑),或者确保您的业务逻辑包含适当的 NaN 处理逻辑。
处理混合数据类型时,您应该迭代
zip(df['A'], df['B'], ...)
而不是迭代df[['A', 'B']].to_numpy()
,因为后者会隐式地将数据转换为最常见的类型。例如,如果 A 是数字而 B 是字符串,to_numpy()
则会将整个数组转换为字符串,这可能不是您想要的。幸运的是,zip
将您的列合并在一起是解决此问题的最直接的解决方法。
*您的里程可能会因上述注意事项部分所述的原因而有所不同。
一个明显的例子
让我们通过添加两个 pandas 列的简单示例来演示差异A + B
。这是一个可矢量化的操作,因此很容易对比上述方法的性能。
基准测试代码,供您参考。底部的行测量了一个用 numpandas 编写的函数,numpandas 是 Pandas 的一种风格,它与 NumPy 大量混合,以发挥最大性能。除非您知道自己在做什么,否则应避免编写 numpandas 代码。尽可能坚持使用 API(即,优先vec
于vec_numpy
)。
不过,我应该指出,事情并不总是如此简单。有时,“哪种方法最适合某项操作”的答案是“这取决于您的数据”。我的建议是,在确定一种方法之前,先在您的数据上测试不同的方法。
我的个人观点*
对 iter 系列的各种替代方案进行的大部分分析都是从性能的角度进行的。然而,在大多数情况下,您通常会处理一个合理大小的数据集(不超过几千或 10 万行),性能将排在解决方案的简单性/可读性之后。
这是我选择解决问题的方法时的个人偏好。
对于新手来说:
矢量化(如果可能);
apply()
; 列表推导;itertuples()
/iteritems()
;iterrows()
; Cython
对于更有经验的人来说:
矢量化(如果可能);;
apply()
列表推导;Cython;itertuples()
/iteritems()
;iterrows()
对于可以矢量化的问题,矢量化是最常用的方法。始终寻求矢量化!如有疑问,请查阅文档,或在 Stack Overflow 上查找有关您的特定任务的现有问题。
我确实倾向于在很多帖子中继续谈论apply
它有多糟糕,但我承认初学者更容易理解它在做什么。此外,我的这篇帖子apply
中解释了它的相当多用例。
Cython 在列表中排名较低,因为它需要更多时间和精力才能正确完成。通常,您永远不需要用 pandas 编写要求这种性能水平的代码,因为列表推导式无法满足这种性能水平。
对于任何个人观点,请谨慎对待!
进一步阅读
10 分钟了解 Pandas及其基本功能- 有用的链接,向您介绍 Pandas 及其矢量化*/cythonized 函数库。
增强性能- 增强标准 Pandas 操作的文档入门指南
Pandas 中的 for 循环真的很糟糕吗?我什么时候应该关心它? ——我详细撰写了一篇关于列表推导及其对各种操作(主要是涉及非数字数据的操作)的适用性的文章
我什么时候应该(不)想在我的代码中使用 pandas apply()? -
apply
很慢(但不像系列那么慢iter*
。然而,在某些情况下,人们可以(或应该)将其视为apply
一种严肃的替代方案,特别是在某些GroupBy
操作中)。Pandas 字符串方法是“矢量化的”,因为它们是在系列上指定的,但对每个元素进行操作。底层机制仍然是迭代的,因为字符串操作本质上很难矢量化。
我为什么写这个答案
我从新用户那里注意到一个常见趋势是问“我如何迭代我的 df 来做 X?”这样的问题。显示iterrows()
在for
循环内执行某项操作时调用的代码。原因如下。尚未了解矢量化概念的库新用户可能会将解决其问题的代码设想为迭代数据来做某事。由于不知道如何迭代 DataFrame,他们做的第一件事就是谷歌搜索,最后找到了这个问题。然后他们看到公认的答案告诉他们如何做,他们闭上眼睛运行这段代码,而从未首先质疑迭代是否是正确的做法。
这个答案的目的是帮助新用户理解迭代不一定是解决所有问题的办法,可能存在更好、更快、更惯用的解决方案,值得花时间去探索。我并不是想挑起迭代与矢量化的战争,但我希望新用户在使用这个库开发问题解决方案时能够了解情况。
最后...用 TLDR 来总结这篇文章
解决方案 3:
首先考虑一下您是否真的需要迭代DataFrame 中的行。请参阅cs95 的答案以了解替代方案。
如果您仍需要迭代行,则可以使用以下方法。请注意 其他答案中未提及的一些重要注意事项。
数据框.iterrows()
for index, row in df.iterrows():
print(row["c1"], row["c2"])
DataFrame.itertuples()
for row in df.itertuples(index=True, name='Pandas'):
print(row.c1, row.c2)
itertuples()
应该比iterrows()
但请注意,根据文档(目前为 pandas 0.24.2):
iterrows:
dtype
行与行之间可能不匹配
因为 iterrows 为每一行返回一个 Series,所以它不会保留行间的 dtype(DataFrames 的 dtype 会保留列间的 dtype)。为了在迭代行时保留 dtype,最好使用 itertuples(),它返回值的命名元组,并且通常比 iterrows() 快得多
iterrows:不修改行
您永远不应该修改您正在迭代的内容。这不能保证在所有情况下都有效。根据数据类型,迭代器返回的是副本而不是视图,并且写入它不会产生任何效果。
改用DataFrame.apply():
new_df = df.apply(lambda x: x * 2, axis=1)
迭代元组:
如果列名是无效的 Python 标识符、重复或以下划线开头,则列名将重命名为位置名称。如果列数较多(>255),则返回常规元组。
有关更多详细信息,请参阅有关迭代的 pandas 文档。
解决方案 4:
您应该使用df.iterrows()
。尽管逐行迭代并不是特别高效,因为Series
必须创建对象。
解决方案 5:
虽然iterrows()
是一个不错的选择,但有时itertuples()
可能会更快:
df = pd.DataFrame({'a': randn(1000), 'b': randn(1000),'N': randint(100, 1000, (1000)), 'x': 'x'})
%timeit [row.a * 2 for idx, row in df.iterrows()]
# => 10 loops, best of 3: 50.3 ms per loop
%timeit [row[1] * 2 for row in df.itertuples()]
# => 1000 loops, best of 3: 541 µs per loop
解决方案 6:
您可以df.iloc
按如下方式使用该函数:
for i in range(0, len(df)):
print(df.iloc[i]['c1'], df.iloc[i]['c2'])
解决方案 7:
您还可以使用df.apply()
函数来迭代行并访问多列。
文档:DataFrame.apply()
def valuation_formula(x, y):
return x * y * 0.5
df['price'] = df.apply(lambda row: valuation_formula(row['x'], row['y']), axis=1)
注意axis=1
这里与 相同axis='columns'
,并且用于将函数应用于每一行而不是每一列。如果未指定,则默认行为是将函数应用于每一列。
解决方案 8:
如何高效迭代
如果你真的需要迭代 Pandas DataFrame
,那么你可能要避免使用iterrows()
。有不同的方法,通常的方法iterrows()
远非最佳。itertuples **()
` 可以快 100 倍。**
简而言之:
一般情况下,使用
df.itertuples(name=None)
。特别是当您的列数固定且少于 255 列时。请参阅下面的项目 (3)。否则,请使用
df.itertuples()
,除非您的列包含特殊字符(例如空格或 )-
。请参阅下面的项目符号 (2)。itertuples()
即使您的数据框中有奇怪的列,也可以使用下面的最后一个示例。请参阅下面的项目符号 (4)。iterrows()
仅当您无法使用任何先前的解决方案时才使用。请参阅下面的项目 (1)。
迭代 Pandas 中的行的不同方法DataFrame
:
首先,为了在下面的所有示例中使用,生成一个具有一百万行和四列的随机数据框,如下所示:
df = pd.DataFrame(np.random.randint(0, 100, size=(1000000, 4)), columns=list('ABCD'))
print(df)
所有这些示例的输出都显示在底部。
通常的做法
iterrows()
很方便,但是速度很慢:
start_time = time.clock()
result = 0
for _, row in df.iterrows():
result += max(row['B'], row['C'])
total_elapsed_time = round(time.clock() - start_time, 2)
print("1. Iterrows done in {} seconds, result = {}".format(total_elapsed_time, result))
使用默认命名
itertuples()
已经快得多,但它不适用于以下列名My Col-Name is very Strange
(如果列重复或列名不能简单地转换为 Python 变量名,则应避免使用此方法):
start_time = time.clock()
result = 0
for row in df.itertuples(index=False):
result += max(row.B, row.C)
total_elapsed_time = round(time.clock() - start_time, 2)
print("2. Named Itertuples done in {} seconds, result = {}".format(total_elapsed_time, result))
使用无名称
itertuples()
设置name=None
甚至更快,但不太方便,因为您必须为每列定义一个变量。
start_time = time.clock()
result = 0
for(_, col1, col2, col3, col4) in df.itertuples(name=None):
result += max(col2, col3)
total_elapsed_time = round(time.clock() - start_time, 2)
print("3. Itertuples done in {} seconds, result = {}".format(total_elapsed_time, result))
最后,使用多价比
itertuples()
前面的例子慢,但是您不必为每列定义一个变量,并且它可以与诸如的列名一起使用My Col-Name is very Strange
。
start_time = time.clock()
result = 0
for row in df.itertuples(index=False):
result += max(row[df.columns.get_loc('B')], row[df.columns.get_loc('C')])
total_elapsed_time = round(time.clock() - start_time, 2)
print("4. Polyvalent Itertuples working even with special characters in the column name done in {} seconds, result = {}".format(total_elapsed_time, result))
以上所有代码和示例的输出:
A B C D
0 41 63 42 23
1 54 9 24 65
2 15 34 10 9
3 39 94 82 97
4 4 88 79 54
... .. .. .. ..
999995 48 27 4 25
999996 16 51 34 28
999997 1 39 61 14
999998 66 51 27 70
999999 51 53 47 99
[1000000 rows x 4 columns]
1. Iterrows done in 104.96 seconds, result = 66151519
2. Named Itertuples done in 1.26 seconds, result = 66151519
3. Itertuples done in 0.94 seconds, result = 66151519
4. Polyvalent Itertuples working even with special characters in the column name done in 2.94 seconds, result = 66151519
@Gabriel Staples 在他的回答中对这些结果进行了绘图:
参见
iterrows()
这篇文章对和进行了非常有趣的比较itertuples()
解决方案 9:
我正在寻找如何迭代行 和 列并在这里结束:
for i, row in df.iterrows():
for j, column in row.iteritems():
print(column)
解决方案 10:
我们有多种选择来做同样的事情,并且很多人已经分享了他们的答案。
我发现以下两种方法简单且有效:
数据框.iterrows()
DataFrame.itertuples()
例子:
import pandas as pd
inp = [{'c1':10, 'c2':100}, {'c1':11,'c2':110}, {'c1':12,'c2':120}]
df = pd.DataFrame(inp)
print (df)
# With the iterrows method
for index, row in df.iterrows():
print(row["c1"], row["c2"])
# With the itertuples method
for row in df.itertuples(index=True, name='Pandas'):
print(row.c1, row.c2)
注意:itertuples() 应该比 iterrows() 更快
解决方案 11:
关键要点:
使用矢量化。
对您的代码进行速度分析!不要因为您认为某样东西更快就假设它更快;对其进行速度分析并证明它更快。结果可能会让您大吃一惊。
如何DataFrame
在不迭代的情况下迭代 Pandas
经过几周的努力,我得出了以下答案:
以下是13 种迭代 Pandas 的技术DataFrame
。如您所见,所需时间差异很大。最快的技术比最慢的技术快约1363 倍!正如 @cs95 在这里所说,关键要点是不要迭代!改用矢量化(“数组编程”)。这实际上意味着您应该直接在数学公式中使用数组,而不是尝试手动迭代数组。当然,底层对象必须支持这一点,但 Numpy 和 Pandas 都支持。
在 Pandas 中,有很多方法可以使用矢量化,您可以在下面的图表和我的示例代码中看到。直接使用数组时,底层循环仍然会发生,但(我认为)是在非常优化的底层 C 代码中,而不是通过原始 Python。
结果
测试了 13 种技术,编号为 1 至 13。技术编号和名称位于每个栏目下方。总计算时间位于每个栏目上方。下方是乘数,显示它比最右边最快的技术花费的时间长多少:
来自pandas_dataframe_iteration_vs_vectorization_vs_list_comprehension_speed_tests.svg
我的eRCaGuy_hello_world repo(由此代码生成)。
概括
您真正需要的是列表理解和矢量化(可能带有布尔索引)。
使用列表理解(好)和矢量化(最好)。我认为纯矢量化总是可行的,但在复杂的计算中可能需要额外的工作。在这个答案中搜索“布尔索引”、“布尔数组”和“布尔掩码”(这三个是同一个东西),看看一些更复杂的情况,其中可以使用纯矢量化。
以下是 13 种技巧,按从快到慢的顺序列出。我建议永远不要使用最后(最慢)的 3 到 4 种技巧。
技术8:
8_pure_vectorization__with_df.loc[]_boolean_array_indexing_for_if_statment_corner_case
技术6:
6_vectorization__with_apply_for_if_statement_corner_case
技术7:
7_vectorization__with_list_comprehension_for_if_statment_corner_case
技术11:
11_list_comprehension_w_zip_and_direct_variable_assignment_calculated_in_place
技术10:
10_list_comprehension_w_zip_and_direct_variable_assignment_passed_to_func
技术12:
12_list_comprehension_w_zip_and_row_tuple_passed_to_func
技术5:
5_itertuples_in_for_loop
技术13:
13_list_comprehension_w__to_numpy__and_direct_variable_assignment_passed_to_func
技术9:
9_apply_function_with_lambda
技术 1:
1_raw_for_loop_using_regular_df_indexing
技术2:
2_raw_for_loop_using_df.loc[]_indexing
技术4:
4_iterrows_in_for_loop
技术3:
3_raw_for_loop_using_df.iloc[]_indexing
经验法则:
永远不要使用技术 3、4 和 2。它们非常慢,没有任何优势。但请记住:不是索引技术(例如
.loc[]
或).iloc[]
使这些技术变得不好,而是它们所处的循环使for
它们变得不好!.loc[]
例如,我使用最快的(纯矢量化)方法!因此,以下是 3 种最慢的技术,它们永远不应该使用:3_raw_for_loop_using_df.iloc[]_indexing
4_iterrows_in_for_loop
2_raw_for_loop_using_df.loc[]_indexing
技术
1_raw_for_loop_using_regular_df_indexing
也永远不应该被使用,但是如果你要使用原始 for 循环,它比其他的更快。函数
.apply()
(9_apply_function_with_lambda
) 还可以,但一般来说,我也会避免使用它。然而, Technique 的6_vectorization__with_apply_for_if_statement_corner_case
表现确实比 更好7_vectorization__with_list_comprehension_for_if_statment_corner_case
,这很有趣。列表推导很棒!它不是最快的,但易于使用且非常快!
它的优点在于它可以与任何旨在处理单个值或数组值的函数一起使用。这意味着您可以
if
在函数内部使用非常复杂的语句和内容。因此,这里的权衡是,它通过使用外部计算函数为您提供了真正可读和可重用的代码,同时仍为您提供了极快的速度!
矢量化是最快和最好的方法,当方程式简单时,您应该使用它。您可以选择只对方程式中较复杂的部分使用
.apply()
或列表推导式,同时仍然可以轻松地对其余部分使用矢量化。纯矢量化绝对是最快和最好的,如果您愿意付出努力使其发挥作用,就应该使用它。
对于简单的情况,您应该使用它。
对于复杂的情况、语句等,也可以通过布尔索引
if
使纯矢量化发挥作用,但这样做会增加额外的工作量并降低可读性。因此,您可以选择仅对那些边缘情况使用列表理解(通常是最好的)或.apply()(通常较慢,但并非总是如此),同时仍然使用矢量化进行其余计算。例如:参见技术和。7_vectorization__with_list_comprehension_for_if_statment_corner_case
`6_vectorization__with_apply_for_if_statement_corner_case`
测试数据
假设我们有以下 Pandas DataFrame。它有 200 万行,每列 4 列(A
、B
、C
和D
),每列都有从-1000
到 的随机值1000
:
df =
A B C D
0 -365 842 284 -942
1 532 416 -102 888
2 397 321 -296 -616
3 -215 879 557 895
4 857 701 -157 480
... ... ... ... ...
1999995 -101 -233 -377 -939
1999996 -989 380 917 145
1999997 -879 333 -372 -970
1999998 738 982 -743 312
1999999 -306 -103 459 745
我像这样生成了这个 DataFrame:
import numpy as np
import pandas as pd
# Create an array (numpy list of lists) of fake data
MIN_VAL = -1000
MAX_VAL = 1000
# NUM_ROWS = 10_000_000
NUM_ROWS = 2_000_000 # default for final tests
# NUM_ROWS = 1_000_000
# NUM_ROWS = 100_000
# NUM_ROWS = 10_000 # default for rapid development & initial tests
NUM_COLS = 4
data = np.random.randint(MIN_VAL, MAX_VAL, size=(NUM_ROWS, NUM_COLS))
# Now convert it to a Pandas DataFrame with columns named "A", "B", "C", and "D"
df_original = pd.DataFrame(data, columns=["A", "B", "C", "D"])
print(f"df =
{df_original}")
测试方程/计算
我想证明所有这些技术在非平凡函数或方程上都是可行的,所以我故意让它们计算的方程要求:
if
语句DataFrame 中多列的数据
DataFrame 中多行的数据
我们将为每一行计算的方程是这样的。我随意编造了它,但我认为它包含足够的复杂性,您将能够扩展我所做的工作,以在 Pandas 中执行您想要的任何方程式,并进行完全矢量化:
在 Python 中,上述等式可以写成如下形式:
# Calculate and return a new value, `val`, by performing the following equation:
val = (
2 * A_i_minus_2
+ 3 * A_i_minus_1
+ 4 * A
+ 5 * A_i_plus_1
# Python ternary operator; don't forget parentheses around the entire
# ternary expression!
+ ((6 * B) if B > 0 else (60 * B))
+ 7 * C
- 8 * D
)
或者,你可以这样写:
# Calculate and return a new value, `val`, by performing the following equation:
if B > 0:
B_new = 6 * B
else:
B_new = 60 * B
val = (
2 * A_i_minus_2
+ 3 * A_i_minus_1
+ 4 * A
+ 5 * A_i_plus_1
+ B_new
+ 7 * C
- 8 * D
)
以上任何一种都可以包装到函数中。例如:
def calculate_val(
A_i_minus_2,
A_i_minus_1,
A,
A_i_plus_1,
B,
C,
D):
val = (
2 * A_i_minus_2
+ 3 * A_i_minus_1
+ 4 * A
+ 5 * A_i_plus_1
# Python ternary operator; don't forget parentheses around the
# entire ternary expression!
+ ((6 * B) if B > 0 else (60 * B))
+ 7 * C
- 8 * D
)
return val
技术
完整代码可供下载,并在我的eRCaGuy_hello_worldpython/pandas_dataframe_iteration_vs_vectorization_vs_list_comprehension_speed_tests.py
repo中的文件中运行。
以下是所有 13 种技术的代码:
技术 1:
1_raw_for_loop_using_regular_df_indexing
val = [np.NAN]*len(df)
for i in range(len(df)):
if i < 2 or i > len(df)-2:
continue
val[i] = calculate_val(
df["A"][i-2],
df["A"][i-1],
df["A"][i],
df["A"][i+1],
df["B"][i],
df["C"][i],
df["D"][i],
)
df["val"] = val # put this column back into the dataframe
技术2:
2_raw_for_loop_using_df.loc[]_indexing
val = [np.NAN]*len(df)
for i in range(len(df)):
if i < 2 or i > len(df)-2:
continue
val[i] = calculate_val(
df.loc[i-2, "A"],
df.loc[i-1, "A"],
df.loc[i, "A"],
df.loc[i+1, "A"],
df.loc[i, "B"],
df.loc[i, "C"],
df.loc[i, "D"],
)
df["val"] = val # put this column back into the dataframe
技术3:
3_raw_for_loop_using_df.iloc[]_indexing
# column indices
i_A = 0
i_B = 1
i_C = 2
i_D = 3
val = [np.NAN]*len(df)
for i in range(len(df)):
if i < 2 or i > len(df)-2:
continue
val[i] = calculate_val(
df.iloc[i-2, i_A],
df.iloc[i-1, i_A],
df.iloc[i, i_A],
df.iloc[i+1, i_A],
df.iloc[i, i_B],
df.iloc[i, i_C],
df.iloc[i, i_D],
)
df["val"] = val # put this column back into the dataframe
技术4:
4_iterrows_in_for_loop
val = [np.NAN]*len(df)
for index, row in df.iterrows():
if index < 2 or index > len(df)-2:
continue
val[index] = calculate_val(
df["A"][index-2],
df["A"][index-1],
row["A"],
df["A"][index+1],
row["B"],
row["C"],
row["D"],
)
df["val"] = val # put this column back into the dataframe
对于接下来的所有示例,我们必须首先准备数据框,添加具有前一个值和下一个值的列:A_(i-2)
、A_(i-1)
和A_(i+1)
。DataFrame 中的这些列将分别命名为A_i_minus_2
、A_i_minus_1
和A_i_plus_1
:
df_original["A_i_minus_2"] = df_original["A"].shift(2) # val at index i-2
df_original["A_i_minus_1"] = df_original["A"].shift(1) # val at index i-1
df_original["A_i_plus_1"] = df_original["A"].shift(-1) # val at index i+1
# Note: to ensure that no partial calculations are ever done with rows which
# have NaN values due to the shifting, we can either drop such rows with
# `.dropna()`, or set all values in these rows to NaN. I'll choose the latter
# so that the stats that will be generated with the techniques below will end
# up matching the stats which were produced by the prior techniques above. ie:
# the number of rows will be identical to before.
#
# df_original = df_original.dropna()
df_original.iloc[:2, :] = np.NAN # slicing operators: first two rows,
# all columns
df_original.iloc[-1:, :] = np.NAN # slicing operators: last row, all columns
运行上面的矢量化代码来生成这 3 个新列总共花费了0.044961 秒。
现在来讨论其余的技术:
技术5:
5_itertuples_in_for_loop
val = [np.NAN]*len(df)
for row in df.itertuples():
val[row.Index] = calculate_val(
row.A_i_minus_2,
row.A_i_minus_1,
row.A,
row.A_i_plus_1,
row.B,
row.C,
row.D,
)
df["val"] = val # put this column back into the dataframe
技术6:
6_vectorization__with_apply_for_if_statement_corner_case
def calculate_new_column_b_value(b_value):
# Python ternary operator
b_value_new = (6 * b_value) if b_value > 0 else (60 * b_value)
return b_value_new
# In this particular example, since we have an embedded `if-else` statement
# for the `B` column, pure vectorization is less intuitive. So, first we'll
# calculate a new `B` column using
# **`apply()`**, then we'll use vectorization for the rest.
df["B_new"] = df["B"].apply(calculate_new_column_b_value)
# OR (same thing, but with a lambda function instead)
# df["B_new"] = df["B"].apply(lambda x: (6 * x) if x > 0 else (60 * x))
# Now we can use vectorization for the rest. "Vectorization" in this case
# means to simply use the column series variables in equations directly,
# without manually iterating over them. Pandas DataFrames will handle the
# underlying iteration automatically for you. You just focus on the math.
df["val"] = (
2 * df["A_i_minus_2"]
+ 3 * df["A_i_minus_1"]
+ 4 * df["A"]
+ 5 * df["A_i_plus_1"]
+ df["B_new"]
+ 7 * df["C"]
- 8 * df["D"]
)
技术7:
7_vectorization__with_list_comprehension_for_if_statment_corner_case
# In this particular example, since we have an embedded `if-else` statement
# for the `B` column, pure vectorization is less intuitive. So, first we'll
# calculate a new `B` column using **list comprehension**, then we'll use
# vectorization for the rest.
df["B_new"] = [
calculate_new_column_b_value(b_value) for b_value in df["B"]
]
# Now we can use vectorization for the rest. "Vectorization" in this case
# means to simply use the column series variables in equations directly,
# without manually iterating over them. Pandas DataFrames will handle the
# underlying iteration automatically for you. You just focus on the math.
df["val"] = (
2 * df["A_i_minus_2"]
+ 3 * df["A_i_minus_1"]
+ 4 * df["A"]
+ 5 * df["A_i_plus_1"]
+ df["B_new"]
+ 7 * df["C"]
- 8 * df["D"]
)
技术8:
8_pure_vectorization__with_df.loc[]_boolean_array_indexing_for_if_statment_corner_case
这使用布尔索引(又称布尔掩码)来完成等式中语句的等效操作if
。这样,可以对整个方程式使用纯矢量化,从而最大限度地提高性能和速度。
# If statement to evaluate:
#
# if B > 0:
# B_new = 6 * B
# else:
# B_new = 60 * B
#
# In this particular example, since we have an embedded `if-else` statement
# for the `B` column, we can use some boolean array indexing through
# `df.loc[]` for some pure vectorization magic.
#
# Explanation:
#
# Long:
#
# The format is: `df.loc[rows, columns]`, except in this case, the rows are
# specified by a "boolean array" (AKA: a boolean expression, list of
# booleans, or "boolean mask"), specifying all rows where `B` is > 0. Then,
# only in that `B` column for those rows, set the value accordingly. After
# we do this for where `B` is > 0, we do the same thing for where `B`
# is <= 0, except with the other equation.
#
# Short:
#
# For all rows where the boolean expression applies, set the column value
# accordingly.
#
# GitHub CoPilot first showed me this `.loc[]` technique.
# See also the official documentation:
# https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.loc.html
#
# ===========================
# 1st: handle the > 0 case
# ===========================
df["B_new"] = df.loc[df["B"] > 0, "B"] * 6
#
# ===========================
# 2nd: handle the <= 0 case, merging the results into the
# previously-created "B_new" column
# ===========================
# - NB: this does NOT work; it overwrites and replaces the whole "B_new"
# column instead:
#
# df["B_new"] = df.loc[df["B"] <= 0, "B"] * 60
#
# This works:
df.loc[df["B"] <= 0, "B_new"] = df.loc[df["B"] <= 0, "B"] * 60
# Now use normal vectorization for the rest.
df["val"] = (
2 * df["A_i_minus_2"]
+ 3 * df["A_i_minus_1"]
+ 4 * df["A"]
+ 5 * df["A_i_plus_1"]
+ df["B_new"]
+ 7 * df["C"]
- 8 * df["D"]
)
技术9:
9_apply_function_with_lambda
df["val"] = df.apply(
lambda row: calculate_val(
row["A_i_minus_2"],
row["A_i_minus_1"],
row["A"],
row["A_i_plus_1"],
row["B"],
row["C"],
row["D"]
),
axis='columns' # same as `axis=1`: "apply function to each row",
# rather than to each column
)
技术10:
10_list_comprehension_w_zip_and_direct_variable_assignment_passed_to_func
df["val"] = [
# Note: you *could* do the calculations directly here instead of using a
# function call, so long as you don't have indented code blocks such as
# sub-routines or multi-line if statements.
#
# I'm using a function call.
calculate_val(
A_i_minus_2,
A_i_minus_1,
A,
A_i_plus_1,
B,
C,
D
) for A_i_minus_2, A_i_minus_1, A, A_i_plus_1, B, C, D
in zip(
df["A_i_minus_2"],
df["A_i_minus_1"],
df["A"],
df["A_i_plus_1"],
df["B"],
df["C"],
df["D"]
)
]
技术11:
11_list_comprehension_w_zip_and_direct_variable_assignment_calculated_in_place
df["val"] = [
2 * A_i_minus_2
+ 3 * A_i_minus_1
+ 4 * A
+ 5 * A_i_plus_1
# Python ternary operator; don't forget parentheses around the entire
# ternary expression!
+ ((6 * B) if B > 0 else (60 * B))
+ 7 * C
- 8 * D
for A_i_minus_2, A_i_minus_1, A, A_i_plus_1, B, C, D
in zip(
df["A_i_minus_2"],
df["A_i_minus_1"],
df["A"],
df["A_i_plus_1"],
df["B"],
df["C"],
df["D"]
)
]
技术12:
12_list_comprehension_w_zip_and_row_tuple_passed_to_func
df["val"] = [
calculate_val(
row[0],
row[1],
row[2],
row[3],
row[4],
row[5],
row[6],
) for row
in zip(
df["A_i_minus_2"],
df["A_i_minus_1"],
df["A"],
df["A_i_plus_1"],
df["B"],
df["C"],
df["D"]
)
]
技术13:
13_list_comprehension_w__to_numpy__and_direct_variable_assignment_passed_to_func
df["val"] = [
# Note: you *could* do the calculations directly here instead of using a
# function call, so long as you don't have indented code blocks such as
# sub-routines or multi-line if statements.
#
# I'm using a function call.
calculate_val(
A_i_minus_2,
A_i_minus_1,
A,
A_i_plus_1,
B,
C,
D
) for A_i_minus_2, A_i_minus_1, A, A_i_plus_1, B, C, D
# Note: this `[[...]]` double-bracket indexing is used to select a
# subset of columns from the dataframe. The inner `[]` brackets
# create a list from the column names within them, and the outer
# `[]` brackets accept this list to index into the dataframe and
# select just this list of columns, in that order.
# - See the official documentation on it here:
# https://pandas.pydata.org/docs/user_guide/indexing.html#basics
# - Search for the phrase "You can pass a list of columns to [] to
# select columns in that order."
# - I learned this from this comment here:
# https://stackoverflow.com/questions/16476924/how-to-iterate-over-rows-in-a-dataframe-in-pandas/55557758#comment136020567_55557758
# - One of the **list comprehension** examples in this answer here
# uses `.to_numpy()` like this:
# https://stackoverflow.com/a/55557758/4561887
in df[[
"A_i_minus_2",
"A_i_minus_1",
"A",
"A_i_plus_1",
"B",
"C",
"D"
]].to_numpy() # NB: `.values` works here too, but is deprecated. See:
# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.values.html
]
以下是结果:
for
在 4 个循环技术中使用预移行
我想看看删除这个if
检查并使用 4 个循环技术中的预移位行for
是否会产生很大的效果:
if i < 2 or i > len(df)-2:
continue
...所以我创建了这个文件并做了这些修改:pandas_dataframe_iteration_vs_vectorization_vs_list_comprehension_speed_tests_mod.py
。在文件中搜索“MOD:”以找到 4 个新的修改技术。
它只略有改进。以下是这 17 种技术的结果,其中 4 种新技术_MOD_
的名称开头附近(紧跟其编号)有单词。这次超过 50 万行,而不是 2M:
更多信息.iterrtuples()
使用时实际上还有更多细微差别.itertuples()
。要深入了解其中的一些,请阅读@Romain Capron 的回答。这是我根据他的结果制作的条形图:
我为他的结果绘制的代码位于python/pandas_plot_bar_chart_better_GREAT_AUTOLABEL_DATA.py
我的eRCaGuy_hello_world仓库中。
未来工作
使用 Cython(将 Python 编译成 C 代码)或仅使用 Python 调用的原始 C 函数可能会更快,但我不会在这些测试中这样做。我只会研究和速度测试这些选项以实现重大优化。
我目前不了解 Cython,也不觉得有必要学习它。如上所示,只要正确使用纯矢量化,运行速度就非常快,仅需 0.1 秒即可处理 200万行,即每秒 2000 万行。
参考
大量官方 Pandas 文档,特别是
DataFrame
这里的文档:https ://pandas.pydata.org/pandas-docs/stable/reference/frame.html 。@cs95 给出了非常好的答案- 在这里我学到了如何使用列表推导来迭代 DataFrame。
关于的这个答案
itertuples()
,由@Romain Capron提供——我仔细研究了它并对其进行了编辑/格式化。所有这些都是我自己的代码,但我想指出的是,我与 GitHub Copilot(大部分)、Bing AI 和 ChatGPT 进行了数十次聊天,以便弄清楚其中的许多技术并在进行过程中调试我的代码。
Bing Chat 为我生成了漂亮的 LaTeX 公式,并显示以下提示。当然,我验证了输出:
将这段 Python 代码转换成漂亮的方程式,这样我就可以粘贴到 Stack Overflow 上:
val = ( 2 * A_i_minus_2 + 3 * A_i_minus_1 + 4 * A + 5 * A_i_plus_1 # Python ternary operator; don't forget parentheses around the entire ternary expression! + ((6 * B) if B > 0 else (60 * B)) + 7 * C - 8 * D )
参见
该答案也发布在我的个人网站上:https://gabrielstaples.com/python_iterate_over_pandas_dataframe/
https://en.wikipedia.org/wiki/Array_programming - 数组编程或“矢量化”:
在计算机科学中,数组编程是指允许一次性对整个值集应用运算的解决方案。此类解决方案通常用于科学和工程领域。
支持数组编程的现代编程语言(也称为向量或多维语言)经过专门设计,可将标量上的运算推广到透明地应用于向量、矩阵和高维数组。这些语言包括 APL、J、Fortran、MATLAB、Analytica、Octave、R、Cilk Plus、Julia、Perl 数据语言 (PDL)。在这些语言中,对整个数组进行操作的运算可以称为向量化运算,1无论它是否在实现向量指令的向量处理器上执行。
Pandas 中的 for 循环真的很糟糕吗?我什么时候应该关心它?
1. 我的答案
pandas iterrows 有性能问题吗?
1. 这个答案
1. 我在下面的评论:
> ...根据我的结果,我想说,这些是最好的方法,按以下顺序排列:
>
>
>
> 1. 矢量化,
> 2. 列表理解,
> 3. `.itertuples()`,
> 4. `.apply()`,
> 5. 原始`for`循环,
> 6. `.iterrows().`
> 我没有测试 Cython。
解决方案 12:
你可以编写自己的迭代器来实现namedtuple
from collections import namedtuple
def myiter(d, cols=None):
if cols is None:
v = d.values.tolist()
cols = d.columns.values.tolist()
else:
j = [d.columns.get_loc(c) for c in cols]
v = d.values[:, j].tolist()
n = namedtuple('MyTuple', cols)
for line in iter(v):
yield n(*line)
这直接与 相媲美pd.DataFrame.itertuples
。我的目标是以更高的效率完成相同的任务。
对于具有我的函数的给定数据框:
list(myiter(df))
[MyTuple(c1=10, c2=100), MyTuple(c1=11, c2=110), MyTuple(c1=12, c2=120)]
或者使用pd.DataFrame.itertuples
:
list(df.itertuples(index=False))
[Pandas(c1=10, c2=100), Pandas(c1=11, c2=110), Pandas(c1=12, c2=120)]
全面测试
我们测试使所有列可用并对列进行子集设置。
def iterfullA(d):
return list(myiter(d))
def iterfullB(d):
return list(d.itertuples(index=False))
def itersubA(d):
return list(myiter(d, ['col3', 'col4', 'col5', 'col6', 'col7']))
def itersubB(d):
return list(d[['col3', 'col4', 'col5', 'col6', 'col7']].itertuples(index=False))
res = pd.DataFrame(
index=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
columns='iterfullA iterfullB itersubA itersubB'.split(),
dtype=float
)
for i in res.index:
d = pd.DataFrame(np.random.randint(10, size=(i, 10))).add_prefix('col')
for j in res.columns:
stmt = '{}(d)'.format(j)
setp = 'from __main__ import d, {}'.format(j)
res.at[i, j] = timeit(stmt, setp, number=100)
res.groupby(res.columns.str[4:-1], axis=1).plot(loglog=True);
解决方案 13:
要循环所有行,dataframe
您可以使用:
for x in range(len(date_example.index)):
print date_example['Date'].iloc[x]
解决方案 14:
for ind in df.index:
print df['c1'][ind], df['c2'][ind]
解决方案 15:
有时有用的模式是:
# Borrowing @KutalmisB df example
df = pd.DataFrame({'col1': [1, 2], 'col2': [0.1, 0.2]}, index=['a', 'b'])
# The to_dict call results in a list of dicts
# where each row_dict is a dictionary with k:v pairs of columns:value for that row
for row_dict in df.to_dict(orient='records'):
print(row_dict)
其结果是:
{'col1':1.0, 'col2':0.1}
{'col1':2.0, 'col2':0.2}
解决方案 16:
更新:cs95 已更新他的答案,以包含纯 numpy 矢量化。您可以直接参考他的答案。
cs95 表明,Pandas 矢量化在使用数据框计算内容方面远远优于其他 Pandas 方法。
我想补充一点,如果你先将数据框转换为 NumPy 数组,然后使用矢量化,它甚至比 Pandas 数据框矢量化更快(其中包括将其转换回数据框系列的时间)。
如果将以下函数添加到 cs95 的基准代码中,这一点就会变得非常明显:
def np_vectorization(df):
np_arr = df.to_numpy()
return pd.Series(np_arr[:,0] + np_arr[:,1], index=df.index)
def just_np_vectorization(df):
np_arr = df.to_numpy()
return np_arr[:,0] + np_arr[:,1]
解决方案 17:
简而言之
如果可能的话使用矢量化
如果操作无法矢量化 - 使用列表推导
如果你需要一个代表整行的单个对象 - 使用 itertuples
如果上述方法太慢 - 尝试swifter.apply
如果还是太慢——尝试Cython程序
基准
解决方案 18:
为了循环 a 中的所有行dataframe
并方便地使用每行的值,可以将 转换为s。例如:namedtuples
`ndarray`
df = pd.DataFrame({'col1': [1, 2], 'col2': [0.1, 0.2]}, index=['a', 'b'])
迭代行:
for row in df.itertuples(index=False, name='Pandas'):
print np.asarray(row)
结果:
[ 1. 0.1]
[ 2. 0.2]
请注意,如果index=True
,索引将被添加为元组的第一个元素,这对于某些应用程序来说可能是不可取的。
解决方案 19:
有一种方法可以迭代抛出行,同时获得 DataFrame 作为返回值,而不是 Series。我没有看到任何人提到你可以将索引作为列表传递,以便将行作为 DataFrame 返回:
for i in range(len(df)):
row = df.iloc[[i]]
注意双括号的用法。这将返回一个只有一行的 DataFrame。
解决方案 20:
我建议使用df.at[row, column]
(源)来迭代所有熊猫单元。
例如:
for row in range(len(df)):
print(df.at[row, 'c1'], df.at[row, 'c2'])
输出将是:
10 100
11 110
12 120
奖金
您还可以使用 修改单元格的值df.at[row, column] = newValue
。
for row in range(len(df)):
df.at[row, 'c1'] = 'data-' + str(df.at[row, 'c1'])
print(df.at[row, 'c1'], df.at[row, 'c2'])
输出将是:
data-10 100
data-11 110
data-12 120
解决方案 21:
有时循环确实比矢量化代码更好
正如这里的许多答案正确指出的那样,您在 Pandas 中的默认计划应该是编写矢量化代码(使用其隐式循环),而不是自己尝试显式循环。但问题仍然是您是否应该在Pandas 中编写循环,如果是,那么在这些情况下循环的最佳方法是什么。
我认为至少有一种情况适合使用循环:当您需要以某种复杂的方式计算依赖于其他行中的值的函数时。在这种情况下,循环代码通常比矢量化代码更简单、更易读且更不容易出错。
循环代码甚至可能更快,如下所示,因此在速度至关重要的情况下,循环可能有意义。但实际上,这些只是您可能应该在 numpy/numba(而不是 Pandas)中工作的案例的子集,因为优化的 numpy/numba 几乎总是比 Pandas 更快。
让我们用一个例子来说明这一点。假设你想对某一列求和,但当其他列等于零时就重置它:
import pandas as pd
import numpy as np
df = pd.DataFrame( { 'x':[1,2,3,4,5,6], 'y':[1,1,1,0,1,1] } )
# x y desired_result
#0 1 1 1
#1 2 1 3
#2 3 1 6
#3 4 0 4
#4 5 1 9
#5 6 1 15
这是一个很好的例子,你可以编写一行 Pandas 来实现这一点,尽管它的可读性不是特别强,特别是如果你对 Pandas 还不是很熟悉的话:
df.groupby( (df.y==0).cumsum() )['x'].cumsum()
对于大多数情况来说这已经足够快了,虽然您也可以通过避免来编写更快的代码groupby
,但它的可读性可能会更差。
或者,如果我们将其写成循环会怎么样?你可以使用 NumPy 执行以下操作:
import numba as nb
@nb.jit(nopython=True) # Optional
def custom_sum(x,y):
x_sum = x.copy()
for i in range(1,len(df)):
if y[i] > 0: x_sum[i] = x_sum[i-1] + x[i]
return x_sum
df['desired_result'] = custom_sum( df.x.to_numpy(), df.y.to_numpy() )
不可否认,将 DataFrame 列转换为 NumPy 数组需要一些开销,但核心代码只有一行,即使您对 Pandas 或 NumPy 一无所知也可以读懂:
if y[i] > 0: x_sum[i] = x_sum[i-1] + x[i]
而且此代码实际上比矢量化代码更快。在一些包含 100,000 行的快速测试中,上述代码比groupby方法快 10 倍左右。请注意,速度的一个关键是 numba,它是可选的。如果没有“@nb.jit”行,循环代码实际上比groupby方法慢 10 倍左右。
显然,这个例子非常简单,您可能更愿意使用一行 pandas 代码,而不是编写一个循环并承担相关开销。但是,对于这个问题的更复杂版本,NumPy/numba 循环方法的可读性或速度可能更有意义。
解决方案 22:
对于查看和修改值,我会使用iterrows()
。在 for 循环中,通过使用元组解包(参见示例:i, row
),我仅使用row
来查看值,并在想要修改值时使用 和i
方法loc
。如前面的答案所述,在这里您不应该修改您正在迭代的内容。
for i, row in df.iterrows():
df_column_A = df.loc[i, 'A']
if df_column_A == 'Old_Value':
df_column_A = 'New_value'
这里row
循环中的是该行的副本,而不是该行的视图。因此,您不应该编写类似这样的代码row['A'] = 'New_Value'
,它不会修改 DataFrame。但是,您可以使用i
和loc
指定 DataFrame 来完成工作。
解决方案 23:
有很多方法可以迭代 Pandas DataFrame 中的行。一种非常简单直观的方法是:
df = pd.DataFrame({'A':[1, 2, 3], 'B':[4, 5, 6], 'C':[7, 8, 9]})
print(df)
for i in range(df.shape[0]):
# For printing the second column
print(df.iloc[i, 1])
# For printing more than one columns
print(df.iloc[i, [0, 2]])
解决方案 24:
最简单的方法是使用apply
函数
def print_row(row):
print row['c1'], row['c2']
df.apply(lambda row: print_row(row), axis=1)
解决方案 25:
可能是最优雅的解决方案(但肯定不是最有效的):
for row in df.values:
c2 = row[1]
print(row)
# ...
for c1, c2 in df.values:
# ...
注意:
文档
.to_numpy()
明确建议使用在最坏的情况下,生成的 NumPy 数组将具有适合所有列的 dtype
object
首先,有充分的理由不使用循环
不过,我认为应该在这里包含这个选项,作为(人们应该认为)简单问题的直接解决方案。
解决方案 26:
1. 迭代df.index
并通过访问at[]
一种非常易读的方法是迭代索引(如@Grag2015at
所建议的)。但是,为了提高效率,不要使用链式索引:
for ind in df.index:
print(df.at[ind, 'col A'])
此方法的优点for i in range(len(df))
是即使索引不是,它也能起作用RangeIndex
。请参阅以下示例:
df = pd.DataFrame({'col A': list('ABCDE'), 'col B': range(5)}, index=list('abcde'))
for ind in df.index:
print(df.at[ind, 'col A'], df.at[ind, 'col B']) # <---- OK
df.at[ind, 'col C'] = df.at[ind, 'col B'] * 2 # <---- can assign values
for ind in range(len(df)):
print(df.at[ind, 'col A'], df.at[ind, 'col B']) # <---- KeyError
如果需要某一行的整数位置(例如,获取前一行的值),则将其包装如下enumerate()
:
for i, ind in enumerate(df.index):
prev_row_ind = df.index[i-1] if i > 0 else df.index[i]
df.at[ind, 'col C'] = df.at[prev_row_ind, 'col B'] * 2
2.get_loc
使用itertuples()
尽管它比快得多iterrows()
,但它的一个主要缺点itertuples()
是,如果列标签中包含空格(例如'col C'
变成_1
等),它会破坏列标签,从而导致很难在迭代中访问值。
您可以使用它df.columns.get_loc()
来获取列标签的整数位置,并使用它来索引命名元组。请注意,每个命名元组的第一个元素都是索引标签,因此要通过整数位置正确访问列,您必须将返回的内容加 1 get_loc
,或者在开头解包元组。
df = pd.DataFrame({'col A': list('ABCDE'), 'col B': range(5)}, index=list('abcde'))
for row in df.itertuples(name=None):
pos = df.columns.get_loc('col B') + 1 # <---- add 1 here
print(row[pos])
for ind, *row in df.itertuples(name=None):
# ^^^^^^^^^ <---- unpacked here
pos = df.columns.get_loc('col B') # <---- already unpacked
df.at[ind, 'col C'] = row[pos] * 2
print(row[pos])
3. 转换为字典并迭代dict_items
循环数据框的另一种方法是将其转换为字典,orient='index'
然后对其进行迭代dict_items
或dict_values
。
df = pd.DataFrame({'col A': list('ABCDE'), 'col B': range(5)})
for row in df.to_dict('index').values():
# ^^^^^^^^^ <--- iterate over dict_values
print(row['col A'], row['col B'])
for index, row in df.to_dict('index').items():
# ^^^^^^^^ <--- iterate over dict_items
df.at[index, 'col A'] = row['col A'] + str(row['col B'])
这不会像 那样破坏数据类型iterrows
,不会像 那样破坏列标签itertuples
,并且与列数无关(zip(df['col A'], df['col B'], ...)
如果有很多列,则会很快变得麻烦)。
最后,正如@cs95提到的,尽可能避免循环。特别是如果您的数据是数字,只要您稍微挖掘一下,库中就会有一种针对您的任务的优化方法。
尽管如此,在某些情况下,迭代比矢量化操作更有效。一个常见的任务是将 pandas 数据框转储到嵌套的 json 中。至少从 pandas 1.5.3 开始,在这种情况下,itertuples()
循环比任何涉及方法的矢量化操作都要快得多groupby.apply
。
解决方案 27:
您还可以进行 NumPy 索引,以获得更快的速度。它实际上不是迭代,但对于某些应用程序来说,它比迭代效果好得多。
subset = row['c1'][0:5]
all = row['c1'][:]
您可能还想将其转换为数组。这些索引/选择应该已经像 NumPy 数组一样工作了,但我遇到了问题,需要进行转换
np.asarray(all)
imgs[:] = cv2.resize(imgs[:], (224,224) ) # Resize every image in an hdf5 file
解决方案 28:
df.iterrows()
返回tuple(a, b)
其中a
是index
并且b
是row
。
解决方案 29:
此示例使用 iloc 隔离数据框中的每个数字。
import pandas as pd
a = [1, 2, 3, 4]
b = [5, 6, 7, 8]
mjr = pd.DataFrame({'a':a, 'b':b})
size = mjr.shape
for i in range(size[0]):
for j in range(size[1]):
print(mjr.iloc[i, j])
解决方案 30:
免责声明:尽管这里有很多答案建议不要使用迭代(循环)方法(我大部分都同意),但我仍然认为它是以下情况的合理方法:
使用 API 中的数据扩展数据框
假设您有一个包含不完整用户数据的大型数据框。现在您必须使用其他列来扩展此数据,例如用户的age
和gender
。
这两个值都必须从后端 API 中获取。我假设 API 不提供“批处理”端点(一次接受多个用户 ID)。否则,您应该只调用一次 API。
网络请求的成本(等待时间)远远超过了数据帧的迭代。我们谈论的是数百毫秒的网络往返时间,而使用其他迭代方法的收益微不足道。
每行一个昂贵的网络请求
因此在这种情况下,我绝对更喜欢使用迭代方法。尽管网络请求很昂贵,但可以保证对数据框中的每一行仅触发一次。以下是使用DataFrame.iterrows的示例:
例子
for index, row in users_df.iterrows():
user_id = row['user_id']
# Trigger expensive network request once for each row
response_dict = backend_api.get(f'/api/user-data/{user_id}')
# Extend dataframe with multiple data from response
users_df.at[index, 'age'] = response_dict.get('age')
users_df.at[index, 'gender'] = response_dict.get('gender')
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件