迭代熊猫系列元素的最佳方法

Posted

技术标签:

【中文标题】迭代熊猫系列元素的最佳方法【英文标题】:best way to iterate through elements of pandas Series 【发布时间】:2021-10-10 18:34:13 【问题描述】:

以下所有内容似乎都适用于遍历熊猫系列的元素。我相信还有更多的方法可以做到这一点。有什么区别,哪种方法最好?

import pandas


arr = pandas.Series([1, 1, 1, 2, 2, 2, 3, 3])

# 1
for el in arr:
    print(el)

# 2
for _, el in arr.iteritems():
    print(el)

# 3
for el in arr.array:
    print(el)

# 4
for el in arr.values:
    print(el)

# 5
for i in range(len(arr)):
    print(arr.iloc[i])

【问题讨论】:

为什么需要迭代? why you shouldn't use iterrows 的许多论点可能也适用于系列。话虽如此,“最好的方式”是什么?表现?简明?惯用语? @fsimonjetz,比方说惯用语 如果迭代的目的只是为了打印,那么很难看出如何做真的很重要。您在上面显示的任何方法都可以。如果您正在做一些数值操作,那么我同意@tdy 的回答,即您应该转换为 numpy 数组并对其进行迭代。 FWIW,我也有另一个使用 numpy 循环的问题的答案,这同样适用于您的问题(如果它是数字的):***.com/questions/7837722/… 为什么需要迭代?这几乎总是不必要的,有.apply()、系列加法和乘法等。你没有展示为什么print() 不是用例的例子。向我们展示一些用例。 【参考方案1】:

TL;DR

Iterating in pandas is an antipattern,通常可以通过矢量化、applying, aggregating, transforming 或cythonizing 来避免。

但是,如果 Series 迭代是绝对必要的,那么性能将取决于 dtype 和 index:

Index Fastest if numpy dtype Fastest if pandas dtype Idiomatic
Unneeded in s.to_numpy() in s.array in s
Default in enumerate(s.to_numpy()) in enumerate(s.array) in s.items()
Custom in zip(s.index, s.to_numpy()) in s.items() in s.items()

对于基于 numpy 的系列,使用 s.to_numpy()

    如果 Series 是 python or numpy dtype,通常迭代底层 numpy ndarray 是最快的:

    for el in s.to_numpy(): # if dtype is datetime, int, float, str, string
    
    datetime
    int float float + nan str string

    要访问索引,实际上最快的是enumerate()zip() numpy ndarray:

    for i, el in enumerate(s.to_numpy()): # if default range index
    
    for i, el in zip(s.index, s.to_numpy()): # if custom index
    

    两者都比惯用的 s.items() / s.iteritems() 快:

    datetime + index

    要进行微优化,切换到s.tolist() 以缩短int/float/str 系列:

    for el in s.to_numpy(): # if >100K elements
    
    for el in s.tolist(): # to micro-optimize if <100K elements
    

    警告:不要使用list(s),因为doesn't use compiled code 会变慢。


对于基于 pandas 的系列,请使用 s.arrays.items()

Pandas extension dtypes 包含额外的(元)数据,例如:

pandas dtype contents
Categorical 2 arrays
DatetimeTZ array + timezone metadata
Interval 2 arrays
Period array + frequency metadata
... ...

将这些扩展数组转换为 numpy "may be expensive",因为它可能涉及复制/强制数据,所以:

    如果 Series 是 pandas extension dtype,通常迭代底层 pandas 数组是最快的:

    for el in s.array: # if dtype is pandas-only extension
    

    例如,具有约 100 个唯一 Categorical 值:

    Categorical
    DatetimeTZ Period Interval

    要访问索引,惯用的 s.items() 对于 pandas dtypes 来说非常快:

    for i, el in s.items(): # if need index for pandas-only dtype
    
    DatetimeTZ + index Interval + index Period + index

    要进行微优化,为默认索引的Categorical 数组切换到稍快的enumerate()

    for i, el in enumerate(s.array): # to micro-optimize Categorical dtype if need default range index
    
    Categorical + index

