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

d.b*_*d.b 8 python pandas

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

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])
Run Code Online (Sandbox Code Playgroud)

tdy*_*tdy 14

TL; 博士

Pandas 中迭代是一种反模式,通常可以通过向量化、应用、聚合、转换cythonizing来避免。

但是,如果绝对需要系列迭代,则性能将取决于 dtype 和索引:


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

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

    for el in s.to_numpy(): # if dtype is datetime, int, float, str, string
    
    Run Code Online (Sandbox Code Playgroud)
    约会时间
    日期时间系列的迭代时间(无索引)
    整数 漂浮 浮动 + 南 字符串 细绳
    int 系列的迭代时间(无索引) 浮点系列的迭代时间(无索引) 浮点系列的迭代时间(无索引) str 系列的迭代时间(无索引) 字符串系列的迭代时间(无索引)
  2. 要访问索引,实际上最快的是enumerate()zip()numpy ndarray:

    for i, el in enumerate(s.to_numpy()): # if default range index
    
    Run Code Online (Sandbox Code Playgroud)
    for i, el in zip(s.index, s.to_numpy()): # if custom index
    
    Run Code Online (Sandbox Code Playgroud)

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

    日期时间 + 索引
    日期时间系列的迭代时间(带索引)
  3. 微优化,切换到s.tolist()更短int/ float/str系列:

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

    警告:不要使用,list(s)因为它不使用编译代码,这会使它变慢。


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

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

大熊猫数据类型 内容
Categorical 2个阵列
DatetimeTZ 数组 + 时区元数据
Interval 2个阵列
Period 数组 + 频率元数据
... ...

将这些扩展数组转换为 numpy “可能很昂贵”,因为它可能涉及复制/强制数据,因此:

  1. 如果 Series 是Pandas 扩展 dtype,则迭代底层 Pandas 数组通常是最快的:

    for el in s.array: # if dtype is pandas-only extension
    
    Run Code Online (Sandbox Code Playgroud)

    例如,有 ~100 个唯一Categorical值:

    分类的
    分类系列的迭代时间(无索引)
    日期时间TZ 时期 间隔
    DatetimeTZ 系列的迭代时间(无索引) Period 系列的迭代时间(无索引) 间隔系列的迭代时间(无索引)
  2. 要访问索引,s.items() pandas dtypes的惯用语非常快:

    for i, el in s.items(): # if need index for pandas-only dtype
    
    Run Code Online (Sandbox Code Playgroud)
    日期时间TZ + 索引 间隔 + 索引 期间+索引
    DatetimeTZ 系列的迭代时间(带索引) 间隔系列的迭代时间(带索引) 周期序列的迭代时间(带索引)
  3. 要进行微优化,请切换到enumerate()默认索引Categorical数组的稍微快一点:

    for i, el in enumerate(s.array): # to micro-optimize Categorical dtype if need default range index
    
    Run Code Online (Sandbox Code Playgroud)
    分类+索引
    分类序列的迭代时间(带索引)

注意事项

  1. 避免使用s.values

  2. 避免修改迭代的 Series

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

  3. 尽可能避免手动迭代

    1. 向量化、(布尔)索引等。

    2. 应用函数,例如:

      注意:这些是不是尽管常见的误解vectorizations。

    3. 卸载到 cython/numba


规格:的ThinkPad X1极端创3(核心i7-10850H 2.70GHz,32GB DDR4 2933MHz)
版本:python==3.9.2pandas==1.3.1numpy==1.20.2
测试数据:系列生成代码段中的代码

'''
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'))
Run Code Online (Sandbox Code Playgroud)