【问题标题】:Pandas scalar value getting and setting: ix or iat?Pandas 标量值获取和设置:ix 还是 iat?
【发布时间】:2015-05-08 15:54:41
【问题描述】:

我试图弄清楚何时在 pandas DataFrame 中使用不同的选择方法。特别是,我正在寻找访问标量值。我经常听到ix 被普遍推荐。但是在pandas documentation 建议使用atiat 进行快速标量值访问:

Since indexing with [] must handle a lot of cases (single-label access, slicing, boolean indexing, etc.), it has a bit of overhead in order to figure out what you’re asking for. If you only want to access a scalar value, the fastest way is to use theatand iat methods, which are implemented on all of the data structures.

所以,我认为iat 应该更快地获取和设置单个单元格。然而,经过一些测试,我们发现ix 读取单元格的速度相当或更快,而iat 为单元格赋值要快得多。

这种行为是否记录在任何地方?总是如此,为什么会发生这种情况?它是否必须与返回视图或副本有关?如果有人能对这个问题有所了解并解释获取和设置单元格值的建议以及原因,我将不胜感激。

这里有一些使用 pandas(版本 0.15.2)的测试。

为了确保这种行为不是这个版本的错误,我也在 0.11.0 上进行了测试。我不提供结果,但趋势完全一样 - ix being much faster for getting, and iat for setting individual cells

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.rand(1000,2),columns = ['A','B'])
idx = 0

timeit for i in range(1000): df.ix[i,'A'] = 1
timeit for i in range(1000): df.iat[i,idx] = 2

>> 10 loops, best of 3: 92.6 ms per loop
>> 10 loops, best of 3: 21.7 ms per loop

timeit for i in range(1000): tmp = df.ix[i,'A'] 
timeit for i in range(1000): tmp = df.iat[i,idx] 

>> 100 loops, best of 3: 5.31 ms per loop
>> 10 loops, best of 3: 19.4 ms per loop

【问题讨论】:

  • 我认为 get_value() 和 set_value() 更快
  • 有趣的是要注意相对差异。使用 pandas 0.14.1,我分别得到以下结果:1) 10 个循环,最好的 3:每个循环 108 ms 2) 100 个循环,最好的 3:每个循环 12.1 ms 3) 100 个循环,最好的 3:5.55每个循环的毫秒数 4) 100 个循环,最好的 3 个:每个循环 9.43 毫秒
  • 看起来 iat for getting 在最近的版本中得到了显着改善。对于 pandas 0.19.1,我仍然看到 ix 在最后 2 次计时中比 iat 快,但只是勉强。以上结果是 5.31 vs 19.4 (in version 0.15.2) ,但我看到的是 6.01 vs 7.16 (in version 0.19.1)。

标签: python indexing pandas


【解决方案1】:

Pandas 用indexing classes 做了一些非常有趣的事情。我认为我无法描述一种简单的方法来知道使用哪种方法,但我可以对实现提供一些见解。

DataFrame#ix 是一个_IXIndexer,它没有声明自己的__getitem____setitem__。这两种方法很重要,因为它们控制了 Pandas 访问值的方式。由于_IXIndexer 没有声明这些方法,所以使用super class _NDFrameIndexer 代替。

进一步挖掘_NDFrameIndexer__getitem__ 表明它相对简单,并且在某些情况下封装了get_value 中的逻辑。然后__getitem__ 在某些情况下接近get_value 的速度。

_NDFrameIndexer__setitem__ 是另一回事。起初它看起来很简单,但它调用的第二个方法是_setitem_with_indexer,它在大多数情况下都做了相当多的工作。

此信息表明,使用ix 获取值的调用在最佳情况下受到get_value 的限制,使用ix 设置值的调用需要核心提交者来解释。

现在DataFrame#iat 是一个_iAtIndexer,它也没有声明自己的__getitem____setitem__,因此回退到它的超类_ScalarAccessIndexer 的实现。

