我有一个熊猫数据框,df
:
c1 c2
0 10 100
1 11 110
2 12 120
如何迭代此数据框的行?对于每一行,我希望能够通过列名访问其元素(单元格中的值)。例如:
for row in df.rows:
print(row['c1'], row['c2'])
我发现了一个similar question,它建议使用以下任何一种:
for date, row in df.T.iteritems():
for row in df.iterrows():
但我不明白 row
对象是什么以及如何使用它。
pandas
也是读取 csv 文件的首选。使用 API 操作数据更容易编程
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 中遍历 DataFrame 中的行?
答案:不要*!
Pandas 中的迭代是一种反模式,只有在用尽所有其他选项时才应该这样做。您不应使用名称中包含“iter
”的任何函数超过几千行,否则您将不得不习惯大量等待。
你想打印一个DataFrame吗?使用 DataFrame.to_string()
。
你想计算一些东西吗?在这种情况下,按以下顺序搜索方法(从 here 修改的列表):
向量化 Cython 例程列表理解(vanilla for loop)DataFrame.apply():i)可以在 Cython 中执行的缩减,ii)Python 空间中的迭代 DataFrame.itertuples() 和 iteritems() DataFrame.iterrows()
iterrows
和 itertuples
(在这个问题的答案中都获得了很多投票)应该在非常罕见的情况下使用,例如为顺序处理生成行对象/名称元组,这实际上是这些函数唯一有用的事情。
向当局上诉
The documentation page 在迭代中有一个巨大的红色警告框,上面写着:
遍历 pandas 对象通常很慢。在许多情况下,不需要手动迭代行 [...]。
* 它实际上比“不要”要复杂一些。 df.iterrows()
是这个问题的正确答案,但“矢量化您的操作”是更好的答案。我承认在某些情况下无法避免迭代(例如,某些操作的结果取决于为前一行计算的值)。但是,需要对库有一定的了解才能知道何时。如果您不确定是否需要迭代解决方案,您可能不需要。 PS:要了解更多关于我写这个答案的理由,请跳到最底部。
比循环更快:矢量化,Cython
大量的基本操作和计算由 pandas“矢量化”(通过 NumPy 或通过 Cythonized 函数)。这包括算术、比较、(大多数)归约、重塑(例如旋转)、连接和 groupby 操作。查看 Essential Basic Functionality 上的文档,为您的问题找到合适的矢量化方法。
如果不存在,请随意使用自定义 Cython extensions 编写您自己的。
下一个最好的事情:列出理解*
如果 1) 没有可用的矢量化解决方案,列表推导应该是您的下一个停靠点,2) 性能很重要,但还不足以解决对代码进行 cythonizing 的麻烦,以及 3) 您正在尝试执行元素转换在你的代码上。有一个 good amount of evidence 表明列表推导对于许多常见的 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() 会将整个数组转换为字符串,这可能不是您想要的。幸运的是,将列压缩在一起是最直接的解决方法。
*您的里程可能会因上述注意事项部分中列出的原因而有所不同。
一个明显的例子
让我们通过添加两个 pandas 列 A + B
的简单示例来演示差异。这是一个可向量化的操作,因此很容易对比上述方法的性能。
Benchmarking code, for your reference。底部的行测量了一个用 numpandas 编写的函数,这是一种与 NumPy 大量混合以挤出最大性能的 Pandas 风格。除非您知道自己在做什么,否则应避免编写 numpandas 代码。尽可能坚持使用 API(即,更喜欢 vec
而不是 vec_numpy
)。
然而,我应该提一下,它并不总是这么干脆利落的。有时,“什么是最佳操作方法”的答案是“这取决于您的数据”。我的建议是在确定一种方法之前对您的数据测试不同的方法。
我的个人意见 *
对 iter 系列的各种替代方案进行的大多数分析都是从性能的角度进行的。但是,在大多数情况下,您通常会处理大小合理的数据集(不超过几千或 100K 行),性能将仅次于解决方案的简单性/可读性。
这是我在选择用于解决问题的方法时的个人偏好。
对于新手:
矢量化(如果可能);申请();列出理解; itertuples()/iteritems(); iterrows();赛通
对于更有经验的人:
矢量化(如果可能);申请();列出理解;赛通; itertuples()/iteritems(); iterrows()
对于可以向量化的任何问题,向量化是最惯用的方法。始终寻求矢量化!如有疑问,请查阅文档,或在 Stack Overflow 上查看有关您的特定任务的现有问题。
我确实倾向于在我的很多帖子中继续谈论 apply
的糟糕程度,但我承认初学者更容易理解它在做什么。此外,this post of mine 中解释了 apply
的很多用例。
Cython 在列表中排名较低,因为它需要更多的时间和精力才能正确完成。您通常永远不需要使用 pandas 编写需要这种性能水平的代码,即使是列表推导也无法满足。
*与任何个人意见一样,请多加盐!
延伸阅读
10 分钟了解 pandas 和基本基本功能 - 向您介绍 Pandas 及其矢量化*/cythonized 函数库的有用链接。
增强性能 - 增强标准 Pandas 操作的文档入门
pandas 中的 for 循环真的很糟糕吗?我什么时候应该关心? - 我对列表理解及其对各种操作的适用性(主要是涉及非数字数据的操作)的详细说明
我什么时候应该(不)想在我的代码中使用 pandas apply()? - apply 很慢(但不像 iter* 系列那么慢。但是,在某些情况下可以(或应该)考虑将 apply 作为一种重要的替代方案,尤其是在某些 GroupBy 操作中)。
Pandas 字符串方法是“矢量化的”,因为它们是在系列上指定的,但对每个元素都进行操作。底层机制仍然是迭代的,因为字符串操作本质上很难向量化。
为什么我写这个答案
我从新用户那里注意到的一个常见趋势是提出“如何迭代我的 df 以执行 X?”形式的问题。显示在 for
循环内执行某些操作时调用 iterrows()
的代码。这就是为什么。一个没有被引入向量化概念的库的新用户可能会将解决他们问题的代码设想为迭代他们的数据来做某事。不知道如何迭代 DataFrame,他们做的第一件事就是用谷歌搜索它,然后在这个问题上结束。然后,他们看到接受的答案告诉他们如何去做,然后他们闭上眼睛运行这段代码,而不会首先质疑迭代是否是正确的做法。
这个答案的目的是帮助新用户理解迭代不一定是所有问题的解决方案,并且可能存在更好、更快和更惯用的解决方案,值得花时间去探索它们。我并不是要开始一场迭代与矢量化的战争,但我希望新用户在为他们的这个库的问题开发解决方案时被告知。
zip(df['A'], df['B'])
而不是 df.iterrows()
。
DataFrame.values
会将每一列转换为通用数据类型。 DataFrame.to_numpy()
也这样做。幸运的是,我们可以将 zip
用于任意数量的列。
首先考虑您是否真的需要迭代 DataFrame 中的行。请参阅 this answer 了解替代方案。
如果您仍然需要遍历行,可以使用下面的方法。请注意一些其他答案中未提及的重要警告。
DataFrame.iterrows() 用于索引,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,所以它不会跨行保留 dtypes(dtypes 在 DataFrames 的列中保留)。要在遍历行时保留 dtypes,最好使用 itertuples(),它返回值的命名元组,通常比 iterrows() 快得多
iterrows:不修改行
你永远不应该修改你正在迭代的东西。这不能保证在所有情况下都有效。根据数据类型,迭代器返回一个副本而不是一个视图,写入它不会有任何效果。
请改用 DataFrame.apply():
new_df = df.apply(lambda x: x * 2, axis = 1)
迭代:
如果列名是无效的 Python 标识符、重复或以下划线开头,它们将被重命名为位置名称。对于大量列 (>255),将返回常规元组。
有关详细信息,请参阅 pandas docs on iteration。
for row in df[['c1','c2']].itertuples(index=True, name=None):
之类的内容来仅在行迭代器中包含某些列。
row.c1
而不是 getattr(row, "c1")
。
getattr(row, "c1")
而不是 row.c1
,您将失去 itertuples
的任何性能优势,并且如果您确实需要通过字符串访问该属性,则应该使用 iterrows 代替。
numba
和 cython
进行改进(同一个文档说“首先在 Python 中进行优化总是值得的”)。我写这个答案是为了帮助其他人避免(有时令人沮丧)的问题,因为其他答案都没有提到这些警告。误导任何人或告诉“这是正确的做法”从来都不是我的本意。我已经改进了答案。
您应该使用 df.iterrows()
。尽管逐行迭代并不是特别有效,因为必须创建 Series
对象。
虽然 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
for a,b,c in izip(df["a"],df["b"],df["c"]:
几乎同样快。
iterrows()
将每行数据打包成一个系列,而 itertuples()
没有。
df
是从字典创建的,因此 row[1]
可以引用任何列。事实证明,尽管整数与浮点列的时间大致相同。
您可以按如下方式使用 df.iloc
函数:
for i in range(0, len(df)):
print(df.iloc[i]['c1'], df.iloc[i]['c2'])
itertuples
保留数据类型,但去掉它不喜欢的任何名称。 iterrows
则相反。
for i in range(df.shape[0])
可能会稍微加快这种方法,但对于我的应用程序,它仍然比上面的 iterrows() 方法慢约 3.5 倍。
my_iter = df.itertuples()
需要双倍的内存和大量的时间来复制它。 iterrows()
相同。
您还可以使用 df.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)
apply
不会“迭代”行,而是逐行应用函数。如果您确实确实 需要迭代和indeces,例如在比较不同行的值时(在这种情况下,您只能进行迭代),则上述代码将不起作用。
如何高效迭代
如果您确实需要迭代 Pandas 数据框,您可能希望避免使用 iterrows()。有不同的方法,通常的 iterrows()
远不是最好的。 itertuples() 可以快 100 倍。
简而言之:
作为一般规则,使用 df.itertuples(name=None)。特别是当您有固定数量的列且少于 255 列时。见第 (3) 点
否则,请使用 df.itertuples(),除非您的列有特殊字符,例如空格或“-”。见点(2)
通过使用最后一个示例,即使您的数据框有奇怪的列,也可以使用 itertuples()。见第 (4) 点
如果您不能使用以前的解决方案,请仅使用 iterrows()。见点(1)
遍历 Pandas 数据框中的行的不同方法:
生成具有一百万行和 4 列的随机数据帧:
df = pd.DataFrame(np.random.randint(0, 100, size=(1000000, 4)), columns=list('ABCD'))
print(df)
1) 通常的 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))
2) 默认的 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))
3) 使用 name=None 的默认 itertuples()
更快,但不是很方便,因为您必须为每列定义一个变量。
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))
4) 最后,命名的 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
This article is a very interesting comparison between iterrows and itertuples
我一直在寻找如何迭代行和列并在这里结束:
for i, row in df.iterrows():
for j, column in row.iteritems():
print(column)
您可以编写自己的迭代器来实现 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);
https://i.stack.imgur.com/rt88e.png
https://i.stack.imgur.com/azbOF.png
intertuples
,橙线是通过 yield 块的迭代器列表。 interrows
不进行比较。
要循环 dataframe
中的所有行,您可以使用:
for x in range(len(date_example.index)):
print date_example['Date'].iloc[x]
for ind in df.index:
print df['c1'][ind], df['c2'][ind]
我们有多种选择来做同样的事情,很多人都分享了他们的答案。
我发现以下两种方法既简单又有效:
DataFrame.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 iterrows method
for index, row in df.iterrows():
print(row["c1"], row["c2"])
#With itertuples method
for row in df.itertuples(index=True, name='Pandas'):
print(row.c1, row.c2)
注意: itertuples() 应该比 iterrows() 快
有时有用的模式是:
# 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}
更新:cs95 已更新 his answer 以包含纯 numpy 向量化。你可以参考他的回答。
cs95 shows 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]
https://i.stack.imgur.com/L0u4A.png
简而言之
尽可能使用矢量化
如果一个操作不能向量化 - 使用列表推导
如果您需要代表整行的单个对象 - 使用 itertuples
如果上述速度太慢 - 尝试 swifter.apply
如果它仍然太慢 - 尝试 Cython 例程
基准
https://i.stack.imgur.com/ytaWK.png
有一种方法可以在获取 DataFrame 而不是 Series 的同时迭代 throw 行。我没有看到有人提到您可以将索引作为列表传递给要作为 DataFrame 返回的行:
for i in range(len(df)):
row = df.iloc[[i]]
注意双括号的使用。这将返回具有单行的 DataFrame。
要循环 dataframe
中的所有行并方便地使用每行的值,可以将 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
,索引将作为元组的第一个元素添加,这对于某些应用程序可能是不可取的。
对于查看和修改值,我会使用 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 来完成这项工作。
有很多方法可以遍历 Pandas 数据框中的行。一种非常简单直观的方法是:
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]])
最简单的方法,使用 apply
函数
def print_row(row):
print row['c1'], row['c2']
df.apply(lambda row: print_row(row), axis=1)
正如这里的许多答案正确而清楚地指出的那样,您通常不应该尝试在 Pandas 中循环,而应该编写矢量化代码。但是问题仍然存在,您是否应该在 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 循环方法的可读性或速度可能是有意义的。
df.iterrows()
返回 tuple(a, b)
,其中 a
是 index
,b
是 row
。
您还可以执行 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
免责声明:虽然这里有很多答案建议不要使用迭代(循环)方法(我大多同意),但对于以下情况,我仍然认为它是一种合理的方法:
使用来自 API 的数据扩展数据框
假设您有一个包含不完整用户数据的大型数据框。现在您必须使用其他列扩展此数据,例如用户的 age
和 gender
。
这两个值都必须从后端 API 中获取。我假设 API 不提供“批处理”端点(可以一次接受多个用户 ID)。否则,您应该只调用一次 API。
网络请求的成本(等待时间)远远超过了数据帧的迭代。我们谈论的是数百毫秒的网络往返时间,而使用替代迭代方法的收益微不足道。
每行 1 个昂贵的网络请求
所以在这种情况下,我绝对更喜欢使用迭代方法。尽管网络请求很昂贵,但可以保证数据帧中的每一行只触发一次。以下是使用 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')
此示例使用 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])
某些库(例如,我使用的 Java 互操作库)需要一次连续传递值,例如,如果是流数据。为了复制流式传输的性质,我将我的数据帧值一个一个地“流式传输”,我写了以下内容,它不时派上用场。
class DataFrameReader:
def __init__(self, df):
self._df = df
self._row = None
self._columns = df.columns.tolist()
self.reset()
self.row_index = 0
def __getattr__(self, key):
return self.__getitem__(key)
def read(self) -> bool:
self._row = next(self._iterator, None)
self.row_index += 1
return self._row is not None
def columns(self):
return self._columns
def reset(self) -> None:
self._iterator = self._df.itertuples()
def get_index(self):
return self._row[0]
def index(self):
return self._row[0]
def to_dict(self, columns: List[str] = None):
return self.row(columns=columns)
def tolist(self, cols) -> List[object]:
return [self.__getitem__(c) for c in cols]
def row(self, columns: List[str] = None) -> Dict[str, object]:
cols = set(self._columns if columns is None else columns)
return {c : self.__getitem__(c) for c in self._columns if c in cols}
def __getitem__(self, key) -> object:
# the df index of the row is at index 0
try:
if type(key) is list:
ix = [self._columns.index(key) + 1 for k in key]
else:
ix = self._columns.index(key) + 1
return self._row[ix]
except BaseException as e:
return None
def __next__(self) -> 'DataFrameReader':
if self.read():
return self
else:
raise StopIteration
def __iter__(self) -> 'DataFrameReader':
return self
可以使用哪个:
for row in DataFrameReader(df):
print(row.my_column_name)
print(row.to_dict())
print(row['my_column_name'])
print(row.tolist())
并保留正在迭代的行的值/名称映射。显然,这比使用上述的 apply 和 Cython 慢很多,但在某些情况下是必要的。
除了这篇文章中的好答案,我将提出 分而治之 方法,我写这个答案不是为了废除其他好的答案,而是用另一种对我有效的方法来实现它们.它有两个步骤 splitting
和 merging
pandas 数据框:
分而治之的优点:
您不需要使用矢量化或任何其他方法将数据框的类型转换为另一种类型
你不需要 Cythonize 你的代码,这通常需要你额外的时间
在我的情况下, iterrows() 和 itertuples() 在整个数据帧上都具有相同的性能
取决于您选择的切片索引,您将能够以指数方式加快迭代。索引越高,迭代过程越快。
分而治之的缺点:
您不应该依赖于相同数据帧和不同切片的迭代过程。这意味着如果您想从其他切片读取或写入,可能很难做到这一点。
=================== 分而治之=================
第 1 步:分割/切片
在这一步中,我们将在整个数据帧上划分迭代。认为您要将 csv 文件读入 pandas df 然后对其进行迭代。在可能的情况下,我有 5,000,000 条记录,我将把它分成 100,000 条记录。
注意:我需要重申本页其他解决方案中解释的其他运行时分析,“记录数”在 df 上搜索时具有“运行时”的指数比例。根据我的数据的基准,这里是结果:
Number of records | Iteration per second
========================================
100,000 | 500 it/s
500,000 | 200 it/s
1,000,000 | 50 it/s
5,000,000 | 20 it/s
第 2 步:合并
这将是一个简单的步骤,只需将所有写入的 csv 文件合并到一个数据帧中,然后将其写入一个更大的 csv 文件。
这是示例代码:
# Step 1 (Splitting/Slicing)
import pandas as pd
df_all = pd.read_csv('C:/KtV.csv')
df_index = 100000
df_len = len(df)
for i in range(df_len // df_index + 1):
lower_bound = i * df_index
higher_bound = min(lower_bound + df_index, df_len)
# splitting/slicing df (make sure to copy() otherwise it will be a view
df = df_all[lower_bound:higher_bound].copy()
'''
write your iteration over the sliced df here
using iterrows() or intertuples() or ...
'''
# writing into csv files
df.to_csv('C:/KtV_prep_'+str(i)+'.csv')
# Step 2 (Merging)
filename='C:/KtV_prep_'
df = (pd.read_csv(f) for f in [filename+str(i)+'.csv' for i in range(ktv_len // ktv_index + 1)])
df_prep_all = pd.concat(df)
df_prep_all.to_csv('C:/KtV_prep_all.csv')
参考:
Efficient way of iteration over datafreame
Concatenate csv files into one Pandas Dataframe
正如 the accepted answer 所述,将函数应用于行的最快方法是使用矢量化函数,即所谓的 NumPy ufuncs
(通用函数)。
但是当你想要应用的功能还没有在 NumPy 中实现时你应该怎么做呢?
好吧,使用 numba
中的 vectorize
装饰器,您可以像这样轻松地直接在 Python 中创建 ufunc:
from numba import vectorize, float64
@vectorize([float64(float64)])
def f(x):
#x is your line, do something with it, and return a float
这个函数的文档在这里:Creating NumPy universal functions
可能是最优雅的解决方案(但肯定不是最有效的):
for row in df.values:
c2 = row[1]
print(row)
# ...
for c1, c2 in df.values:
# ...
注意:
文档明确建议改用 .to_numpy()
生成的 NumPy 数组将具有适合所有列的 dtype,在最坏的情况下是对象
首先有充分的理由不使用循环
尽管如此,我认为这个选项应该包括在这里,作为一个(人们应该认为的)微不足道的问题的直接解决方案。
431341610650
这样的数值读作4.31E+11
。有没有办法保留数据类型?itertuples
,如下所述。另请参阅pandas.pydata.org/pandas-docs/stable/generated/…