注意事项

    Avoid using s.values:

    使用s.to_numpy() 获取底层的numpy ndarray 使用s.array获取底层pandas数组

    Avoid modifying the iterated Series:

    你应该永远不要修改你正在迭代的东西。这不能保证在所有情况下都有效。根据数据类型,迭代器返回一个副本而不是一个视图,写入它不会有任何效果!

    Avoid iterating manually 尽可能改为:

      矢量化、(布尔)索引等

      Applying functions,例如:

      s.apply(some_function) s.agg(['min', 'max', 'mean']) s.transform([np.sqrt, np.exp])

      注意:尽管存在常见的误解,但它们不是矢量化。

      Offloading to cython/numba


规格:ThinkPad X1 Extreme Gen 3(Core i7-10850H 2.70GHz,32GB DDR4 2933MHz)版本:python==3.9.2 , pandas==1.3.1, numpy==1.20.2测试数据:sn-p 中的系列生成代码

'''
Note: This is python code in a js snippet, so "run code snippet" will not work.
The snippet is just to avoid cluttering the main post with supplemental code.
'''

import pandas as pd
import numpy as np

int_series = pd.Series(np.random.randint(1000000000, size=n))
float_series = pd.Series(np.random.randn(size=n))
floatnan_series = pd.Series(np.random.choice([np.nan, np.inf]*n + np.random.randn(n).tolist(), size=n))
str_series = pd.Series(np.random.randint(10000000000000000, size=n)).astype(str)
string_series = pd.Series(np.random.randint(10000000000000000, size=n)).astype('string')
datetime_series = pd.Series(np.random.choice(pd.date_range('2000-01-01', '2021-01-01'), size=n))
datetimetz_series = pd.Series(np.random.choice(pd.date_range('2000-01-01', '2021-01-01', tz='CET'), size=n))
categorical_series = pd.Series(np.random.randint(100, size=n)).astype('category')
interval_series = pd.Series(pd.arrays.IntervalArray.from_arrays(-np.random.random(size=n), np.random.random(size=n)))
period_series = pd.Series(pd.period_range(end='2021-01-01', periods=n, freq='s'))

【讨论】:

【参考方案2】:

使用items:

for i, v in arr.items():
    print(f'index: i and value: v')

输出:

index: 0 and value: 1
index: 1 and value: 1
index: 2 and value: 1
index: 3 and value: 2
index: 4 and value: 2
index: 5 and value: 2
index: 6 and value: 3
index: 7 and value: 3

【讨论】:

【参考方案3】:

测试结果如下:循环的执行速度最慢。 iterrows()针对pandas的dataframe进行了优化,相比直接循环有明显提升。 apply() 方法也在行之间循环,但它比 iterrows 高效得多,因为使用了 python 等迭代器进行了一系列全局优化。 numpy 数组的向量化运行最快,其次是 pandas 系列的向量化。由于矢量化同时作用于整个序列,因此可以节省更多时间。 Numpy在底层使用预编译的C代码进行优化,避免了pandas系列操作中的大量开销。所以numpy数组的运算速度比pandas系列快很多。

loop: 1.80301690102 
iterrows: 0.724927186966 
apply: 0.645957946777
pandas series: 0.333024024963 
numpy array: 0.260366916656

列表循环 > numpy 数组 > pandas 系列 > 应用 > iterrows

【讨论】:

您说的是迭代数据帧(系列甚至没有.iterrows() 方法)。数据框迭代已在许多其他 SO 帖子中介绍。【参考方案4】:

遍历 pandas/python 的方法

arr = pandas.Series([1, 1, 1, 2, 2, 2, 3, 3])

#Using Python range() method
for i in range(len(arr)):
    print(arr[i])

范围不包括序列中的结束值

#List Comprehension
print([arr[i] for i in range(len(arr))])

列表推导可以使用并且可以识别输入是列表、字符串还是元组

#Using Python enumerate() method
for el,j in enumerate(arr):
    print(j)
#Using Python NumPy module
import numpy as np
print(np.arange(len(arr)))
for i,j in np.ndenumerate(arr):
    print(j)

