【问题标题】:Computing np.diff in Pandas after using groupby leads to unexpected result使用 groupby 后在 Pandas 中计算 np.diff 会导致意外结果
【发布时间】:2019-11-14 08:30:52
【问题描述】:

我有一个数据框,我正在尝试向它附加一列顺序差异。我找到了一种我非常喜欢的方法(并且可以很好地概括我的用例)。但是一路上我注意到一件奇怪的事情。你能帮我理解一下吗?

以下是一些具有正确结构的数据(代码以here 的答案为模型):

import pandas as pd
import numpy as np
import random
from itertools import product

random.seed(1)       # so you can play along at home
np.random.seed(2)    # ditto

# make a list of dates for a few periods
dates = pd.date_range(start='2013-10-01', periods=4).to_native_types()
# make a list of tickers
tickers = ['ticker_%d' % i for i in range(3)]
# make a list of all the possible (date, ticker) tuples
pairs = list(product(dates, tickers))
# put them in a random order
random.shuffle(pairs)
# exclude a few possible pairs
pairs = pairs[:-3]
# make some data for all of our selected (date, ticker) tuples
values = np.random.rand(len(pairs))

mydates, mytickers = zip(*pairs)
data = pd.DataFrame({'date': mydates, 'ticker': mytickers, 'value':values})

好的,太好了。这给了我一个像这样的框架:

     date        ticker      value
0    2013-10-03  ticker_2    0.435995
1    2013-10-04  ticker_2    0.025926
2    2013-10-02  ticker_1    0.549662
3    2013-10-01  ticker_0    0.435322
4    2013-10-02  ticker_2    0.420368
5    2013-10-03  ticker_0    0.330335
6    2013-10-04  ticker_1    0.204649
7    2013-10-02  ticker_0    0.619271
8    2013-10-01  ticker_2    0.299655

我的目标是向该数据框中添加一个包含顺序更改的新列。数据需要这样做,但排序和差异需要“按代码”完成,以便另一个代码中的间隙不会导致给定代码的 NA。我想在不以任何其他方式干扰数据帧的情况下做到这一点(即,我不希望根据进行差异所需的内容对结果数据帧进行重新排序)。以下代码有效:

data1 = data.copy() #let's leave the original data alone for later experiments
data1.sort(['ticker', 'date'], inplace=True)
data1['diffs'] = data1.groupby(['ticker'])['value'].transform(lambda x: x.diff())
data1.sort_index(inplace=True)
data1

然后返回:

     date        ticker      value       diffs
0    2013-10-03  ticker_2    0.435995    0.015627
1    2013-10-04  ticker_2    0.025926   -0.410069
2    2013-10-02  ticker_1    0.549662    NaN
3    2013-10-01  ticker_0    0.435322    NaN
4    2013-10-02  ticker_2    0.420368    0.120713
5    2013-10-03  ticker_0    0.330335   -0.288936
6    2013-10-04  ticker_1    0.204649   -0.345014
7    2013-10-02  ticker_0    0.619271    0.183949
8    2013-10-01  ticker_2    0.299655    NaN

到目前为止,一切都很好。如果我用这里显示的更简洁的代码替换上面的中间行,一切仍然有效:

data2 = data.copy()
data2.sort(['ticker', 'date'], inplace=True)
data2['diffs'] = data2.groupby('ticker')['value'].diff()
data2.sort_index(inplace=True)
data2

快速检查表明,实际上data1 等于data2。但是,如果我这样做:

data3 = data.copy()
data3.sort(['ticker', 'date'], inplace=True)
data3['diffs'] = data3.groupby('ticker')['value'].transform(np.diff)
data3.sort_index(inplace=True)
data3

我得到一个奇怪的结果:

     date        ticker     value       diffs
0    2013-10-03  ticker_2    0.435995    0
1    2013-10-04  ticker_2    0.025926   NaN
2    2013-10-02  ticker_1    0.549662   NaN
3    2013-10-01  ticker_0    0.435322   NaN
4    2013-10-02  ticker_2    0.420368   NaN
5    2013-10-03  ticker_0    0.330335    0
6    2013-10-04  ticker_1    0.204649   NaN
7    2013-10-02  ticker_0    0.619271   NaN
8    2013-10-01  ticker_2    0.299655    0

这里发生了什么?当你在 Pandas 对象上调用 .diff 方法时,不只是调用 np.diff 吗?我知道DataFrame 类上有一个diff 方法,但是如果没有我用来使data1 工作的lambda 函数语法,我无法弄清楚如何将它传递给transform。我错过了什么吗?为什么data3 中的diffs 列有问题?我怎样才能在 transform 中调用 Pandas diff 方法而无需编写 lambda 来执行它?

【问题讨论】:

  • 请将sort=False 标志添加到 .groupby() 中。你在给我压力。出于某种原因,带有 .diff 的 .groupby 会使用大量内存,而且效率很低,并且在其中添加不必要的排序只会让情况变得更糟。
  • 您的问题是关于np.diff 而不是pandas.diff。为您编辑了标题。