_ScalarAccessIndexer 有一个simple __getitem__ 实现,但它需要一个循环才能将密钥转换为正确的格式。在调用 get_value 之前,额外的循环会增加一些额外的处理时间。

_ScalarAccessIndexer 也有一个相当的simple __setitem__ 实现,它在设置值之前转换参数set_value 所需的键。

此信息表明使用iat 获取值的调用受​​到get_valuefor loop 的限制。使用iat 设置值主要受限于对set_value 的调用。因此,使用iat 获取值会有一点开销,而设置它们的开销较小。

TL;DR

我相信您根据文档使用了正确的 Int64Index 索引访问器,但我认为这并不意味着它是最快的。直接使用 get_valueset_value 可以找到最佳性能,但它们需要更深入地了解 Pandas DataFrames 的实现方式。

备注

值得注意的是,关于 Pandas 的文档提到 get_valueset_value 已被弃用,我认为这应该是 iget_value

示例

为了显示使用几个索引器的性能差异(包括直接调用get_valueset_value),我制作了这个脚本:

example.py:

import timeit


def print_index_speed(stmnt_name, stmnt):
    """
    Repeatedly run the statement provided then repeat the process and take the
    minimum execution time.
    """
    setup = """
import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.rand(1000,2),columns = ['A','B'])
idx = 0
    """

    minimum_execution_time = min(
        timeit.Timer(stmnt, setup=setup).repeat(5, 10))

    print("{stmnt_name}: {time}".format(
        stmnt_name=stmnt_name,
        time=round(minimum_execution_time, 5)))

print_index_speed("set ix", "for i in range(1000): df.ix[i, 'A'] = 1")
print_index_speed("set at", "for i in range(1000): df.at[i, 'A'] = 2")
print_index_speed("set iat", "for i in range(1000): df.iat[i, idx] = 3")
print_index_speed("set loc", "for i in range(1000): df.loc[i, 'A'] = 4")
print_index_speed("set iloc", "for i in range(1000): df.iloc[i, idx] = 5")
print_index_speed(
    "set_value scalar",
    "for i in range(1000): df.set_value(i, idx, 6, True)")
print_index_speed(
    "set_value label",
    "for i in range(1000): df.set_value(i, 'A', 7, False)")

print_index_speed("get ix", "for i in range(1000): tmp = df.ix[i, 'A']")
print_index_speed("get at", "for i in range(1000): tmp = df.at[i, 'A']")
print_index_speed("get iat", "for i in range(1000): tmp = df.iat[i, idx]")
print_index_speed("get loc", "for i in range(1000): tmp = df.loc[i, 'A']")
print_index_speed("get iloc", "for i in range(1000): tmp = df.iloc[i, idx]")
print_index_speed(
    "get_value scalar",
    "for i in range(1000): tmp = df.get_value(i, idx, True)")
print_index_speed(
    "get_value label",
    "for i in range(1000): tmp = df.get_value(i, 'A', False)")

输出:

set ix: 0.9918
set at: 0.06801
set iat: 0.08606
set loc: 1.04173
set iloc: 1.0021
set_value: 0.0452
**set_value**: 0.03516
get ix: 0.04827
get at: 0.06889
get iat: 0.07813
get loc: 0.8966
get iloc: 0.87484
get_value: 0.04994
**get_value**: 0.03111

【讨论】:

  • 感谢您深入了解实现细节!非常有趣的信息!是的,似乎 set_value 和 get_value 是表现最好的。您是对的,无论是否折旧,文档都有些令人困惑。最近的 pandas 版本文档(0.15.1)没有明确提到它被弃用,这个文档也没有关于所有 API 更改:pandas.pydata.org/pandas-docs/dev/whatsnew.html#api-changes 它只明确提到 iget_value。
猜你喜欢
  • 2018-04-01
  • 2012-11-21
  • 2023-03-07
  • 2016-01-24
  • 2018-01-26
  • 2017-07-04
  • 1970-01-01
  • 1970-01-01
  • 2020-02-28
相关资源
最近更新 更多