enumerate 使用非常广泛,因为 enumerate 将计数器添加到列表或任何其他可迭代对象,并由函数将其作为枚举对象返回。它减少了在迭代操作时保持元素计数的开销。这里不需要柜台。您可以使用 np.ndenumerate() 来模拟 numpy 数组的枚举行为。对于非常大的 n 维列表,建议使用 numpy。

您还使用传统的 for 循环和 while 循环

x=0
while x<len(arr):
    print(arr[x])
    x +=1
    
#Using lambda function
list(map(lambda x:x, arr))

lambda 减少了代码行数,可与侧过滤器、reduce 或 map 一起使用。

如果您想遍历数据框的行而不是系列,我们可以使用 iterrows、itertuple 和 iteritems。就内存和计算而言,最好的方法是将列用作向量并使用 numpy 数组执行向量计算。当涉及到大数据时,循环非常昂贵。当您将它们制作成 numpy 数组并对其进行处理时,它会更容易和更快。

【讨论】:

【参考方案5】:

我认为,更重要的是了解对化妆品的要求,同时寻找针对个人要求的解决方案。

在我看来,除非我们正在处理的数据量很大,否则成本不会太高,在这种情况下,我们必须在我们的方法中有所选择,对于小数据集,无论哪种方法都可以,如下所述..

PEP 469、PEP 3106和Views And Iterators Instead Of Lists中有很好的解释

在 Python 3 中,只有一个名为 items() 的方法。它使用迭代器,因此速度很快,并且允许在编辑时遍历字典。请注意,方法 iteritems() 已从 Python 3 中删除。

可以查看 Python3 Wiki Built-In_Changes 以获取更多详细信息。

arr = pandas.Series([1, 1, 1, 2, 2, 2, 3, 3])
$ for index, value in arr.items():
   print(f"Index : index, Value : value")

Index : 0, Value : 1
Index : 1, Value : 1
Index : 2, Value : 1
Index : 3, Value : 2
Index : 4, Value : 2
Index : 5, Value : 2
Index : 6, Value : 3
Index : 7, Value : 3

$ for index, value in arr.iteritems():
   print(f"Index : index, Value : value")
   
Index : 0, Value : 1
Index : 1, Value : 1
Index : 2, Value : 1
Index : 3, Value : 2
Index : 4, Value : 2
Index : 5, Value : 2
Index : 6, Value : 3
Index : 7, Value : 3

$ for _, value in arr.iteritems():
   print(f"Index : index, Value : value")

Index : 7, Value : 1
Index : 7, Value : 1
Index : 7, Value : 1
Index : 7, Value : 2
Index : 7, Value : 2
Index : 7, Value : 2
Index : 7, Value : 3
Index : 7, Value : 3

$ for i, v in enumerate(arr):
   print(f"Index : i, Value : v")
Index : 0, Value : 1
Index : 1, Value : 1
Index : 2, Value : 1
Index : 3, Value : 2
Index : 4, Value : 2
Index : 5, Value : 2
Index : 6, Value : 3
Index : 7, Value : 3

$ for value in arr:
   print(value)

1
1
1
2
2
2
3
3



$ for value in arr.tolist():
   print(value)

1
1
1
2
2
2
3
3

有一篇关于How to iterate over rows in a DataFrame in Pandas 的好帖子虽然说的是df,但它解释了所有关于item()iteritems() 等的内容。

关于 SO items & iteritems 的另一个很好的讨论。

【讨论】:

【参考方案6】:

对于向量编程(pandas、R、octave、..),建议不要对向量进行迭代。相反,请使用库提供的映射函数来应用系列或数据集。

在您将打印功能应用于每个元素的情况下,代码将是:

import pandas
arr = pandas.Series([1, 1, 1, 2, 2, 2, 3, 3])

arr.apply(print)

【讨论】:

以上是关于迭代熊猫系列元素的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

对熊猫系列的 k 个元素组应用函数

如何检索熊猫系列对象中第 n 个元素的值?

如何获得熊猫系列的元素逻辑非?

如何获得熊猫系列的元素逻辑非?

在熊猫系列中查找元素的索引

如何计算熊猫系列列表中每个元素的出现次数?