标签: python pandas diff


【解决方案1】:

很好的易于重现的例子!!更多的问题应该是这样的!

只需传递一个lambda进行transform(这相当于直接传递afuncton对象,例如np.diff(或Series.diff)。所以这相当于data1/data2

In [32]: data3['diffs'] = data3.groupby('ticker')['value'].transform(Series.diff)

In [34]: data3.sort_index(inplace=True)

In [25]: data3
Out[25]: 
         date    ticker     value     diffs
0  2013-10-03  ticker_2  0.435995  0.015627
1  2013-10-04  ticker_2  0.025926 -0.410069
2  2013-10-02  ticker_1  0.549662       NaN
3  2013-10-01  ticker_0  0.435322       NaN
4  2013-10-02  ticker_2  0.420368  0.120713
5  2013-10-03  ticker_0  0.330335 -0.288936
6  2013-10-04  ticker_1  0.204649 -0.345014
7  2013-10-02  ticker_0  0.619271  0.183949
8  2013-10-01  ticker_2  0.299655       NaN

[9 rows x 4 columns]

我相信 np.diff 不遵循 numpy 自己的 unfunc 准则来处理数组输入(因此它尝试了各种方法来强制输入和发送输出,例如 __array__ 输入 __array_wrap__ 输出)。我不太确定为什么,请参阅更多信息here。所以底线是np.diff 没有正确处理索引并进行自己的计算(在这种情况下是错误的)。

Pandas 有很多方法,它们不仅仅调用 numpy 函数,主要是因为它们处理不同的 dtypes,处理 nans,在这种情况下,处理“特殊”差异。例如您可以将时间频率传递给 datelike-index,它计算实际差异的 n 数量。

【讨论】:

  • 这是奇怪的行为!
  • 是的....这可能是一个错误,也可能只是一个古怪的怪癖(IOW 他们可能是这种方式的原因)。
  • 感谢补充。我是 SO 的新手,并试图让像你这样的人更容易回答我不那么深入的问题!你跳过我的sort(['ticker', 'date'], inplace=True) 步骤了吗?因此,您的答案似乎与我的不同。没什么大不了的,我想我明白你在说什么。关于样式/稳定性/可扩展性最佳实践的任何感觉会导致您更喜欢transform(lambda x: x.diff()) 方法或blahblah.diff() 方法来完成此任务?
  • @DJ_8one6 哈哈,我改了;我意识到你已经有了 lambda 解决方案。像您这样的 groupby 正在这样做(例如,使用 data3.groupby('ticker')['value'] 会将 Series 传递给应用的函数;如果您这样做了 data3.groupby('ticker').apply(...),您将得到一个 DataFrame。这就是为什么 lambda 很好,您不必显式引用该函数作为它的匿名者。
  • 当我创建一个 groupby 时,我会这样做:data3.groupby(...).apply(f) 其中f 是:def f(x): print x; return x。准确查看传入的内容。
【解决方案2】:

可以看到Series.diff()方法与np.diff()不同:

In [11]: data.value.diff()  # Note the NaN
Out[11]: 
0         NaN
1   -0.410069
2    0.523736
3   -0.114340
4   -0.014955
5   -0.090033
6   -0.125686
7    0.414622
8   -0.319616
Name: value, dtype: float64

In [12]: np.diff(data.value.values)  # the values array of the column
Out[12]: 
array([-0.41006867,  0.52373625, -0.11434009, -0.01495459, -0.09003298,
       -0.12568619,  0.41462233, -0.31961629])

In [13]: np.diff(data.value) # on the column (Series)
Out[13]: 
0   NaN
1     0
2     0
3     0
4     0
5     0
6     0
7     0
8   NaN
Name: value, dtype: float64

In [14]: np.diff(data.value.index)  # er... on the index
Out[14]: Int64Index([8], dtype=int64)

In [15]: np.diff(data.value.index.values)
Out[15]: array([1, 1, 1, 1, 1, 1, 1, 1])

【讨论】:

  • 知道了。你的例子很清楚。在我原来的帖子中,我展示了两种有效的方法(1:涉及 lambda 和 2:涉及直接在 Pandas Series 上使用 diff 方法)。您认为哪一个更合适/最佳实践?一般来说,您是否尝试尽可能少地使用lambda,或者您是否发现它们更容易看到正在发生的事情的结构?
  • @DJ_8one6 实际上我发现 lambdas 在 pandas 中的效率惊人(试一试它的时间可以忽略不计,虽然我认为 pd.Series.diff 读起来更好)! Atm groupby.diff() 实际上是在做 groupby.apply(pd.Series.diff),这通常看起来更慢。也许在 0.14 中 .diff 可以做得更快,这是许多低调的水果 / groupby 方法之一。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-24
  • 2020-06-22
  • 1970-01-01
  • 2020-10-17
  • 2018-04-25
相关资源
最近更新 更多